diff --git a/.ci-cache-epoch b/.ci-cache-epoch index 60d3b2f..b6a7d89 100644 --- a/.ci-cache-epoch +++ b/.ci-cache-epoch @@ -1 +1 @@ -15 +16 diff --git a/docs/features/async/22-absorbing-libcontext.md b/docs/features/async/22-absorbing-libcontext.md index d349ec9..fa592a1 100644 --- a/docs/features/async/22-absorbing-libcontext.md +++ b/docs/features/async/22-absorbing-libcontext.md @@ -1241,6 +1241,32 @@ upstream-divergence decision) remove the population. Until then the ring is the correct containment: bounded at 32, over-capacity silent at the flip, and the eager path stands ready for any flow that does complete-and-release. +### The awaited-ccall entry class RETIRED for the open path (2026-08-09) + +`kicadOpenFile` — the last production member of the overlapped-wake class the +D-on beacon sweep named — no longer parks the main stack. The body is +unchanged; `kicadOpenFileStart` runs it on a DISPATCH CONTEXT (every wait +inside the load becomes a context park through the registry) and resolves an +"open" wait token on completion; the shim conditionally rewraps +`Module.kicadOpenFile` to return the token's plain JS promise, so the shell's +`await` and the busy gate are byte-compatible (older per-app binaries without +the starter keep the legacy suspending export). The F0 early-resolve retention +covers the fast-error path by construction. + +**Gates: collab-load-fuzz + mailbox-ordering (the staged parked-open levers) +green — with ZERO `overlapped-wake`/`hot-main-swap-out` beacons where they +always fired; suite 139/1; battery 395/1; repro oracle 3/3 warm loads PASS +with `rootHotTotal=0`, `fiberStackParks=0` — and `fcsTotal` COLLAPSED from +~1800–5000 per load to 97–235.** The load no longer round-trips fiber swaps +through main-stack in-place parks; that order-of-magnitude drop is the +measured shape of "the main stack is the scheduler and nothing else". + +Remaining members of the class: the standalone per-app binaries (eeschema.js +etc., rebuilt rarely — conversion rides their next rebuild via the same +starter pattern) and the wx TEST HARNESS pages' direct `ccall` buttons +(harness-only; the interlock and shim capture/restore stay until those are +routed or retired). + ### Prod-provider smoke (2026-08-08) — done, with one pre-existing red bisected Against the live web stack (playwright-web config, reference backend :3060): diff --git a/scripts/common/shims/asyncify-scheduler.js b/scripts/common/shims/asyncify-scheduler.js index 366f191..37c3ff3 100644 --- a/scripts/common/shims/asyncify-scheduler.js +++ b/scripts/common/shims/asyncify-scheduler.js @@ -143,6 +143,23 @@ if (typeof Asyncify !== "undefined" && !globalThis.__wxSchedulerInstalled) { if (this.mutatorsWrapped > 0) console.log("[wx-scheduler] embind lane: wrapped " + this.mutatorsWrapped + " mutator(s)"); }, + // Phase F (doc 22 §10, the awaited-ccall entry class): when the binary + // carries kicadOpenFileStart, the open body runs on a DISPATCH CONTEXT + // and the await surface becomes a plain JS promise over the wait token — + // the main stack never parks in place during a load. Conditional: older + // binaries without the starter keep the legacy suspending export. + _wrapOpenFile: function () { + var self = this; + var start = Module["kicadOpenFileStart"]; + if (typeof start !== "function" || typeof Module["kicadOpenFile"] !== "function") return; + Module["kicadOpenFile"] = function (path) { + var token = start(path); + // waitPromise consumes early-resolved entries (the fast-error path), + // so a job that finished before this await still resolves correctly. + return self.waitPromise(token).then(function (r) { return !!r; }); + }; + console.log("[wx-scheduler] open lane: kicadOpenFile routed through the dispatch context"); + }, _armMutatorPump: function () { if (this._mutatorPumpArmed) return; this._mutatorPumpArmed = true; @@ -763,11 +780,13 @@ if (typeof Asyncify !== "undefined" && !globalThis.__wxSchedulerInstalled) { if (typeof Module !== "undefined") { if (Module["calledRun"]) { AsyncifyScheduler._wrapMutators(); + AsyncifyScheduler._wrapOpenFile(); } else { var __wxSchedPrevInit = Module["onRuntimeInitialized"]; Module["onRuntimeInitialized"] = function () { if (typeof __wxSchedPrevInit === "function") __wxSchedPrevInit(); AsyncifyScheduler._wrapMutators(); + AsyncifyScheduler._wrapOpenFile(); }; } } diff --git a/wasm/bindings/kicad_editor_embind.cpp b/wasm/bindings/kicad_editor_embind.cpp index bf79118..6bc1883 100644 --- a/wasm/bindings/kicad_editor_embind.cpp +++ b/wasm/bindings/kicad_editor_embind.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -164,6 +165,42 @@ static bool kicadOpenFile( std::string path ) return ok; } +// Phase F (docs/features/async/22 §10, the awaited-ccall entry class): the +// open body above, driven from a DISPATCH CONTEXT instead of the main stack. +// Run there, every wait inside the load parks the context through the +// registry — the main stack never parks in place, which is the last +// production member of the overlapped-wake class the D-on beacon sweep named. +// The shim wraps Module.kicadOpenFile over this starter when it exists: the +// wrapper returns the wait token's promise, so the shell's `await` semantics +// (and the busy gate) are unchanged. Early failures resolve before the +// wrapper awaits — covered by the wait registry's early-resolve retention. +extern "C" void wxWasmRunOnDispatchContext( void ( *fn )( void* ), void* arg ); +extern "C" int wxWasmBeginWait( const char* aKind ); +extern "C" void wxWasmResolveWait( int aToken, int aResult ); + +namespace +{ +struct OPEN_JOB +{ + std::string path; + int token; +}; + +void kicadOpenFileJob( void* aArg ) +{ + std::unique_ptr job( static_cast( aArg ) ); + const bool ok = kicadOpenFile( job->path ); + wxWasmResolveWait( job->token, ok ? 1 : 0 ); +} +} // namespace + +static int kicadOpenFileStart( std::string path ) +{ + const int token = wxWasmBeginWait( "open" ); + wxWasmRunOnDispatchContext( &kicadOpenFileJob, new OPEN_JOB{ std::move( path ), token } ); + return token; +} + // JS-pollable open-in-flight probe (open_gate.h): the web shell defers the // collab/presence attach until the open chain has truly completed. static bool kicadOpenFileBusy() @@ -573,6 +610,7 @@ EMSCRIPTEN_BINDINGS(kicad_editor) { function("kicadCollabFiberBusy", &kicadCollabFiberBusyProbe); // Programmatic file open (preferred over UI automation from the web app). function("kicadOpenFile", &kicadOpenFile); + function("kicadOpenFileStart", &kicadOpenFileStart); function("kicadOpenFileBusy", &kicadOpenFileBusy); function("kicadTestSetOpenPark", &kicadTestSetOpenPark); function("kicadTestArmTimerPark", &kicadTestArmTimerPark);