Skip to content

lift: branch on the driver's exit verdict, not a re-derived done() (0.6.6 regression) - #312

Merged
lannbot merged 1 commit into
mainfrom
fix/lift-done-verdict-latched
Sep 7, 2026
Merged

lift: branch on the driver's exit verdict, not a re-derived done() (0.6.6 regression)#312
lannbot merged 1 commit into
mainfrom
fix/lift-done-verdict-latched

Conversation

@lannbot

@lannbot lannbot commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

An async-typed lifted export's driver exits on one of two verdicts:
done() held (task.return seen, no wasm call or hop in flight) or
idle (#292 / #310). Since #310 the lift RE-EVALUATED driveDone() in
its .then continuation to decide between finishHostEntry and
backgroundCompletion. That predicate is time-varying: one microtask
after an EXIT-done, another driver of the same store (the settlement
pump servicing a settled host call) can resume a background activation
that transiently hop-parks, so midWasmCall()/hopParked() read true
and the lift took backgroundCompletion() — which resolves on
Task.onFinished, i.e. never for a callback-ABI task whose spawned
futures are immortal. Regression from 0.6.5, whose continuation was
pending.then(finishHostEntry) unconditionally.

Observed in polyvisor: lifecycle.boot (spawns the engine driver,
event pump and accept loop, then task.returns) never resolved on a
reload once a store sync task added enough host-call traffic for the
pump to win the race. Trace: EXIT-done ... done=true followed by
-> backgroundCompletion threads=1 resolvedSeen=true.

Fix: drive/driveLoop/driveAsync return their exit verdict
(DriveExit = "done" | "idle") and the lift branches on that. The
verdict is a fact about the loop that ran; re-deriving it later asks a
different question. driveStoreAsync discards the verdict (the pumps
have no completion to route).

Test: runtime/tests/lift_done_verdict_test.ts — a callback-ABI lift
with an immortal second thread task.returns and exits done; a foreign
thread hop-parks in the microtask window before the continuation.
Red on main ("expected resolved, got pending"), green with the fix.

Consumer evidence: polymorph-components/polyvisor, deno task e2e on 0.6.6 fails drive-connect/drive-round-trip (boot never resolves on reload); forcing trapOnIdle: true or latching the verdict makes all 16 scenarios pass. Traces: POLYENGINE_DRIVE_TRACE + a log at the lift's exit sites.

Not in this PR (to file separately): backgroundCompletion resolves on Task.onFinished (last thread unregisters) rather than on the task's resolve callback, so a task with long-lived spawned futures that idles before task.return never answers the host even after it returns.

Automerge armed.

An async-typed lifted export's driver exits on one of two verdicts:
`done()` held (task.return seen, no wasm call or hop in flight) or
idle (#292 / #310). Since #310 the lift RE-EVALUATED `driveDone()` in
its `.then` continuation to decide between `finishHostEntry` and
`backgroundCompletion`. That predicate is time-varying: one microtask
after an EXIT-done, another driver of the same store (the settlement
pump servicing a settled host call) can resume a background activation
that transiently hop-parks, so `midWasmCall()`/`hopParked()` read true
and the lift took `backgroundCompletion()` — which resolves on
`Task.onFinished`, i.e. never for a callback-ABI task whose spawned
futures are immortal. Regression from 0.6.5, whose continuation was
`pending.then(finishHostEntry)` unconditionally.

Observed in polyvisor: `lifecycle.boot` (spawns the engine driver,
event pump and accept loop, then task.returns) never resolved on a
reload once a store sync task added enough host-call traffic for the
pump to win the race. Trace: `EXIT-done ... done=true` followed by
`-> backgroundCompletion threads=1 resolvedSeen=true`.

Fix: `drive`/`driveLoop`/`driveAsync` return their exit verdict
(`DriveExit = "done" | "idle"`) and the lift branches on that. The
verdict is a fact about the loop that ran; re-deriving it later asks a
different question. `driveStoreAsync` discards the verdict (the pumps
have no completion to route).

Test: `runtime/tests/lift_done_verdict_test.ts` — a callback-ABI lift
with an immortal second thread task.returns and exits done; a foreign
thread hop-parks in the microtask window before the continuation.
Red on main ("expected resolved, got pending"), green with the fix.
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