jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose
The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
This commit is contained in:
parent
3ee174e9b4
commit
9c475a804e
120 changed files with 1522 additions and 2974 deletions
|
|
@ -3,7 +3,7 @@
|
|||
// third copy: symlink standalone/public/wasm -> the synced artifact dir
|
||||
// (tests/apps/kicad, populated by tests/scripts/setup-kicad-wasm.sh). Vite then
|
||||
// serves them at /wasm from the app's own origin — required because KiCad WASM
|
||||
// (Asyncify build, COEP/cross-origin-isolated document) refuses to load its
|
||||
// (a COEP/cross-origin-isolated document) refuses to load its
|
||||
// glue/wasm from a different origin.
|
||||
//
|
||||
// No bytes are duplicated; the link points straight at the synced dir.
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@ function installQuitHook(
|
|||
// there instead of leaving the editor.
|
||||
//
|
||||
// Defer the navigation out of the wasm callback: this fires from inside the
|
||||
// frame's C++ destructor (via EM_ASM under Asyncify), and the teardown keeps
|
||||
// frame's C++ destructor (via EM_ASM), and the teardown keeps
|
||||
// running after we return. A cross-document location.assign() started here is
|
||||
// aborted by that continuing teardown — so hand it to a fresh task once the
|
||||
// wasm stack has unwound.
|
||||
|
|
@ -1251,18 +1251,15 @@ export function WasmTool({
|
|||
append(`[fatal] ${kind}: ${msg}`);
|
||||
append(dumpTrace());
|
||||
// The scheduler flight recorder: event ring + wait/activation state at
|
||||
// death — the targeting data for suspension-machinery traps. Canonical
|
||||
// name is __wxWaitDump (jspi-scheduler); __wxAsyncifyDump is the legacy
|
||||
// shim's name, kept as a fallback one release. The jspi dump is an
|
||||
// object, the legacy one a string — normalize.
|
||||
// death — the targeting data for suspension-machinery traps
|
||||
// (__wxWaitDump, jspi-scheduler.js).
|
||||
const dumper = (
|
||||
window as Window & {
|
||||
__wxWaitDump?: () => unknown;
|
||||
__wxAsyncifyDump?: () => unknown;
|
||||
}
|
||||
);
|
||||
const rec = (dumper.__wxWaitDump ?? dumper.__wxAsyncifyDump)?.();
|
||||
if (rec) append(typeof rec === "string" ? rec : JSON.stringify(rec));
|
||||
const rec = dumper.__wxWaitDump?.();
|
||||
if (rec) append(JSON.stringify(rec));
|
||||
setFatal(msg);
|
||||
setShowLog(true);
|
||||
// Arm the React-independent floor too: it stays invisible while our
|
||||
|
|
@ -1279,7 +1276,7 @@ export function WasmTool({
|
|||
if (!isTerminalError(e.reason, msg)) return;
|
||||
promote("unhandled rejection", msg);
|
||||
};
|
||||
// With PROXY_TO_PTHREAD, main()/wx/timers — and therefore every asyncify
|
||||
// With PROXY_TO_PTHREAD, main()/wx/timers — and therefore every wasm
|
||||
// trap in this family — throw INSIDE a pthread worker. A worker's uncaught
|
||||
// error fires an ErrorEvent on the Worker OBJECT, never on `window`, so the
|
||||
// two listeners below can't see the very traps this overlay exists for
|
||||
|
|
@ -1802,7 +1799,7 @@ export function WasmTool({
|
|||
// Everything below drives BARE embind entries that walk the loaded
|
||||
// model (collab snapshot/adopt, presence bind, drift). Deferred until
|
||||
// the open chain settled (openResult) — calling them while the
|
||||
// kicadOpenFile Asyncify chain is still parked mid-load walks a
|
||||
// kicadOpenFile activation is still parked mid-load walks a
|
||||
// half-built model and traps ("indirect call signature mismatch").
|
||||
const attachCollabAndPresence = async () => {
|
||||
// Drift detection: while a sheet is collaboratively edited, periodically (every N
|
||||
|
|
@ -1886,7 +1883,7 @@ export function WasmTool({
|
|||
// STAGGERED out of the settle window (2026-08-02, the ladder
|
||||
// result): warm sibling-heavy projects crash at exactly this moment
|
||||
// (V1/V4 fail 2/2 warm, V2/V3 without siblings never do), and the
|
||||
// asyncify flight recorder places the fatal interleave inside the
|
||||
// scheduler flight recorder places the fatal interleave inside the
|
||||
// settle-time wake windows. The restage's room connects + restage
|
||||
// fetches were the only sibling-specific traffic contending with
|
||||
// those windows. Nothing here is needed for first paint — the boot
|
||||
|
|
|
|||
|
|
@ -91,9 +91,10 @@ export function initErrorReporting(): void {
|
|||
// requestAnimationFrame / addEventListener in try/catch to attach
|
||||
// better stack traces. KiCad-on-Emscripten drives its main loop
|
||||
// through exactly those (emscripten_async_call → setTimeout, and the
|
||||
// GAL refresh timer re-arms in a tight loop), so wrapping them adds a
|
||||
// closure per tick and changes stack identity in the middle of an
|
||||
// Asyncify-transformed call graph. Not worth it for prettier traces.
|
||||
// GAL refresh timer re-arms in a tight loop), so wrapping them adds
|
||||
// a closure per tick on the hottest paths for marginal gain. (Under
|
||||
// JSPI stack traces are not mangled, so source-map symbolication may
|
||||
// now be worth revisiting — behavior kept as is.)
|
||||
i.name !== "BrowserApiErrors" && i.name !== "Breadcrumbs",
|
||||
),
|
||||
// Keep breadcrumbs — clicks, navigation and fetches are exactly the "what
|
||||
|
|
|
|||
|
|
@ -51,15 +51,15 @@ export function hasWritableLib(lists: Iterable<LibInfo[]>): boolean {
|
|||
* `var Module` and publishes `FS`/`wxElementRegistry` onto `window` — exactly the
|
||||
* surface the iframe approach used, only now in the top-level window.
|
||||
*
|
||||
* Two browser facts make running in the main document (rather than at /wasm/...)
|
||||
* work without touching the build:
|
||||
* - `locateFile` is overridden to resolve `<base>/<file>`, so the .wasm and the
|
||||
* pthread worker script are fetched from the asset dir regardless of the
|
||||
* SPA route the user is on.
|
||||
* - `mainScriptUrlOrBlob` pins the pthread worker to `<base>/<tool>.js`. For a
|
||||
* same-origin base that's the URL directly; for a cross-origin CDN base it's
|
||||
* a same-origin blob shim that importScripts the glue (see
|
||||
* `pthreadWorkerScript` — `new Worker(<cross-origin URL>)` is illegal).
|
||||
* Running in the main document (rather than at /wasm/...) works without touching
|
||||
* the build because `locateFile` is overridden to resolve `<base>/<file>`, so
|
||||
* the .wasm and other assets are fetched from the asset dir regardless of the
|
||||
* SPA route the user is on. Pthread child workers spawn from `_scriptName`
|
||||
* (the glue's own absolute URL, captured when `<tool>.js` executes), so a
|
||||
* same-origin asset base just works. KNOWN GAP: a cross-origin (CDN) base
|
||||
* cannot spawn the pthread workers — `new Worker(<cross-origin URL>)` is a
|
||||
* SecurityError, and emscripten 6 ignores `mainScriptUrlOrBlob` — see
|
||||
* docs/features/async/23-jspi-runtime.md.
|
||||
*
|
||||
* Single-instance: the build owns process-global state (one `Module`, one wasm
|
||||
* memory) so only ONE tool can run per page load. A second boot — switching
|
||||
|
|
@ -154,17 +154,18 @@ function loadScript(src: string): Promise<void> {
|
|||
}
|
||||
|
||||
/**
|
||||
* The pthread worker "script" passed as `Module.mainScriptUrlOrBlob`. KiCad's
|
||||
* pthreads spawn CLASSIC workers via `new Worker(...)` (see `<tool>.js`
|
||||
* `allocateUnusedWorker`):
|
||||
* - SAME-ORIGIN base → the plain URL string (the proven local/dev path).
|
||||
* - CROSS-ORIGIN base (the CDN) → a SAME-ORIGIN `blob:` worker that
|
||||
* `importScripts()` the cross-origin glue. `new Worker(<cross-origin URL>)`
|
||||
* is a SecurityError, but a `blob:` URL inherits the page origin (legal),
|
||||
* and a classic worker's `importScripts` MAY load a cross-origin script when
|
||||
* the CDN sends `Cross-Origin-Resource-Policy: cross-origin` (needed because
|
||||
* the page is COEP `require-corp`). The `.wasm`/`images.tar.gz` fetches just
|
||||
* need `ACAO` + `CORP` on the CDN. See docs/features/demo-deploy/0001-*.
|
||||
* DORMANT — nothing consumes this today. It built the value once passed as
|
||||
* `Module.mainScriptUrlOrBlob`, which emscripten 6 ignores: pthread child
|
||||
* workers spawn from `_scriptName` instead, which is same-origin-only. Kept
|
||||
* because the cross-origin (CDN) pthread fix — a KNOWN GAP, see
|
||||
* docs/features/async/23-jspi-runtime.md — will need exactly this plumbing:
|
||||
* a SAME-ORIGIN `blob:` worker that `importScripts()` the cross-origin glue.
|
||||
* `new Worker(<cross-origin URL>)` is a SecurityError, but a `blob:` URL
|
||||
* inherits the page origin (legal), and a classic worker's `importScripts` MAY
|
||||
* load a cross-origin script when the CDN sends
|
||||
* `Cross-Origin-Resource-Policy: cross-origin` (needed because the page is
|
||||
* COEP `require-corp`). The `.wasm`/`images.tar.gz` fetches just need
|
||||
* `ACAO` + `CORP` on the CDN. See docs/features/demo-deploy/0001-*.
|
||||
*/
|
||||
/**
|
||||
* C++ diagnostics that must reach the BROWSER console, not just the in-page log.
|
||||
|
|
@ -485,9 +486,10 @@ async function doBoot(opts: BootOptions): Promise<void> {
|
|||
}
|
||||
};
|
||||
|
||||
// preRun (seeding tools only): suppress the first-run setup wizard, whose modal
|
||||
// loop crashes Asyncify in our ephemeral MEMFS. Make all settings providers
|
||||
// report NeedsUserInput()==false — the wizard's "use defaults" path.
|
||||
// preRun (seeding tools only): suppress the first-run setup wizard — its modal
|
||||
// loop is unsupported on our ephemeral MEMFS (nothing it writes would survive
|
||||
// a reload, and the wizard would re-run every boot). Make all settings
|
||||
// providers report NeedsUserInput()==false — the wizard's "use defaults" path.
|
||||
const seedKicadConfig = () => {
|
||||
const FS = moduleFS();
|
||||
FS.mkdirTree(KICAD_CONFIG_DIR);
|
||||
|
|
@ -618,9 +620,6 @@ async function doBoot(opts: BootOptions): Promise<void> {
|
|||
},
|
||||
// Resolve wasm + pthread worker against the asset base, not the SPA route.
|
||||
locateFile: (path: string) => `${base}/${path}`,
|
||||
// Pin the pthread worker script. Same-origin → direct URL; cross-origin CDN
|
||||
// → a same-origin blob shim that importScripts the glue (see helper above).
|
||||
mainScriptUrlOrBlob: pthreadWorkerScript(base, bundle, traceMask),
|
||||
// Own the wasm fetch so we can report download progress (see helper). Streams
|
||||
// straight into the compiler; passing `module` to the callback lets emscripten
|
||||
// share it with the pthread workers (this hook fires on the main thread only).
|
||||
|
|
|
|||
|
|
@ -125,10 +125,10 @@ export function startDriftDetection(opts: DriftDetectOptions): DriftDetector {
|
|||
// unchanged since the last report, or the session cap is spent). Being fully
|
||||
// synchronous is what lets the session-end check finish during page unload.
|
||||
function computeDrift(): DriftReportBody | null {
|
||||
// Defer while collab fiber work is in flight (0008 finding #10b): a save
|
||||
// on the bare embind stack during a parked apply fiber mis-dispatches
|
||||
// Defer while collab apply work is in flight (0008 finding #10b): a save
|
||||
// on the bare embind stack during a parked apply coroutine mis-dispatches
|
||||
// (table index OOB). The next Y-update trigger retries.
|
||||
const busy = (opts.mod as { kicadCollabFiberBusy?: () => boolean }).kicadCollabFiberBusy;
|
||||
const busy = (opts.mod as { kicadCollabBusy?: () => boolean }).kicadCollabBusy;
|
||||
if (busy?.()) return null;
|
||||
save(scratchPath);
|
||||
let text: unknown;
|
||||
|
|
|
|||
|
|
@ -96,8 +96,9 @@ export const TOOL_FRAME: Record<Tool, string | undefined> = {
|
|||
* Every standalone tool here boots through common/single_top.cpp, which runs
|
||||
* STARTWIZARD::CheckAndRun() — the first-run "KiCad Setup" wizard. It shows
|
||||
* whenever any provider (SETTINGS / LIBRARIES / PRIVACY) reports
|
||||
* NeedsUserInput(), which is always true on our ephemeral MEMFS with no config,
|
||||
* and its modal loop crashes Asyncify. So for all of them we seed a default
|
||||
* NeedsUserInput() — always true on our ephemeral MEMFS with no config, where
|
||||
* the wizard's modal loop is unsupported (nothing it writes survives a reload,
|
||||
* so it would re-run every boot). So for all of them we seed a default
|
||||
* KiCad config before main() (kicad_common.json privacy flags + the lib-tables
|
||||
* the providers check) so NeedsUserInput() is false and the wizard is skipped.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -390,8 +390,9 @@ export function installLibsProvider(
|
|||
try {
|
||||
switch (op) {
|
||||
case "list": {
|
||||
// Enumerate gate (load-fanout): park this crossing (Asyncify) until
|
||||
// the caller's precondition — typically the presync — has settled.
|
||||
// Enumerate gate (load-fanout): park this crossing (the suspended
|
||||
// C++ caller stays parked) until the caller's precondition —
|
||||
// typically the presync — has settled.
|
||||
if (opts?.enumerateGate) await opts.enumerateGate(kind);
|
||||
// Each plugin parses its own key: footprints / symbols.
|
||||
const key = kind === "footprint" ? "footprints" : "symbols";
|
||||
|
|
@ -402,7 +403,7 @@ export function installLibsProvider(
|
|||
// Bracket the whole-library hydrate so the editor can overlay a
|
||||
// "loading libraries (slow, not hung)" state over the otherwise
|
||||
// silent multi-second freeze. `true` before the (async) IDB read so
|
||||
// the overlay can paint while the C++ side is Asyncify-suspended;
|
||||
// the overlay can paint while the C++ side is suspended;
|
||||
// `false` once the bytes are framed and about to cross the bridge.
|
||||
clearTimeout(fatResetTimer);
|
||||
if (fatTotal === 0) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
// Deliberately NOT here (S4 territory): worker-side interlock precision (a
|
||||
// mutator can still land while a NON-open chain is parked — those entries keep
|
||||
// their own C-side guards until S4 moves queuing into the bindings), and any
|
||||
// change to drift-detect's skip-if-fiber-busy logic (#10b semantics, its own
|
||||
// change to drift-detect's skip-if-collab-busy logic (#10b semantics, its own
|
||||
// deliberate contract).
|
||||
|
||||
interface BusyProbe {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { resolveWasmBase } from "./wasm-assets";
|
|||
* kicad_editor.wasm carries no ngspice: eeschema's NGSPICE class binds to the
|
||||
* sharedspice client stub (wasm/stubs/sharedspice_client.cpp), whose
|
||||
* EM_ASYNC_JS bridges suspend the editor and land here. The ngspice_service
|
||||
* module (own emscripten instance, pthreads, `-sASYNCIFY=0`) boots in a
|
||||
* module (own emscripten instance, pthreads, no suspension backend) boots in a
|
||||
* dedicated Worker on the FIRST request — a session that never opens the
|
||||
* simulator never fetches it.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { resolveWasmBase } from "./wasm-assets";
|
|||
* the SCENEGRAPH serialized in KiCad's binary cache format, which the
|
||||
* C++ stub rebuilds with S3D::ReadCache.
|
||||
*
|
||||
* The occ_service module (own emscripten instance, `-sASYNCIFY=0`) boots in a
|
||||
* The occ_service module (own emscripten instance, no suspension backend) boots in a
|
||||
* dedicated Worker on the FIRST request — a pcbnew session that never exports
|
||||
* and never views STEP models never fetches it. Same cross-origin worker rules
|
||||
* as the pthread workers (boot.ts): a same-origin blob wrapper importScripts
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { openFileInTool } from "./open-flow";
|
|||
* The programmatic-open settle gate (open_gate.h / kicadOpenFileBusy): the
|
||||
* shell must not report the open finished — and so must not go on to drive
|
||||
* bare embind entries (collab snapshot, presence bind) — while the
|
||||
* kicadOpenFile Asyncify chain is still parked mid-load. Regression tests for
|
||||
* kicadOpenFile activation is still parked mid-load. Regression tests for
|
||||
* the prod "indirect call signature mismatch" trap at board load.
|
||||
*/
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ function makeWin(opts: {
|
|||
Module: {
|
||||
kicadOpenFile: (p: string) => {
|
||||
opened.push(p);
|
||||
return false; // asyncify placeholder return — callers must ignore it
|
||||
return false; // non-Promise return — readiness comes from the settle probe, not this value
|
||||
},
|
||||
...(opts.busy ? { kicadOpenFileBusy: opts.busy } : {}),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -99,17 +99,16 @@ function hasProgrammaticHook(win: ToolWindow): boolean {
|
|||
}
|
||||
|
||||
/**
|
||||
* Invoke the programmatic hook. kicadOpenFile suspends mid-load either way:
|
||||
* under JSPI it is an embind async() export and returns a real Promise for the
|
||||
* whole load chain; legacy asyncify builds return a falsy placeholder. The
|
||||
* Invoke the programmatic hook. kicadOpenFile is an embind async() export: it
|
||||
* suspends mid-load and returns a real Promise for the whole load chain. The
|
||||
* caller contains the Promise's rejection and gates readiness on the settle
|
||||
* probe, which is truthful for both shapes.
|
||||
* probe.
|
||||
*/
|
||||
function invokeProgrammaticOpen(
|
||||
win: ToolWindow,
|
||||
absPath: string,
|
||||
log: (m: string) => void,
|
||||
): unknown {
|
||||
): Promise<unknown> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mod = win.Module as any;
|
||||
const ret = mod.kicadOpenFile(absPath);
|
||||
|
|
@ -143,10 +142,10 @@ function inputDialogVisible(win: ToolWindow): boolean {
|
|||
}
|
||||
|
||||
/**
|
||||
* Wait until the kicadOpenFile Asyncify chain has TRULY completed.
|
||||
* Wait until the kicadOpenFile load chain has TRULY completed.
|
||||
*
|
||||
* kicadOpenFile suspends and unwinds back to JS long before the load finishes;
|
||||
* for the whole load the chain stays parked mid-mutation of the board/schematic.
|
||||
* for the whole load its activation stays parked mid-mutation of the board/schematic.
|
||||
* Any bare embind entry that walks the model during such a park (collab
|
||||
* snapshot, presence bind) can virtual-dispatch through a half-built item and
|
||||
* trap with "indirect call signature mismatch" — the same reentrancy class the
|
||||
|
|
@ -209,7 +208,8 @@ export async function openFileInTool(
|
|||
// embind only registers the hook during runtime init — which lands AFTER the
|
||||
// Emscripten FS is ready, i.e. after driveProjectIntoTool calls us. Probing
|
||||
// the hook before the frame exists therefore always missed and fell back to
|
||||
// UI automation (and the wizard's modal loop then crashed Asyncify). Waiting
|
||||
// UI automation (and the wizard's unsupported modal loop then killed the
|
||||
// load — see boot.ts seedKicadConfig). Waiting
|
||||
// for a visible Frame guarantees the runtime is initialized, the hook is
|
||||
// registered, and a top window exists — so we probe only after this point.
|
||||
const ready = await waitFor(
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
/**
|
||||
* N5 — flood/fairness unit gates for the scheduler shim
|
||||
* (docs/features/async/17 §3d N5; the shim source is scripts/common/shims/
|
||||
* jspi-scheduler.js — the JSPI-era successor of asyncify-scheduler.js —
|
||||
* loaded here against a fake runtime surface).
|
||||
* jspi-scheduler.js, loaded here against a fake runtime surface).
|
||||
*
|
||||
* Doc 06 §starvation: FIFO by default; a stimulus flood must neither reorder
|
||||
* deliveries nor starve them, and the time-boxed pump must not monopolize the
|
||||
* thread in one burst. These are unit gates — the e2e batteries cover the
|
||||
* same machinery under the real runtime.
|
||||
*
|
||||
* Retired with the asyncify shim (states unrepresentable under JSPI):
|
||||
* deferred wakes (readyWakes/_scheduleWakeDrain), currData single-writer
|
||||
* tripwire, state() machine string. The S4 wait-registry gates below are the
|
||||
* JSPI-era additions.
|
||||
* same machinery under the real runtime. The S4 wait-registry gates below
|
||||
* cover the token-wait contract the C++ bridges rely on.
|
||||
*/
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
|
@ -25,7 +20,6 @@ const SHIM_PATH = path.resolve(
|
|||
);
|
||||
|
||||
type SchedulerShape = {
|
||||
backend: string;
|
||||
mailbox: unknown[];
|
||||
mutatorQueue: unknown[];
|
||||
mutatorsDelivered: number;
|
||||
|
|
@ -70,14 +64,14 @@ function loadShim(opts: { busy: () => boolean }) {
|
|||
return S;
|
||||
}
|
||||
|
||||
describe("N5: scheduler shim under flood (jspi backend)", () => {
|
||||
describe("N5: scheduler shim under flood", () => {
|
||||
beforeEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("identifies as the jspi backend", () => {
|
||||
it("installs the scheduler exactly once", () => {
|
||||
const S = loadShim({ busy: () => false });
|
||||
expect(S.backend).toBe("jspi");
|
||||
expect(S).toBeTruthy();
|
||||
expect(globalThis.__wxSchedulerInstalled).toBe(true);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue