docs 22: Phase E first attempt (K1 as a context wait) - REVERTED, with the post-mortem
Converting the symbol-library bridge from one EM_ASYNC_JS into beginWait -> start -> wxWasmYieldUntil -> take_result made the app hang after every library request: the KiCad suite went from 7 minutes to 1.2 HOURS, 111 passed with the rest timing out, and the log goes silent right after a lib request - a parked context nobody resumes. Reverted; the tree keeps the working EM_ASYNC_JS and no code from the attempt. Recorded for the next attempt: - The PRECONDITION it found (four lines, quoted in the doc, deliberately NOT landed): wxWasmYieldUntil must not park a context whose wait is already resolved, because resolveWait deletes the entry. The in-place form had no such window; every bridge in this pattern re-opens it. It was not the hang, but it has only ever existed in the broken build, so it lands WITH the conversion it protects, not before. - Three hypotheses in evidence order, cheapest first: the resolve never reaching the registry (this is the first KiCad-side caller of the wait registry - check Module["_wxWasmSchedResolveContextWait"] exists in the KiCad link); the lib request running inside the chooser's modal where the dispatch context is ALREADY parked on a nested wait (a context holds one park); or the Asyncify instrumentation closure shifting when the EM_ASYNC_JS left that translation unit. - Process: gate a bridge conversion on a SINGLE spec, never the full suite. This cost a 1.2-hour run to learn one bit. Tree is unchanged from the last verified landing state (STAR_DISPATCH=0, kicad 139 passed / 1 pre-existing occ-probe) - docs only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBjomQfKyRa3jBdeAKpmTw
This commit is contained in:
parent
ab6e44e57f
commit
54e6560037
1 changed files with 64 additions and 0 deletions
|
|
@ -848,6 +848,70 @@ everything kept. **Next: Phase E** — the K1–K7 bridges are now the only in-p
|
|||
left under a context, and they are what the `current() != 0` fallback above still
|
||||
tolerates.
|
||||
|
||||
### Phase E, first attempt: K1 as a context wait — REVERTED, and what it cost
|
||||
|
||||
The bridge pattern doc 22 §5 calls for, tried on K1 (`sch_io_pcbjam_lib.cpp`, the
|
||||
symbol-library request — chosen first because doc 21 makes it the doc-19 exposure: a
|
||||
chooser's lazy load parks the TOOL COROUTINE that owns the chooser):
|
||||
|
||||
```
|
||||
before: char* r = pcbjam_libs_request_js(op, lib, arg, kind); // EM_ASYNC_JS, parks in place
|
||||
after: int t = wxWasmBeginWait("lib");
|
||||
pcbjam_libs_request_start(t, op, lib, arg, kind); // EM_JS, resolves the wait
|
||||
wxWasmYieldUntil(t); // context park, or in-place fallback
|
||||
char* r = pcbjam_libs_take_result(t);
|
||||
```
|
||||
|
||||
**Result: the KiCad suite went from 7 minutes to 1.2 HOURS — 111 passed, the rest
|
||||
timing out, and the app going SILENT right after a library request.** A parked context
|
||||
that nobody resumes. Reverted; the tree keeps the working `EM_ASYNC_JS`.
|
||||
|
||||
**A PRECONDITION the attempt discovered — re-add it WITH the next conversion, it is
|
||||
not in the tree.** `wxWasmYieldUntil` must not park a context whose wait is already
|
||||
resolved: `resolveWait` DELETES the entry, so a bridge whose promise settles before the
|
||||
caller reaches the park leaves that context waiting for a wake nobody sends. The
|
||||
in-place form had no such window (the same expression created and awaited the promise);
|
||||
every bridge converted to this pattern re-opens it. The guard is four lines —
|
||||
|
||||
```cpp
|
||||
EM_JS(int, wxWasmWaitPendingJs, (int token), { // resolveWait deletes the entry
|
||||
return globalThis.__wxScheduler.waits.has(token) ? 1 : 0;
|
||||
});
|
||||
...
|
||||
if (self && pcbjam_sched::can_yield_here())
|
||||
{
|
||||
if (!wxWasmWaitPendingJs(token)) // already resolved: do NOT park
|
||||
return 0;
|
||||
```
|
||||
|
||||
— and it was NOT the hang (the hang reproduced with it in place). It is deliberately
|
||||
NOT landed: it sits in the wait path of every modal and nested loop, and the only build
|
||||
that ever contained it was the broken one, so it has never been measured at D-off. It
|
||||
belongs in the same commit as the bridge conversion it protects, gated together.
|
||||
|
||||
**Where the next attempt should start — hypotheses, in the order the evidence supports
|
||||
them.** Not yet distinguished; do this with a focused repro (open a schematic with the
|
||||
chooser, one lib request), not the full suite:
|
||||
|
||||
1. **The resolve never reaches the registry.** `resolveWait` routes a context-parked
|
||||
token to `Module["_wxWasmSchedResolveContextWait"]` + `_armSchedPump`; if either
|
||||
export is missing from the KiCad link (they are `EMSCRIPTEN_KEEPALIVE` in wx, but
|
||||
this is the first KiCad-side caller of the wait registry) the mark-ready silently
|
||||
never happens. Check `Module["_wxWasmSchedResolveContextWait"]` in the console
|
||||
first — it is one line and would explain the symptom exactly.
|
||||
2. **The lib path runs where `can_yield_here()` is true but the resumer cannot reach
|
||||
it** — e.g. inside the chooser's modal, where the dispatch context is already
|
||||
parked at a "nested" wait and the lib wait parks it a second time (a context can
|
||||
hold only ONE park; the second yield would strand the first).
|
||||
3. **Asyncify instrumentation closure.** Removing the `EM_ASYNC_JS` from this
|
||||
translation unit changes which functions binaryen instruments; the KiCad lib path
|
||||
now reaches an unwind only through `wxWasmYieldUntilJs`. If the closure no longer
|
||||
covers a frame in that path, the fallback park corrupts instead of suspending.
|
||||
|
||||
**Process note for the next attempt:** gate a bridge conversion on a SINGLE spec first
|
||||
(`eeschema.spec.ts` or a chooser spec), not `npm run test:kicad` — this attempt cost a
|
||||
1.2-hour suite run to learn one bit. The full suite is the confirmation, not the probe.
|
||||
|
||||
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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue