2026-07-30 14:17:48 +02:00
|
|
|
/*
|
|
|
|
|
* 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
|
|
|
|
|
|
2026-07-30 19:08:57 +02:00
|
|
|
#include <wx/wasm/private/dispatch.h>
|
|
|
|
|
|
2026-07-30 14:17:48 +02:00
|
|
|
namespace pcbjam_open
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
inline int& busyCount()
|
|
|
|
|
{
|
|
|
|
|
static int s_count = 0;
|
|
|
|
|
return s_count;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-30 19:08:57 +02:00
|
|
|
/**
|
|
|
|
|
* 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).
|
|
|
|
|
*/
|
2026-07-30 14:17:48 +02:00
|
|
|
struct BusyGuard
|
|
|
|
|
{
|
|
|
|
|
BusyGuard() { ++busyCount(); }
|
|
|
|
|
~BusyGuard() { --busyCount(); }
|
2026-07-30 19:08:57 +02:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
wxWasmDispatchGuard m_dispatch;
|
2026-07-30 14:17:48 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 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
|