Invalidate warmed wasm modules precisely
This commit is contained in:
parent
ada01e34f4
commit
73a2671112
@ -128,6 +128,9 @@ without instantiating it.
|
||||
backend is initialized and the requested artifact/handler is currently
|
||||
available. If an artifact is cold or stale, dispatch compiles it on demand via
|
||||
`get_shared_unit()` and rechecks. There is no native unit-execution fallback.
|
||||
Workers identify cached unit artifacts by nanosecond mtime, ctime, and size.
|
||||
Whole-second mtime alone is insufficient because a dependency-triggered rebuild
|
||||
can replace a wasm artifact within the same second as its prior build.
|
||||
|
||||
---
|
||||
|
||||
@ -323,11 +326,13 @@ header free-functions are `inline`. The wasm backend exposes only declarations
|
||||
## 10. Testing
|
||||
|
||||
- **Regression gate**: `scripts/run_cli_tests.sh --include-wasm-kill` runs the
|
||||
in-runtime CLI suite (`site/tests/cli_runner.uce`) plus the site test pages.
|
||||
in-runtime CLI suite (`site/tests/cli_runner.uce`) plus the site test pages and
|
||||
`scripts/test_dependency_invalidation.sh`. The latter changes a transitive
|
||||
`#load`, then replaces a warmed worker artifact while preserving its
|
||||
whole-second mtime to prove both compiler and worker caches invalidate it.
|
||||
|
||||
- **WebSocket end-to-end**: a headless client performs a raw WS handshake to
|
||||
`:HTTP_PORT` with path `/site/tests/websockets.ws.uce` (self-resolving
|
||||
`SCRIPT_FILENAME`) and asserts the `hello-ack` frame — exercising the full
|
||||
broker → worker → broker → client chain across process boundaries.
|
||||
|
||||
|
||||
|
||||
@ -32,17 +32,34 @@ printf '%s\n' \
|
||||
'CLI(Request& context) { print(dependency_cache_marker()); }' \
|
||||
'RENDER(Request& context) { print(dependency_cache_marker()); }' >"$source_dir/parent.uce"
|
||||
|
||||
first=$(scripts/uce-cli "/$test_name/parent.uce")
|
||||
if [[ "$first" != *dependency-marker-a* ]]; then
|
||||
echo "dependency invalidation setup failed: $first" >&2
|
||||
exit 1
|
||||
fi
|
||||
assert_marker() {
|
||||
local path="$1"
|
||||
local expected="$2"
|
||||
local output
|
||||
output=$(scripts/uce-cli "/$test_name/$path.uce")
|
||||
if [[ "$output" != *"$expected"* ]]; then
|
||||
echo "$path returned stale module; expected $expected: $output" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_marker parent dependency-marker-a
|
||||
|
||||
sed -i 's/dependency-marker-a/dependency-marker-b/' "$source_dir/child.uce"
|
||||
second=$(scripts/uce-cli "/$test_name/parent.uce")
|
||||
if [[ "$second" != *dependency-marker-b* ]]; then
|
||||
echo "loaded dependency change did not recompile parent: $second" >&2
|
||||
exit 1
|
||||
fi
|
||||
assert_marker parent dependency-marker-b
|
||||
|
||||
# Warm every configured worker, then replace the artifact while retaining its
|
||||
# whole-second mtime. The worker cache must notice the nanosecond/ctime change.
|
||||
for _ in {1..16}; do assert_marker parent dependency-marker-b; done
|
||||
sed 's/dependency-marker-b/dependency-marker-c/' "$source_dir/child.uce" >"$source_dir/alternate-child.uce"
|
||||
sed 's/child.uce/alternate-child.uce/' "$source_dir/parent.uce" >"$source_dir/alternate.uce"
|
||||
assert_marker alternate dependency-marker-c
|
||||
parent_wasm="$cache_dir/parent.uce.wasm"
|
||||
alternate_wasm="$cache_dir/alternate.uce.wasm"
|
||||
parent_mtime=$(stat -c %Y "$parent_wasm")
|
||||
cp "$alternate_wasm" "$parent_wasm"
|
||||
touch -d "@$parent_mtime" "$parent_wasm"
|
||||
rm -f "$cache_dir/parent.uce.cwasm"
|
||||
for _ in {1..16}; do assert_marker parent dependency-marker-c; done
|
||||
|
||||
echo "dependency invalidation passed"
|
||||
|
||||
@ -75,7 +75,9 @@ struct WasmUnitModule
|
||||
{
|
||||
String source_path; // absolute .uce path
|
||||
String wasm_path; // artifact in the unit cache
|
||||
time_t mtime = 0;
|
||||
u64 modified_ns = 0;
|
||||
u64 changed_ns = 0;
|
||||
u64 size = 0;
|
||||
WasmDylinkInfo dylink;
|
||||
WasmAbiInfo abi;
|
||||
std::optional<wasmtime::Module> module;
|
||||
@ -640,13 +642,17 @@ public:
|
||||
return(nullptr);
|
||||
}
|
||||
auto cached = module_cache.find(wasm_path);
|
||||
if(cached != module_cache.end() && cached->second->mtime == st.st_mtime)
|
||||
u64 modified_ns = (u64)st.st_mtim.tv_sec * 1000000000ull + (u64)st.st_mtim.tv_nsec;
|
||||
u64 changed_ns = (u64)st.st_ctim.tv_sec * 1000000000ull + (u64)st.st_ctim.tv_nsec;
|
||||
if(cached != module_cache.end() && cached->second->modified_ns == modified_ns && cached->second->changed_ns == changed_ns && cached->second->size == (u64)st.st_size)
|
||||
return(cached->second);
|
||||
|
||||
auto unit = std::make_shared<WasmUnitModule>();
|
||||
unit->source_path = source_path;
|
||||
unit->wasm_path = wasm_path;
|
||||
unit->mtime = st.st_mtime;
|
||||
unit->modified_ns = modified_ns;
|
||||
unit->changed_ns = changed_ns;
|
||||
unit->size = (u64)st.st_size;
|
||||
std::vector<u8> bytes;
|
||||
if(!wasm_read_file(wasm_path, bytes))
|
||||
{
|
||||
@ -696,7 +702,8 @@ private:
|
||||
struct stat cached_stat;
|
||||
if(stat(cached_path.c_str(), &cached_stat) == 0 && stat(wasm_path.c_str(), &wasm_stat) == 0)
|
||||
{
|
||||
if(cached_stat.st_mtime > wasm_stat.st_mtime)
|
||||
if(cached_stat.st_mtim.tv_sec > wasm_stat.st_mtim.tv_sec ||
|
||||
(cached_stat.st_mtim.tv_sec == wasm_stat.st_mtim.tv_sec && cached_stat.st_mtim.tv_nsec > wasm_stat.st_mtim.tv_nsec))
|
||||
{
|
||||
auto deserialized = wasmtime::Module::deserialize_file(engine, cached_path);
|
||||
if(deserialized)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user