Skip to content

Settlement pump: between-calls guest liveness (embedder-api A11) - #121

Merged
lannbot merged 2 commits into
mainfrom
settlement-pump
Aug 12, 2026
Merged

Settlement pump: between-calls guest liveness (embedder-api A11)#121
lannbot merged 2 commits into
mainfrom
settlement-pump

Conversation

@lannbot

@lannbot lannbot commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

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/tick until 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 over wasi: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 and drive'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; its done() is true whenever pendingHostCalls is empty (the precondition of both deadlock traps, so it provably never converts the documented embedder-never-acts hang into a trap); failures park on hostFailure for 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.ts converts, 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 gates green 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.

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
lannbot enabled auto-merge August 12, 2026 17:05
@lannbot
lannbot merged commit a2f84a5 into main Aug 12, 2026
4 checks passed
@lannbot
lannbot deleted the settlement-pump branch August 23, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants