- kicadOpenFile now holds wxWasmDispatchGuard (open_gate.h). It enters through embind, so the interlock read "nothing parked" for the whole load and wx timers dispatched into the half-built board — the residual prod "index out of bounds" that survived the settle gate. - new wasm/bindings/gerbview_embind.cpp (the bundle had no embind surface at all): kicadOpenFile / kicadOpenFiles / kicadOpenFileBusy. Clicking one gerber opens the whole fabrication set in its folder, since a lone layer is not a useful view. - cross-app presence rejoins in the boot fan-out (network-only; the wasm-bound half still waits for the open to settle) — it had been pushed behind the board load. - tests: gerber-set selection units + a gerbview multi-file open e2e. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0137pGo8W7asomGUTRMB7RzM
78 lines
3 KiB
C++
78 lines
3 KiB
C++
/*
|
|
* Truthful "kicadOpenFile in flight" signal for the web shell.
|
|
*
|
|
* kicadOpenFile runs OpenProjectFiles under Asyncify: the embind call unwinds
|
|
* back to JS long before the load finishes, and the chain stays parked (and
|
|
* resumes, and parks again) across the whole multi-second load. Any bare
|
|
* embind entry that walks the model while that chain is parked mid-mutation
|
|
* (collab snapshot, presence bind) can virtual-dispatch through a half-built
|
|
* item and trap ("indirect call signature mismatch" — same class as the wx
|
|
* dispatch interlock and the drift-trio #10b fiber-busy probe, but through a
|
|
* JS entry neither of those covers).
|
|
*
|
|
* The guard is RAII on the open's C++ stack frame: an Asyncify unwind does not
|
|
* run destructors and a rewind resumes past the constructor, so the count is
|
|
* held for the park's entire lifetime and drops exactly when OpenProjectFiles
|
|
* truly returns (the same primitive as wxWasmDispatchGuard). A trap escaping
|
|
* the open leaves the count stuck — the JS poll times out and degrades.
|
|
*/
|
|
#pragma once
|
|
|
|
#include <wx/wasm/private/dispatch.h>
|
|
|
|
namespace pcbjam_open
|
|
{
|
|
|
|
inline int& busyCount()
|
|
{
|
|
static int s_count = 0;
|
|
return s_count;
|
|
}
|
|
|
|
/**
|
|
* Held for the whole open. Two counters, same Asyncify-RAII trick:
|
|
*
|
|
* - `busyCount` is OURS: it answers kicadOpenFileBusy() for the web shell and
|
|
* gates the collab entries (JS → embind reentry).
|
|
* - `wxWasmDispatchGuard` enrolls the open in the WX DISPATCH INTERLOCK. This
|
|
* matters because `kicadOpenFile` enters through embind, not through a wx
|
|
* dispatch entry point, so without it `wxWasmDispatchParked()` reads FALSE
|
|
* for the entire load: every park (progress pump, thread-pool futex wait,
|
|
* lib bridge) lets the pump dispatch a QUEUED WX TIMER into the half-built
|
|
* board — src/wasm/timer.cpp fires it because nothing looks parked — and
|
|
* the handler walks half-mutated widget/board state ("index out of bounds",
|
|
* the same signature as the symbol-chooser crash the interlock was built
|
|
* for). Holding the guard makes those timers defer (retry 17 ms later)
|
|
* until the load truly completes. Paints keep running; the progress
|
|
* dialog's own pump is the designed exception (it zeroes the count).
|
|
*/
|
|
struct BusyGuard
|
|
{
|
|
BusyGuard() { ++busyCount(); }
|
|
~BusyGuard() { --busyCount(); }
|
|
|
|
private:
|
|
wxWasmDispatchGuard m_dispatch;
|
|
};
|
|
|
|
/** JS-pollable: is a kicadOpenFile chain still in flight (possibly parked)? */
|
|
inline bool busy()
|
|
{
|
|
return busyCount() > 0;
|
|
}
|
|
|
|
/**
|
|
* Test-only deterministic park (tests/kicad/collab-load-fuzz.spec.ts): with a
|
|
* nonzero value, kicadOpenFile Asyncify-parks for this many ms on entry and
|
|
* again after OpenProjectFiles returns — busy guard held, model fully loaded.
|
|
* Natural in-load parks (thread-pool futex waits) are scheduler-dependent and
|
|
* never happen on a fast idle machine, so the guard would be untestable in CI
|
|
* without this window. 0 (the default) is a no-op in production.
|
|
*/
|
|
inline int& testParkMs()
|
|
{
|
|
static int s_ms = 0;
|
|
return s_ms;
|
|
}
|
|
|
|
} // namespace pcbjam_open
|