Skip to content

lift: settle an idle-exited async export on task.return, not thread drain - #315

Merged
lannbot merged 1 commit into
mainfrom
fix/background-completion-on-return
Sep 8, 2026
Merged

lift: settle an idle-exited async export on task.return, not thread drain#315
lannbot merged 1 commit into
mainfrom
fix/background-completion-on-return

Conversation

@lannbot

@lannbot lannbot commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #313.

backgroundCompletion (#310) resolved an idle-exited async lift on Task.onFinished — the task's last thread unregistering. A callback-ABI export that keeps spawned futures alive for the instance's life (wit-bindgen spawn_local: event loop, driver, accept loop) never reaches that moment, so a lift that went idle before task.return and was later woken by another driver had its results captured and its host Promise left pending forever. Silent hang, found while tracing #312.

The reference's answer event is on_resolve, called from Task.return_ (definitions.py); lift_and_run keeps ticking afterwards for other work, not to produce the result. wasmtime's call_concurrent has the same shape. The lift's own onResolve closure now fires a one-shot hook that settles the Promise; an already-resolved task settles at once (generalizing the old threads.length === 0 case). No deferral to "no wasm call in flight": driveDone's midWasmCall/hopParked clauses keep a driver driving under a suspended activation, and on this path there is no lift driver left to stop.

Task.onFinished and its unregisterThread firing block are deleted — this obsoletes them and nothing else used them.

Test: runtime/tests/lift_background_return_test.ts, store-level in the style of lift_done_verdict_test.ts. An export goes idle with an immortal thread and a returner parked on a flag; a later export call drives the store; the returner calls task.return; the first Promise resolves while the immortal thread is still parked (asserted, so drain is not what settled it). Red on main ("expected {v:undefined}, got pending"), green with the fix; also green under POLYENGINE_SCHED_SEED=1 and 7.

Gates: just test-runtime — 739 passed, 0 failed. Goldens untouched; no breaking/* label (bug fix).

Automerge armed.

…rain

`backgroundCompletion` (#310) resolved the host's Promise from
`Task.onFinished` — the moment the task's last thread unregistered. A
callback-ABI export that keeps spawned futures alive for the instance's
life (wit-bindgen `spawn_local`: an event loop, a driver, an accept
loop) never reaches that moment, so a lift that went idle before
`task.return` and was later woken by another driver had its results
captured and its Promise left pending forever. Silent hang; found while
tracing #312.

The reference's answer event is `on_resolve`, called from
`Task.return_`; `lift_and_run` keeps ticking afterwards for OTHER work,
not to produce the result. wasmtime's `call_concurrent` is the same
shape. So: the lift's own `onResolve` closure now fires a one-shot hook
that settles the Promise, and an already-resolved task settles at once
(generalizing the old `threads.length === 0` case). No deferral to
"no wasm call in flight": `driveDone`'s liveness clauses keep a DRIVER
driving, and on this path there is no lift driver left to stop.

`Task.onFinished` and its `unregisterThread` firing block are deleted;
nothing else used them.

Test: runtime/tests/lift_background_return_test.ts — an export goes
idle with an immortal thread and a parked returner, a later export call
drives the store, the returner calls `task.return`, the first Promise
resolves while the immortal thread is still parked. Red on main, green
with the fix.

Closes #313.
@lannbot
lannbot enabled auto-merge September 8, 2026 02:39
@lannbot
lannbot merged commit 3ddedd7 into main Sep 8, 2026
4 checks passed
@lannbot
lannbot deleted the fix/background-completion-on-return branch September 8, 2026 02:43
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.

backgroundCompletion waits for thread exit, not task.return; immortal spawned futures make an idle-exited lift never resolve

2 participants