docs 21+22: name the tool-body park site that blocks Phase D
Traced, not inferred: TOOL_MANAGER::RunSynchronousAction spins
`while(synchronousControl == STS_RUNNING) { wxYield(); wxMilliSleep(1); }`
(tool_manager.cpp:370-371), and on wasm wxMilliSleep -> nanosleep ->
__wasm_main_thread_yield_ms is an ASYNCIFY PARK OF THE STACK IT STANDS ON —
in a loop, inside a tool body, with a nested wxYield() dispatch running on
top of the parked stack.
The case closes on its callers: they are exactly the tools whose specs died
at D-on — edit_tool_move_fct (move-with-m, presence-locks move),
sch_drawing_tools (draw wires), the drawing/edit tools behind draw-lines.
The pcbnew spec comments already described the symptom from outside ("the
asyncified pointer-move handler") without naming the park; this is it.
So doc 21's K7 "anywhere" class has ONE caller that blocks Phase D, and it
moves first: the wait must yield the owning context instead of sleeping in
place, with the atomic's transition marking it ready. Recorded with the fix
shape, the upstream-divergence question it raises, and the gate that matters
(the KiCad suite's four canvas-tool specs — the harness has no
RunSynchronousAction and stays green either way).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBjomQfKyRa3jBdeAKpmTw
This commit is contained in:
parent
78ec52ea14
commit
f6171a7d34
2 changed files with 51 additions and 5 deletions
|
|
@ -40,7 +40,7 @@ the ngspice pair as 2); after D-1 no site has a legacy twin — one code path pe
|
|||
| K4 | `js_occExportRequest` — STEP export | `wasm/stubs/exporter_step_stub.cpp:59` | **entry** (export dialog flow) | occ-service worker promise | **D4** |
|
||||
| K5 | `js_occLoadModelRequest` — OCE model load | `wasm/stubs/oce_plugin_stub.cpp:64` | **entry or fiber** (3D cache load path) | occ-service worker promise | **D4** |
|
||||
| K6 | `js_ngspice_request` / `js_ngspice_get_vec` | `wasm/stubs/sharedspice_client.cpp:60/82` | **entry** (sim frame actions; `get_vec` has the known pre-existing asyncify crash) | ngspice-service worker promise | **D4** |
|
||||
| K7 | `__wasm_main_thread_yield_ms` — the nanosleep shadow | `wasm/shims/nanosleep_yield.c:32` | **whatever main-thread stack calls `sleep_for`** — the "anywhere" class (raytracer join loops today) | setTimeout | **D4**, plus a caller sweep: each `sleep_for` reached on the main thread is a park site of its own |
|
||||
| K7 | `__wasm_main_thread_yield_ms` — the nanosleep shadow | `wasm/shims/nanosleep_yield.c:32` | **whatever main-thread stack calls `sleep_for`** — the "anywhere" class (raytracer join loops today), and **`TOOL_MANAGER::RunSynchronousAction`'s spin loop, `tool_manager.cpp:370-371` — an in-place park INSIDE a tool body, in a loop, with a nested `wxYield()` dispatch on top of it** (identified 2026-08-07; doc 22 §10 "The tool-body park site, NAMED") | setTimeout | **D4**, plus a caller sweep: each `sleep_for` reached on the main thread is a park site of its own. The `RunSynchronousAction` caller is the one that BLOCKS Phase D — it kills four canvas-tool specs at D-on with the blue screen — and it moves first |
|
||||
| T1 | open-gate test park (`testParkMs` sleeps) | `wasm/bindings/{pcbnew,eeschema,pl_editor,kicad_editor}_embind.cpp` ×8 | **entry** (deliberate) | timeout | keep — stages the open-window collisions |
|
||||
| T2 | timer park lever | `wasm/bindings/timer_park.h:73` | mailbox-delivered timer entry (deliberate) | timeout | keep |
|
||||
| T3 | fiber park lever | `wasm/bindings/fiber_park.h:78` | **fiber mid-body (deliberate — stages exactly the doc-19 strand)** | timeout | keep; its refusal pin flips red→green at D6 |
|
||||
|
|
|
|||
|
|
@ -694,10 +694,56 @@ flip — this measurement just proves the ordering is not negotiable and that D-
|
|||
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.
|
||||
all of it is correct-or-inert at D-off, and gaps 1+2 stay closed for the flip.
|
||||
|
||||
### The tool-body park site, NAMED (2026-08-07) — `RunSynchronousAction`'s spin loop
|
||||
|
||||
Traced rather than inferred, and it is ONE site, not the open-ended set doc 21's K7 row
|
||||
feared:
|
||||
|
||||
```
|
||||
TOOL_MANAGER::RunSynchronousAction kicad/include/tool/tool_manager.h:197
|
||||
-> processEvent( event ) kicad/common/tool/tool_manager.cpp:366
|
||||
-> while( synchronousControl == STS_RUNNING ) { :368
|
||||
wxYield(); :370 nested dispatch
|
||||
wxMilliSleep( 1 ); :371 -> nanosleep
|
||||
}
|
||||
nanosleep (main thread) wasm/shims/nanosleep_yield.c:41
|
||||
-> __wasm_main_thread_yield_ms :32 EM_ASYNC_JS
|
||||
= AN ASYNCIFY PARK OF THE STACK IT STANDS ON, in a loop, inside a tool body
|
||||
```
|
||||
|
||||
**This is doc 21's K7 row, and its caller set is not "anything that sleeps" — for the
|
||||
canvas tools it is exactly this one loop.** The evidence closing the case is that
|
||||
`RunSynchronousAction`'s callers are precisely the tools whose specs died at D-on:
|
||||
`pcbnew/tools/edit_tool_move_fct.cpp` (move-with-m, presence-locks move),
|
||||
`eeschema/tools/sch_drawing_tools.cpp` (draw wires), the drawing/edit tools behind
|
||||
draw-lines. The spec comments already described the symptom from the outside —
|
||||
"KiCad's GAL updates the active tool's world-space cursor from the **asyncified**
|
||||
pointer-move handler" — without naming the park; this is that park.
|
||||
|
||||
Why it is fatal under D-on specifically: the loop parks the stack it stands on and then
|
||||
`wxYield()`s, so a nested dispatch runs tool work ON TOP of a stack that is mid-park.
|
||||
Pre-D that stack was the entry stack and the fiber layer never tried to move it; with
|
||||
dispatch on a context, a star transfer targets a context whose capture is mid-flight —
|
||||
`doRewind` → `index out of bounds`.
|
||||
|
||||
**The fix shape (next increment): the wait must yield the owning context.** Replace the
|
||||
spin with a context park — `synchronousControl` transitioning out of `STS_RUNNING`
|
||||
marks the parked context ready — so the frame waits by yielding rather than by
|
||||
sleeping-in-place. Notes for whoever takes it:
|
||||
|
||||
- This is a `tool_manager.cpp` change, i.e. the first Phase B edit to KiCad proper, so
|
||||
§5's divergence question comes due (accept it, or hide the park behind a wx-side
|
||||
helper the wasm port supplies and KiCad calls unconditionally — the latter keeps the
|
||||
fork close to upstream and is likely possible, since the loop only needs "wait until
|
||||
this atomic changes").
|
||||
- `wxYield()` inside the loop must keep working: under the star it becomes "let the
|
||||
scheduler run other contexts", which is what a context park already does — so the
|
||||
yield call can likely go away with the sleep rather than be ported.
|
||||
- Gate on the **KiCad suite** (the four canvas-tool specs are the red-to-green pins),
|
||||
never the harness battery — the harness has no `RunSynchronousAction` and will stay
|
||||
green either way. That is this session's most transferable lesson.
|
||||
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue