resources: host-initiated dtors run as full canonical lifts - #163
Merged
Conversation
Fixes #160. definitions.py canon_resource_drop (line 2319) invokes the dtor through Store.lift with CanonicalOptions(async_ = False) — a full canonical lift. The host-initiated drop path instead called the dtor bare while HOLDING enterFrom(null) across its returned promise (callDtorGated allowAsync, the #85 shape), with no Task/Thread behind the activation. Three structural defects followed: * #160: the held bracket made the impl non-host-enterable, so tick's enterability filter (#155) could never resume a suspension point of the dtor's own activation, while the completion promise sat in pendingHostCalls advertising external work — every driver parked on it forever. (Between calls the empty ambient made currentTask() signal PendingCapability before the park — the loud presentation; the silent wedge needed a foreign ambient, the #24 class.) * it was the runtime's only enterFrom(null) bracket spanning an await: the macro-time window of the #156 class, locking every sibling instance through the synthetic root for the dtor's whole activation. * built-ins inside the dtor had no ambient task at all. Now: createDtorEntry builds the dtor's entry with createLiftedFunction (ft = [u32] -> (), inert options, coreType (i32)->()), and hostDtorCall routes every host-initiated drop through it (executor pre-wires dtorHost; direct-construction tokens get it lazily). The activation has a real Task/implicit Thread, the bracket releases at the first park, suspension points resume through tick, settled tails flow through serviceSettled, and traps poison through the lift harness's own path. Async failures park on store.hostFailure (first failure wins); the completion promise is deliberately NOT a pendingHostCalls entry — that registration was the "external work" lie, and it had no other consumer. callDtorGated keeps only the guest-initiated synchronous discipline. One additive harness parameter, allowAsyncCompletion: a sync-typed lift whose caller needs no synchronous answer skips driveSyncLift so a plain-mode thenable-returning (host JS) dtor parks instead of tripping the sync loop's bogus deadlock; the real deadlock trap is still enforced by drive, the same substitution jspi mode makes. Revises the #85 pins (the gate-hold behavior WAS the bug) and adds regression pins: a dtor parked on a scheduler-resumable suspension point completes, and siblings stay enterable while a dtor is in flight. Gates: just gates (full pass; conformance 1257/0, no expectation changes; sched-seeds clean). Embedder surface unchanged (drop() still non-blocking, same failure channel); lockstep version untouched.
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.
resources: host-initiated dtors run as full canonical lifts
Fixes #160. Design settled by the reference itself: definitions.py
canon_resource_drop(line 2319) invokes the dtor throughStore.liftwithCanonicalOptions(async_ = False)— a full canonical lift. The held-bracket path this PR deletes was the divergence, not the fix candidate.The bug (#160)
The host-initiated drop path (
callDtorGated(allowAsync=true), the #85 shape) called the dtor bare and HELDenterFrom(null)— leaf and synthetic root — across the returned promise, with no Task/Thread behind the activation. Three structural defects:Store.tick's enterability filter (scheduler: tick skips threads whose instance is not host-enterable #155) could never resume a suspension point belonging to the dtor's own activation — while the completion promise sat inpendingHostCallsadvertising external work, parking every driver on it forever. Every deadlock detector reads a pending host call as "the host owes an event"; this was the one promise whose settlement needed the scheduler itself.enterFrom(null)bracket spanning an await, locking every sibling instance through the shared synthetic root for the dtor's whole activation (pinned pre-fix by the new sibling test:mayEnterFrom(null) === falsethroughout).currentTask()signalledPendingCapabilitybefore any park (the loud presentation, confirmed by running the new regression test against the old code); with a foreign ambient live, the Guest callback invoked with null context slot (wit-bindgen async_support.rs:578) — polymorph-tls webcrypto-composed target #24 misattribution class.The fix
createDtorEntry(exec/boundary.ts): the dtor's entry built with the existingcreateLiftedFunction—ft = [u32] → (), inertResolvedOptions,coreType (i32) → (), per-modeenterWasminside the harness. The activation gets a real Task/implicit Thread; the entry bracket releases at the first park (leave()before the drive); suspension points resume throughtick; settled tails flow through scheduler: resumeWith has the same not-host-enterable window as tick, and the tick fix cannot transfer #156'sserviceSettled; traps poison through the harness's ownpoison().hostDtorCall(exec/boundary.ts): the host-initiated drop entry —impl === nulldirect path unchanged; otherwise the lifted entry (executor pre-wiresResourceTypeInfo.dtorHost; direct-construction tokens get it lazily). Async failures park onstore.hostFailure ??=(the existing channel). The completion promise is deliberately not registered inpendingHostCalls: that registration was the "external work" lie, and it had no consumer beyond the drivers it misled. The dtor's genuine external dependencies — its host imports — register themselves when they park.callDtorGated(cabi/handles.ts) keeps only the guest-initiated synchronous discipline (allowAsyncremoved); the resources: own-drop dtor bypasses may_enter gating and trap-poisoning; JS-initiated drops skip the promising entry #85 guest-side frame-rule limitation is untouched.allowAsyncCompletion: a sync-typed lift whose caller needs no synchronous answer (only the dtor today —drop(): voidis documented non-blocking) skipsdriveSyncLift, so a plain-mode thenable-returning (host JS) dtor parks instead of tripping the sync loop's bogus deadlock. Not a weakening:driveenforces the same deadlock trap asynchronously — the substitution jspi mode already makes unconditionally.Pinned behavior changes (the #85 pins are revised — that behavior was the bug)
impl.mayEnter === truewhile the dtor is in flight; nopendingHostCallscompletion entry.currentTask()resolves to the dtor's own task) completes once ready — on the old code this test dies atPendingCapabilitybefore the park, the loud presentation of the missing-task defect.mayEnterFrom(null)while a dtor is in flight (pre-fix: locked through the root).store.hostFailure === boom, impl poisoned (mapCoreExceptionpasses non-RuntimeErrorrejections through unchanged).Relation to #156/#161: this removes the only macro-time reachability window of the deferral class; the deferral itself remains correct and necessary for the synchronous windows.
Gates
just gates— full pass. Conformance 1416 commands: 1257 passed, 0 failed, no expectation changes;sched-seedsclean. Embedder surface unchanged (drop(): voidstill non-blocking, same failure channel), so the lockstep version is left alone per AGENTS.md.