Settlement pump: between-calls guest liveness (embedder-api A11) - #121
Merged
Conversation
A host-import promise that settles while no driver is live only mutates scheduler state; the work sat queued until the next export call or host stream/future operation. A guest whose wakeup is a host clock — the componentize-go keep-alive-ticker shape, a task parked WAIT on a waitable set with a pending wasi:clocks wait-for — was frozen between embedder calls: a liveness gap, not a policy. The settlement pump is the third driver: armed at driver exit whenever real host calls remain outstanding (activity arms excluded), it races their settlement and drives the store to quiescence with the same loop and the same cooperative stand-down discipline as the host-activity pump (one-driver-per-store invariant unchanged; its done() returns true whenever pendingHostCalls is empty, so it provably never converts the documented embedder-never-acts hang into a trap). Failures park on hostFailure for the next call, as before. Host-call classification (arm vs real, storeQuiescent) moves to task/scheduler.ts so both between-calls drivers share it without an import cycle. Store-level regression tests pin the contract: settlement resumes a parked guest with no embedder activity; a self-re-arming call sustains progress (the ticker shape, covering the stale-snapshot nudge); arms alone never arm the pump; settlement-time failures surface on the next driving call.
lannbot
enabled auto-merge
August 12, 2026 16:57
auto-merge was automatically disabled
August 12, 2026 17:00
Pull request was closed
# Conflicts: # contracts/embedder-api.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A host-import promise that settles while no driver is live only mutates scheduler state — the registration site's continuation delivers results and readies threads, but nothing calls
serviceSettled/tickuntil the next export call or host stream/future operation. A guest with genuinely background work — canonically, a task parked WAIT on a waitable set whose pending host call is a clock (the componentize-go keep-alive ticker overwasi:clocks.wait-for, see lann/wosh#25) — was frozen between embedder calls.wasmtime delivers such wakeups whenever the embedder dwells in
run_concurrent; on a JS host the event loop is always dwelling, so deltic can make it unconditional.Change
exec/boundary.ts: the settlement pump — a detached keeper armed at driver exit (driveAsync's finally anddrive's synchronous completion) whenever real host calls remain outstanding. It races their settlement and drives the store to quiescence with the same loop and the same cooperative stand-down discipline as the host-activity pump: stands down while any driver is live; itsdone()is true wheneverpendingHostCallsis empty (the precondition of both deadlock traps, so it provably never converts the documented embedder-never-acts hang into a trap); failures park onhostFailurefor the next call. A nudge promise covers stale race snapshots (new host calls registered while parked — the ticker re-arm case).task/scheduler.ts: host-call classification (markHostActivityArm,hasRealHostCall,realHostCalls,storeQuiescent) moves here so both between-calls drivers share it without an import cycle;exec/host_streams.tsconverts, behavior unchanged.contracts/embedder-api.md: amendment A11 (between-calls liveness normative; embedder-never-acts still hangs, never traps; failures still surface on the next driving call).docs/architecture.md: the driver roster is now exactly three (export calls, host-activity pump, settlement pump), all under the one-driver-per-store cooperative invariant.runtime/tests/settlement_pump_test.ts: store-level pins — T-1 settlement resumes a parked guest with no embedder activity; T-2 self-re-arming call sustains progress (ticker shape, stale-snapshot nudge); T-3 activity arms alone never arm the pump (no ticks, no spin, no trap); T-4 settlement-time failure parks and surfaces on the next call. T-1/T-2 verified failing on the pre-pump runtime.Gates
Full
just gatesgreen locally: build, test-rust, test-runtime (493), test-protocol, test-wasi-shims, test-ct-runner, test-bundle, publish-check, examples, test-translate, conformance, sched-seeds, test-ports, test-webrtc, shells (sm/node/jsc/bun), browsers (chromium+firefox), websocket-conformance, smoke-tls, smoke-c0.