design-b D2: attempted, reverted, resequenced behind D3
Bumps wxwidgets d220aae5 (D2a: sched_context.h moved into wx's port, header-only so evtloop.cpp can see it) and 5ee60a81 (D1 fix: 16-align context stacks — EM_ASM's arg buffer lives on the running stack and the glue asserts buf % 16 == 0, so misaligned contexts trapped in readEmAsmArgs; std::vector<char> only gives malloc's 8-byte alignment). The dispatch switch itself is NOT landed. Running the tick's ProcessEvents on a context took the battery from 363 green to 388/7, six of them the coroutine-nested harness wedging at fiber_create_run_destroy_inside_modal via aliased-wake-live -> fiber-resume-refused — doc 19's mechanism. A quasi-modal opened from a tick handler suspends the dispatch context INSIDE the still-in-place wait, putting one more Asyncify layer under every libcontext fiber. Pooling contexts (8 burned in 30 ms) and falling back to entry-stack dispatch both failed to avoid it, because the layer exists as soon as the context is suspended. That is doc 20's own risk 2 arriving on schedule, so the plan is corrected rather than the symptom patched: D3 (waits become context yields) must come first, after which the dispatch context is released at its tick boundary instead of suspended and the failure class is structurally absent. Verified the revert: the nested battery is green again at this baseline (5 passed / 1 failed, the 1 being the environment-sensitive modal:125 that also fails without any of this). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TEHGiiXMShNXbBr7gSJ7iz
This commit is contained in:
parent
41f3f35919
commit
5a3dd44b23
4 changed files with 54 additions and 645 deletions
|
|
@ -1,6 +1,7 @@
|
|||
# 20 — Design B core: parkable activities as scheduler contexts
|
||||
|
||||
> **Status: IN PROGRESS — D-1, D0, D1 DONE (2026-08-05, work log §10).** The remaining core of
|
||||
> **Status: IN PROGRESS — D-1, D0, D1 DONE; D2 attempted and resequenced behind D3
|
||||
> (work log §10). Phase order is now D3 → D2 → D4 → D5? → D6.** The remaining core of
|
||||
> Design B ([`06`](06-design-b-fiber-first-runtime.md) B1+B2), scoped against what S0–S6
|
||||
> actually built ([`17`](17-mailbox-scheduler-plan.md)) and motivated by the
|
||||
> stranded-fiber hang ([`19`](19-quasimodal-fiber-strand.md)). Supersedes doc 12's
|
||||
|
|
@ -117,13 +118,25 @@ Two consequences worth stating plainly:
|
|||
libcontext: create context, swap-out to the scheduler, resume by swap-in, registry as
|
||||
truth, one transition in flight. Exercised only by a dedicated test app — **no
|
||||
production path switched yet**. Gate: new unit/app tests + full battery unchanged.
|
||||
- **D2 · Dispatch contexts (1–2 wk).** The tick resumes a dispatch context instead of
|
||||
running handlers on the entry stack; scheduler policy enforces one runnable dispatch
|
||||
context. The interlock stays, demoted to a **tripwire** asserting it never disagrees with
|
||||
the scheduler. Gate: full net + zero tripwire divergence across the fuzz suites.
|
||||
- **D3 · Waits on contexts (1 wk).** `wxWasmYieldUntil` becomes a context yield. **The
|
||||
doc-19 red spec goes green here** — modal/nested/popup no longer park a tool fiber's body.
|
||||
- **D3 · Waits on contexts (1 wk) — NOW BEFORE D2, see §10's D2 attempt.**
|
||||
`wxWasmYieldUntil` becomes a context yield. **The doc-19 red spec goes green here** —
|
||||
modal/nested/popup no longer park a tool fiber's body.
|
||||
Gate: red→green + battery + kicad suite.
|
||||
- **D2 · Dispatch contexts (1–2 wk) — resequenced AFTER D3.** The tick resumes a dispatch
|
||||
context instead of running handlers on the entry stack; scheduler policy enforces one
|
||||
runnable dispatch context. The interlock stays, demoted to a **tripwire** asserting it
|
||||
never disagrees with the scheduler. Gate: full net + zero tripwire divergence across the
|
||||
fuzz suites.
|
||||
|
||||
**Why the swap (measured 2026-08-06):** dispatch-on-a-context is only safe once waits
|
||||
yield instead of parking in place. While `wxWasmYieldUntil` is still an in-place park, a
|
||||
quasi-modal opened from a tick handler suspends the dispatch context *inside* the wait,
|
||||
which adds an Asyncify layer under every libcontext fiber — and the coroutine-nested
|
||||
battery dies on it (`aliased-wake-live` → `fiber-resume-refused` in
|
||||
`fiber_create_run_destroy_inside_modal`). That is risk 2 ("partial migration is worse
|
||||
than none") arriving on schedule. After D3 the wait releases the context at its tick
|
||||
boundary, so the dispatch context is never suspended in place and the extra layer never
|
||||
exists.
|
||||
- **D4 · Bridges on contexts (1–2 wk).** One helper (`wasm_await_promise`-style) routes
|
||||
lib/3D/clipboard/font/nanosleep/stub parks through the same yield. After this **no
|
||||
`handleSleep` park happens inside a fiber at all** — the quarantine guard is demoted to a
|
||||
|
|
@ -274,6 +287,36 @@ the apparatus works and what its units are; the sizing DECISION needs deep-park
|
|||
from real bridges, so it belongs to D3/D4, not here. A >75% buffer use beacons
|
||||
`BUFFER-PRESSURE`, because that overflow corrupts silently rather than crashing.
|
||||
|
||||
Next: **D2** — the tick resumes a dispatch context instead of running handlers on the entry
|
||||
stack; scheduler policy enforces one runnable dispatch context; the interlock stays but is
|
||||
demoted to a tripwire asserting it never disagrees with the scheduler.
|
||||
### D2 — attempted, REVERTED, and resequenced behind D3 (2026-08-06)
|
||||
|
||||
Landed from the attempt: **D2a** (wx `d220aae5`) moved the layer into wx's port as a
|
||||
header (`wx/wasm/private/sched_context.h`) so evtloop.cpp can see it — adding a `.cpp` to
|
||||
wx means bakefile regeneration. And a real **D1 bug fix** (wx `5ee60a81`): context stacks
|
||||
must be 16-byte aligned or every `EM_ASM` on a context traps in `readEmAsmArgs`
|
||||
(`assert(buf % 16 == 0)` — the arg buffer lives on the running stack).
|
||||
`std::vector<char>::data()` only has malloc's 8-byte alignment, so contexts were
|
||||
misaligned about half the time. D1's harness passed by allocation luck; wx's dispatch
|
||||
found it instantly with 107 failures.
|
||||
|
||||
**Reverted: the dispatch switch itself.** With the tick's `ProcessEvents` on a context the
|
||||
battery went 363 → 388/7. Six of the seven were the coroutine-nested battery wedging at
|
||||
`fiber_create_run_destroy_inside_modal`, preceded by `aliased-wake-live` and killed by
|
||||
`fiber-resume-refused` — doc 19's exact mechanism. Cause: a quasi-modal opened from a tick
|
||||
handler suspends the dispatch context *inside* the still-in-place wait, so every
|
||||
libcontext fiber below now sits under one more Asyncify layer.
|
||||
|
||||
Two designs were tried and both are dead ends while waits park in place:
|
||||
1. *Grow a pool* (one context per suspended one) — the nested harness burned all 8 in
|
||||
30 ms, because a context suspended in a modal is consumed for that modal's lifetime.
|
||||
Nesting depth would size the pool: a leak wearing a cap.
|
||||
2. *Fall back to entry-stack dispatch while suspended* — restores pre-D2 semantics for the
|
||||
modal case and did fix the races `nested_quasi_modal_pump_error`, but the extra layer
|
||||
is already there by the time the fallback is reached, so the nested battery still died.
|
||||
|
||||
**Conclusion (plan correction):** D2 is not landable before D3. Sequence is now
|
||||
D3 → D2 → D4 → D5? → D6. Do not retry dispatch contexts until `wxWasmYieldUntil` yields
|
||||
its context; then the dispatch context is released at its tick boundary rather than
|
||||
suspended, and this whole failure class is structurally absent.
|
||||
|
||||
Next: **D3** — `wxWasmYieldUntil` becomes a context yield; the doc-19 red spec
|
||||
(`tests/kicad/quasimodal-strand.spec.ts`) goes green and its `test.fail()` comes off.
|
||||
|
|
|
|||
Loading…
Reference in a new issue