You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The runner has no defense against hanging cases: one wedged case hangs the whole run (at --jobs 8, a worker) until the CI job limit kills everything with zero attribution. The no-retries policy makes a guard the only defense. Design settled in discussion; recording here.
Two budgets for two hang classes (each enforceable only where its class manifests):
--case-execution-budget <secs> (default 10, 0 disables): budget on actual wasm execution, approximated as epoch ticks observed by Store::epoch_deadline_callback (the callback only fires while wasm executes and missed ticks collapse on resume, so preemption under-counts — contention-robust, ~thread time). Catches CPU spins, where a host-side timer is useless because the executor thread is stuck inside wasm. Trips as a distinguishable error through the normal trap plumbing.
--case-timeout <secs> (default 120, 0 disables): wall clock from case start, enforced by racing the run future against a timer. Catches async wedges (pending().await, wedged stream reads — the findings-wasi:cli runner #3 class), where the epoch callback is useless because no wasm executes. Scheduled-time exclusion (wake→poll latency) deliberately omitted: one case per worker executor today makes it ~zero; revisit with Future: additive concurrent-tests interface (concurrent runs + streaming enumeration) #20.
Containment = the existing trap path: mark the case failed, abandon the session (store drop unwinds suspended fibers safely), fresh session next case. Both --jobs paths via a shared per-case helper (extracting it first — the sequential/parallel copy-paste divergence from #43).
Wire form: Provenance::HangGuard (reserved, never emitted) is renamed in its last safe window to limit-exceeded(string) — externally tagged, so unit provenances stay flat strings and this one serializes as {"limit-exceeded":"execution-budget"} / {"limit-exceeded":"case-timeout"}, leaving room for future limits (memory, fuel) as payload vocabulary rather than schema change. Status fail, one-line detail with the configured value, diagnostics-complete: false, fold's not-reached cause-attribution updated to match.
Enumeration is guarded too (a wedged registry constructor): trips there are run errors (exit 2), not case results.
Fixture: components/hang-fixture (broken by design): hang/spin (loop {} — epoch path), hang/pend (std::future::pending().await — timer path), hang/after (containment). Gated tests run it at 1s/2s budgets.
Out of scope: composed runner (it is the wasm; its guard is the outer wasmtime invocation) and jco/Node (can't preempt wasm). Follow-up issue covers context.progress keepalive + --hard-timeout (requires a wit/tests.wit growth-channel investigation: @since gates vs version-linkage across wit-bindgen/jco/wac).
The runner has no defense against hanging cases: one wedged case hangs the whole run (at
--jobs 8, a worker) until the CI job limit kills everything with zero attribution. The no-retries policy makes a guard the only defense. Design settled in discussion; recording here.Two budgets for two hang classes (each enforceable only where its class manifests):
--case-execution-budget <secs>(default 10,0disables): budget on actual wasm execution, approximated as epoch ticks observed byStore::epoch_deadline_callback(the callback only fires while wasm executes and missed ticks collapse on resume, so preemption under-counts — contention-robust, ~thread time). Catches CPU spins, where a host-side timer is useless because the executor thread is stuck inside wasm. Trips as a distinguishable error through the normal trap plumbing.--case-timeout <secs>(default 120,0disables): wall clock from case start, enforced by racing the run future against a timer. Catches async wedges (pending().await, wedged stream reads — the findings-wasi:cli runner #3 class), where the epoch callback is useless because no wasm executes. Scheduled-time exclusion (wake→poll latency) deliberately omitted: one case per worker executor today makes it ~zero; revisit with Future: additiveconcurrent-testsinterface (concurrent runs + streaming enumeration) #20.Containment = the existing trap path: mark the case failed, abandon the session (store drop unwinds suspended fibers safely), fresh session next case. Both
--jobspaths via a shared per-case helper (extracting it first — the sequential/parallel copy-paste divergence from #43).Wire form:
Provenance::HangGuard(reserved, never emitted) is renamed in its last safe window tolimit-exceeded(string)— externally tagged, so unit provenances stay flat strings and this one serializes as{"limit-exceeded":"execution-budget"}/{"limit-exceeded":"case-timeout"}, leaving room for future limits (memory, fuel) as payload vocabulary rather than schema change. Statusfail, one-line detail with the configured value,diagnostics-complete: false, fold's not-reached cause-attribution updated to match.Enumeration is guarded too (a wedged registry constructor): trips there are run errors (exit 2), not case results.
Fixture:
components/hang-fixture(broken by design):hang/spin(loop {}— epoch path),hang/pend(std::future::pending().await— timer path),hang/after(containment). Gated tests run it at 1s/2s budgets.Out of scope: composed runner (it is the wasm; its guard is the outer wasmtime invocation) and jco/Node (can't preempt wasm). Follow-up issue covers
context.progresskeepalive +--hard-timeout(requires awit/tests.witgrowth-channel investigation:@sincegates vs version-linkage across wit-bindgen/jco/wac).