From f734d700a2893422d14f8ae5dd874f10e61624c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20T=C3=B6rcsv=C3=A1ri?= Date: Sun, 2 Aug 2026 08:18:08 +0200 Subject: [PATCH] =?UTF-8?q?fix(asyncify):=20retire=20the=20deferral=20fami?= =?UTF-8?q?ly=20=E2=80=94=20guard-layer=20road=20closed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v0.1.24 in prod, doubly convicted the same morning: the Leonardo open crawled/hung (open:settled result=failed at the 60s escape, heap never past 256MB — every main-loop iteration runs INSIDE its yield-wake extent, so the "root-owned wake" scope matched thousands of legitimate nested coroutine Call/returns per open, each paying a deferred macrotask, throttled to ≥1s in a background tab), AND the Nano crashed 22ms after deferrals=1 fired. Harmful and insufficient: the fatal nested-rewind interleave and the benign bulk are observationally identical at this layer — no discriminator exists. Retired (second and final retraction, async/16 round 5). What stays shipped and clean: consume-once root suspensions, the internally-parked quarantine + laundering check, the flight recorder + beacons, the WSOD floor, the pendingSleeps leak fix (confirmed by pendingSleeps=[] in the Nano dump). The rare nested-rewind crash is ACCEPTED and fully observable until the structural fix — the design-B fiber-first runtime (async/06,12,13), where one scheduler owns every suspension and this interleave cannot exist. .ci-cache-epoch 6→7. Local: fiber 2/2 (one refusal beacon) + timer + firefox sweep 21 passed, chromium scenarios 11 passed/4 quarantine-skips, web fatal+follow 2/2. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4 --- .ci-cache-epoch | 2 +- docs/features/async/16-fiber-resume-guard.md | 24 +++++++++++ scripts/common/shims/handlesleep.js | 42 ++++++-------------- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/.ci-cache-epoch b/.ci-cache-epoch index 1e8b314..7f8f011 100644 --- a/.ci-cache-epoch +++ b/.ci-cache-epoch @@ -1 +1 @@ -6 +7 diff --git a/docs/features/async/16-fiber-resume-guard.md b/docs/features/async/16-fiber-resume-guard.md index f587542..476141d 100644 --- a/docs/features/async/16-fiber-resume-guard.md +++ b/docs/features/async/16-fiber-resume-guard.md @@ -182,6 +182,30 @@ wake is live (`Asyncify.__wakingRoot`), with the pushes sleep contexts (the v0.1.23 dump carried ~380 leaked zero-linked entries). +## Round 5 (2026-08-02) — the scoped deferral hangs opens: RETIRED, road closed + +v0.1.24 in prod: `open:settled result=failed` at the 60 s escape, heap never +past 256 MB, no traps, no beacons — the open crawled and (in a +timer-throttled background tab) effectively hung. Cause: the main loop's +every iteration runs INSIDE its yield-wake's synchronous extent (the v0.1.23 +recording shows `sleep … w=1` on every tick), so "root re-entry during a +root-owned wake" matches every legitimate nested coroutine Call/return in a +board open — thousands per load, each paying a deferred macrotask. + +**Verdict on the whole deferral family:** the fatal nested-rewind interleave +and the benign nested round-trips share the same observable signature at the +JS runtime layer. No discriminator exists here — this road is closed. What +remains shipped and sound: the consume-once validity + internally-parked +quarantine (never implicated in a regression), the flight recorder, the +beacons, and the WSOD floor. The rare nested-rewind crash is ACCEPTED, fully +observable, until the structural fix. + +**The structural fix is design B** (06/12/13-design-b-*): one fiber-first +scheduler owning every suspension, so dual-protocol nesting cannot exist. +That is the next real investment; guard-layer iteration has hit its ceiling +— five variants, each defeated by a neighboring interleave or by taxing the +benign bulk. + ## Flight recorder (round 3, targeting instrument) The shim keeps a 96-entry ring of asyncify/fiber events (sleep entries with diff --git a/scripts/common/shims/handlesleep.js b/scripts/common/shims/handlesleep.js index 8f3c358..1c64af4 100644 --- a/scripts/common/shims/handlesleep.js +++ b/scripts/common/shims/handlesleep.js @@ -323,36 +323,18 @@ if (typeof Fibers !== "undefined" var isRoot = newFiber === Fibers.__rootFiber; - // THE prod killer, finally recorded by the flight recorder (v0.1.23 dump, - // event "fcs … ROOT w=1" immediately before the trap): a fiber round-trip - // executed inside the ROOT's OWN sleep-wake continuation re-suspends and - // re-rewinds the root NESTED inside its live wake rewind — two rewind - // lifetimes on one context; asyncify dies with state stuck at Rewinding. - // Fiber-owned wakes completing into root are the benign bulk (the same - // recording shows dozens at w=0 / fiber-owned) and are NOT deferred — - // that unscoped deferral was the retracted S4-flaking version. Only a - // root re-entry during a ROOT-OWNED wake defers, one macrotask, so the - // outer rewind fully settles first. - if (isRoot && (Asyncify.__wakingRoot || 0) > 0) { - var deferred = newFiber; - Fibers.__rootDeferrals = (Fibers.__rootDeferrals || 0) + 1; - if (Fibers.__rootDeferrals <= 10 || Fibers.__rootDeferrals % 100 === 0) { - console.warn("[wx-asyncify] root-entry-deferred: fiber completion inside the root's own " - + "wake window; retrying next tick (occurrence " + Fibers.__rootDeferrals + ")"); - } - __fcsRec("defer ROOT-self new=" + deferred); - var retry = function() { - if (Fibers.trampolineRunning || Fibers.nextFiber) { - setTimeout(retry, 0); - return; - } - __fcsRec("defer-retry new=" + deferred); - Fibers.nextFiber = deferred; - Fibers.trampoline(); - }; - setTimeout(retry, 0); - return; - } + // DEFERRAL RETIRED (2026-08-02, second retraction — see async/16 round 5). + // Both deferral variants are unsound: the main loop's every iteration runs + // INSIDE its yield-wake's synchronous extent, so "root re-entry during a + // root-owned wake" also matches every legit nested coroutine Call/return + // in a board open — v0.1.24 deferred thousands of them per load and the + // open crawled/hung (open:settled result=failed at the 60s escape, + // "hung forever" with a throttled background tab). The fatal interleave + // and the benign bulk share the same observable signature at this layer; + // the discriminator does not exist here. The rare nested-rewind crash is + // accepted until the fiber-first runtime (design B) removes the dual + // suspension protocols altogether; the recorder keeps every occurrence + // fully observable. var HEAPU32v = (typeof GROWABLE_HEAP_U32 === "function") ? GROWABLE_HEAP_U32() : HEAPU32; var entryPoint = HEAPU32v[((newFiber + 12) >>> 2) >>> 0];