From 78ec52ea14ffafd24e8352bfbfb8fda401edbf00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20T=C3=B6rcsv=C3=A1ri?= Date: Fri, 7 Aug 2026 19:05:00 +0200 Subject: [PATCH] docs 22 Phase B: gaps 1+2 closed, and the D-on boundary measured on KiCad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase B increment recorded in §10. The wx battery is GREEN at D-on (395/1, the 1 pre-existing) with dispatch contexts, context waits and star transfers all live - the first clean battery of the migration. Gaps 1 and 2 from the D-on probe are closed (terminal coroutine finish; wake/refusal semantics), and a third containment was found and added: an exception escaping a handler propagates out through drain()'s fiber swap and would otherwise leave the registry mid-transition, dead-pumping every later wait. Shim carries the new abandon call; .ci-cache-epoch -> 12. THE BOUNDARY: on the full KiCad suite D-on loses four canvas-tool specs (draw-wires, draw-lines, move-with-m, presence-locks move) to `index out of bounds` in doRewind - the blue screen itself. Real tool coroutines park IN PLACE inside their bodies, and a star transfer over an already-parked stack rewinds state the fiber layer cannot see; the harness's coroutines yield cleanly, so it goes green while KiCad does not (the doc-19 lesson again: the harness models the shape, not the parks). So D cannot carry KiCad until the tool-body park sites are contexts too - C+E completion, which §5 already ordered before the flip. This measurement makes that ordering non-negotiable. Landing state verified: STAR_DISPATCH=0, kicad 139 passed / 1 (pre-existing occ-probe glb) = the Phase A baseline exactly, wx battery 395/1. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LBjomQfKyRa3jBdeAKpmTw --- .ci-cache-epoch | 2 +- .../features/async/22-absorbing-libcontext.md | 56 +++++++++++++++++++ kicad | 2 +- scripts/common/shims/asyncify-scheduler.js | 5 ++ wxwidgets | 2 +- 5 files changed, 64 insertions(+), 3 deletions(-) diff --git a/.ci-cache-epoch b/.ci-cache-epoch index b4de394..48082f7 100644 --- a/.ci-cache-epoch +++ b/.ci-cache-epoch @@ -1 +1 @@ -11 +12 diff --git a/docs/features/async/22-absorbing-libcontext.md b/docs/features/async/22-absorbing-libcontext.md index 5976aed..9c14b64 100644 --- a/docs/features/async/22-absorbing-libcontext.md +++ b/docs/features/async/22-absorbing-libcontext.md @@ -643,6 +643,62 @@ close, each with a deterministic repro suite: topology (every party a context, `return_to` a registry id) makes the question disappear. +### Phase B at D-on (2026-08-07) — gaps 1+2 CLOSED, and the boundary found + +Gaps 1 and 2 are fixed and the wx battery is **green at D-on: 395 passed, 1 failed +(the pre-existing `modal.spec.ts:125`)** — the first clean battery with dispatch +contexts, context waits and star transfers all live. What landed: + +- **Terminal finish (gap 2).** `fiber_finish_transfer` marks a finished coroutine + Finished — never re-queued, never re-entered — and `fiber_transfer` refuses into a + Finished context, dropping the caller into libcontext's existing ghost contract. + This replaces the trampoline's `while(true)` ghost re-entry, which as transfers had + two finished coroutines marking each other Ready forever. `fiber_enterable` now + accepts the star statuses (a transfer-parked context holds a capture as valid as a + symmetric Suspended one), which also silenced the `sched-divergence-enterable` + storm. +- **Dispatch is an IDLE-REUSE SET, not one context — doc 22's "one context suffices" + was wrong.** A nested quasi-modal loop is a wait that only a LATER dispatch can + resolve, so a single dispatcher parked in that loop can never be released: + `nested_quasi_modal_pump_error` wedged exactly there. A tick now reuses any context + parked at `dispatch-idle` and allocates only when all are parked deeper. This is + NOT D2's pool (which took a fresh context per tick and grew with the tick rate); + the live count is bounded by real modal-nesting depth — 1 in steady state. +- **`abandon_transition` (a new containment).** With a second dispatcher the race's + bomb finally fired and revealed the next layer: an exception escaping a handler + propagates out THROUGH `drain()`'s fiber swap, so its post-swap bookkeeping never + runs, the registry stays "transition in flight", and every later pump refuses — + a permanently dead pump and every outstanding wait stalled. The JS error paths now + abandon the transition and POISON the half-unwound context (Finished; the dispatch + set prunes it), mirroring `wx_dispatch_abandon`. `.ci-cache-epoch` → 12. + +**THE BOUNDARY, measured on the full KiCad suite (135 passed / 5 failed vs the 139/1 +baseline).** Four canvas-tool specs — eeschema draw-wires, pcbnew draw-lines, +pcbnew move-with-m, presence-locks move — die with `index out of bounds` in +`doRewind` ← `finishContextSwitch` ← `Fibers.trampoline` ← `maybeStopUnwind`: the +BLUE SCREEN itself, reproduced by the migration's own transitional state. + +The reason the harness cannot see this, and the reason it is a boundary rather than a +bug: **real KiCad tool coroutines park IN PLACE inside their bodies** (a tool waiting +for the next event asyncify-parks on its own stack — doc 21's tool-side sites). A star +transfer turns one symmetric swap into a park-and-drain round trip, and doing that +over a stack that is already asyncify-parked mid-body rewinds state the fiber layer +cannot see. The harness's coroutines yield cleanly, so the transfer lane looks correct +there while KiCad's real tools break — the same "the harness models the shape, not the +parks" lesson as doc 19. + +**Consequence for the plan: D cannot carry KiCad until the tool-body park sites are +contexts too.** That is C+E completion (every wait yields its owning context; no +`handleSleep` park on any fiber stack), which doc 22 §5 already requires before the +flip — this measurement just proves the ordering is not negotiable and that D-on +without it is precisely the partial migration §7.5 forbids. + +**Landing state: `wxWASM_STAR_DISPATCH` back to 0**, with everything above kept — +all of it is correct-or-inert at D-off, and gaps 1+2 stay closed for the flip. The +next Phase B increment is the tool-side park inventory (doc 21 §1's KiCad rows) moved +onto contexts, after which D-on gets re-measured against the KiCad suite, not the +harness. + 1. **pthreads.** Doc 21 §2 settled that every Asyncify park is main-thread and the lib bridge's worker path is a blocking proxy. Phase A must re-check that libcontext is never driven from a worker before assuming the scheduler is main-thread-only. diff --git a/kicad b/kicad index 7a3f906..c1ba4d3 160000 --- a/kicad +++ b/kicad @@ -1 +1 @@ -Subproject commit 7a3f906c48c212bd8396a8e66fd3613e9819c8b1 +Subproject commit c1ba4d3c3b086a4e0d9fdf04a4987dedef539662 diff --git a/scripts/common/shims/asyncify-scheduler.js b/scripts/common/shims/asyncify-scheduler.js index dc701e2..27b6966 100644 --- a/scripts/common/shims/asyncify-scheduler.js +++ b/scripts/common/shims/asyncify-scheduler.js @@ -66,6 +66,11 @@ if (typeof Asyncify !== "undefined" && !globalThis.__wxSchedulerInstalled) { if (Module["_wxWasmSchedPump"]) Module["_wxWasmSchedPump"](); } catch (e) { if (Module["_wx_dispatch_abandon"]) Module["_wx_dispatch_abandon"](); + // The exception escaped a context through drain()'s fiber swap, so + // the transition it started never completed: without this every + // later pump refuses ("transition in flight") and all outstanding + // waits stall forever (doc 22 Phase B). + if (Module["_wxWasmSchedAbandon"]) Module["_wxWasmSchedAbandon"](); throw e; } }, 0); diff --git a/wxwidgets b/wxwidgets index 2e5ab59..57b781c 160000 --- a/wxwidgets +++ b/wxwidgets @@ -1 +1 @@ -Subproject commit 2e5ab5970ae919137a0da44e9c908b55f7b5b3a5 +Subproject commit 57b781c480e6396ea6696b8d63300b0284619dda