fix(asyncify): consume-once root suspensions — replaces the wake-window deferral
The deferral (9ca2ac1) modeled the wrong condition and taxed every parked
fiber completion with a macrotask hop: under CI load that stretched
three-client apply chains and flaked drift-trio S4 twice consecutively
(26/26 green locally under stress) — retracted.
The actual fatal state, readable in all four prod stacks once seen: a SECOND
rewind of the same root suspension. Root suspends once per fiber_swap out of
it; two parked fibers completing against one root suspension epoch (a tool
fiber + a collab fiber both waking around open:settled) each drive
finishContextSwitch(root) — the second rewinds already-consumed data →
"unreachable executed" → poisoned runtime, with the wake-side "index out of
bounds" as the sibling symptom.
Cure: stop exempting root from the validity check the shim already keeps.
First consumption proceeds synchronously — zero added latency anywhere; the
second is refused ([wx-asyncify] "root suspension already consumed") — the
yielded fiber stays properly suspended and resumable, root continues via its
real pending resume, libcontext's ghost-epoch contract enforced one layer
lower. Root remains exempt only from the internally-parked quarantine (its
yield park is routine).
.ci-cache-epoch 4→5 (the epoch-4 cache holds the retracted deferral shim).
Local: fiber 2/2 + timer 1/1, firefox sweep 20 passed, drift-trio-scenarios
kicad-chromium 26/26 under 3-worker stress, web fatal+follow 2/2.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
This commit is contained in:
parent
eff5befd5d
commit
ae33a100c2
3 changed files with 40 additions and 42 deletions
|
|
@ -127,12 +127,26 @@ the fiber's `finishContextSwitch` rewind of `main+20` — replaying frames
|
|||
over live state. The 8 ms-earlier "index out of bounds" is the wake side of
|
||||
the same collision.
|
||||
|
||||
Root entry is legal and constant in healthy flow; ONLY the wake-window
|
||||
overlap is fatal. **Layer 3: serialize, don't refuse.** The shim marks the
|
||||
synchronous wake window (`Asyncify.__inSleepWake` around `wakeUp()`);
|
||||
`finishContextSwitch(root)` inside that window is DEFERRED one macrotask
|
||||
(`[wx-asyncify] root-entry-deferred` beacon) and re-fired via the trampoline
|
||||
once the wake has settled. Ordering change only — nothing is dropped.
|
||||
**Layer 3, first attempt (wake-window deferral) — RETRACTED same day:**
|
||||
deferring every root entry inside a sleep-wake window taxed EVERY parked
|
||||
fiber completion with a macrotask hop; under CI load that stretched
|
||||
three-client apply chains and flaked drift-trio S4 twice in a row (green
|
||||
26/26 locally). It also modeled the wrong condition.
|
||||
|
||||
**Layer 3, final: consume-once root suspensions.** The actual fatal state is
|
||||
a SECOND rewind of the same root suspension: root suspends once per
|
||||
`fiber_swap` out of it, but two parked fibers completing against one root
|
||||
suspension epoch (a tool fiber + a collab fiber both waking around
|
||||
`open:settled`) each trigger `finishContextSwitch(root)` — the second rewinds
|
||||
already-consumed data → "unreachable executed". The shim already records
|
||||
every suspension (including root's); the fix is simply to stop exempting
|
||||
root from the validity check: first consumption proceeds synchronously (zero
|
||||
added latency anywhere), the second is refused
|
||||
(`[wx-asyncify] fiber-resume-refused: root suspension already consumed`) —
|
||||
the yielded fiber stays properly suspended and resumable, root continues via
|
||||
its real pending resume, same contract as libcontext's ghost epochs enforced
|
||||
one layer lower. Root stays exempt ONLY from the internally-parked
|
||||
quarantine (its yield park is routine — the 19-red lesson).
|
||||
|
||||
## The white screen, round 3 (fixed at the DOM level)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue