mailbox S4: wait registry in the shim + work log; bump wxwidgets
Per-kind LIFO wait stacks (beginWait/waitPromise/resolveWait/ resolveTopWait), resolve-before-yield safe, S2 deferred-wake compliant. Gates: asyncify 9/9, coroutine 39/39, wx modal-heavy 45/45, kicad 6/6. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfxKn5utcntBSnxz4ZnYKs
This commit is contained in:
parent
5f29cd7be9
commit
5a8b0a3279
3 changed files with 81 additions and 2 deletions
|
|
@ -235,6 +235,24 @@ rollback = the `WX_SCHEDULER=0` build + a per-step tag.
|
||||||
3. modal dialog (`startModal` + `_wxModalResolvers` die),
|
3. modal dialog (`startModal` + `_wxModalResolvers` die),
|
||||||
4. clipboard + font enum,
|
4. clipboard + font enum,
|
||||||
5. **open** last — the biggest semantic flip (gates → ordering; N2/N3/N6 are the gate).
|
5. **open** last — the biggest semantic flip (gates → ordering; N2/N3/N6 are the gate).
|
||||||
|
> **Work log 2026-08-05 — S4 LANDED and gated (waits 1–3; 4–5 resolved by scoping).**
|
||||||
|
> The doc-13 §2 API exists as `wx/wasm/private/yieldwait.h` (`wxWasmBeginWait` /
|
||||||
|
> `wxWasmYieldUntil` / `wxWasmResolveWait`/`ResolveTopWait`) backed by the shim's wait
|
||||||
|
> registry (per-kind LIFO stacks, resolve-before-yield safe). On scheduler builds:
|
||||||
|
> **nested loops, popups, and modals are registered WAITS with NO pumps** — the top-level
|
||||||
|
> tick is the sole dispatcher at any DoRun depth (the s_wxRunDepth gate is scheduler-
|
||||||
|
> bypassed; the June §6e double-driver hazard needed two awaited pumps, which no longer
|
||||||
|
> exist). Dead on scheduler builds: `startModal`, `wxWasmRunNestedLoop`, the wx-dom popup
|
||||||
|
> pump, `_wxModalResolvers`, `_wxNestedLoopExit`, `_pendingModalResult` (waits begin
|
||||||
|
> before Show(), so a racing EndModal pre-resolves). Tick error containment now releases
|
||||||
|
> the innermost nested wait AND cancels the top modal (the dead pumps' catch role).
|
||||||
|
> Waits 4–5 by scoping: clipboard/font enum keep their EM_ASYNC_JS promise-waits — they
|
||||||
|
> have no pumps and are already S2-managed sleeps (migrating them to tokens buys
|
||||||
|
> uniformity, not behavior; revisit at S5 if the token registry should own ALL waits);
|
||||||
|
> open keeps the C++ gate as second line under the JS embind lane's queueing (N2-gated
|
||||||
|
> since S1). **Gates:** asyncify 9/9 (triple-modal LIFO now on wait stacks), coroutine
|
||||||
|
> 39/39 (quasi-modal with zero pumps), wx modal-heavy 45/45, kicad 6/6 incl.
|
||||||
|
> modal-stack + contextmenu-scrollbar on a fresh warm-cache build.
|
||||||
- **S5 · Guard demolition (few d).** Delete: dispatch interlock + save/zero/restore sites,
|
- **S5 · Guard demolition (few d).** Delete: dispatch interlock + save/zero/restore sites,
|
||||||
timer retry, `ProcessEvents` Paint-only gate, `s_wxRunDepth` gate, open-settle/fiber-busy/
|
timer retry, `ProcessEvents` Paint-only gate, `s_wxRunDepth` gate, open-settle/fiber-busy/
|
||||||
enumerate gates. Downgrade libcontext refusals to dev-build assertions; keep counters-only
|
enumerate gates. Downgrade libcontext refusals to dev-build assertions; keep counters-only
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,64 @@ if (typeof Asyncify !== "undefined" && !globalThis.__wxSchedulerInstalled) {
|
||||||
}, 16);
|
}, 16);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// --- S4 wait registry ---------------------------------------------------
|
||||||
|
// Token-based waits (doc 13 §2: wasm_begin_async_wait / wasm_yield_until /
|
||||||
|
// wasm_resolve_wait). A wait is begun BEFORE the C++ side parks, so a
|
||||||
|
// resolve that races ahead of the park (EndModal during Show()) simply
|
||||||
|
// pre-resolves the promise — yieldUntil then returns immediately. Per-kind
|
||||||
|
// LIFO stacks give wx modal/nested semantics ("innermost first") without
|
||||||
|
// the legacy per-wait resolver stacks (_wxModalResolvers /
|
||||||
|
// _wxNestedLoopExit), which die on scheduler builds. Resolution flows
|
||||||
|
// through the S2 deferred-wake law automatically: resolving a wait wakes
|
||||||
|
// its parked sleep via the wrapped handleSleep path.
|
||||||
|
waits: new Map(), // token → {kind, promise, resolve, resolved}
|
||||||
|
waitSeq: 0,
|
||||||
|
waitStacks: {}, // kind → [unresolved tokens], LIFO
|
||||||
|
waitsBegun: 0,
|
||||||
|
waitsResolved: 0,
|
||||||
|
beginWait: function (kind) {
|
||||||
|
var token = ++this.waitSeq;
|
||||||
|
var entry = { kind: kind, resolved: false, resolve: null, promise: null };
|
||||||
|
var self = this;
|
||||||
|
entry.promise = new Promise(function (resolve) { entry.resolve = resolve; });
|
||||||
|
this.waits.set(token, entry);
|
||||||
|
(this.waitStacks[kind] = this.waitStacks[kind] || []).push(token);
|
||||||
|
this.waitsBegun++;
|
||||||
|
return token;
|
||||||
|
},
|
||||||
|
waitPromise: function (token) {
|
||||||
|
var entry = this.waits.get(token);
|
||||||
|
if (!entry) {
|
||||||
|
console.warn("[wx-scheduler] waitPromise(" + token + "): unknown token");
|
||||||
|
return Promise.resolve(0);
|
||||||
|
}
|
||||||
|
return entry.promise;
|
||||||
|
},
|
||||||
|
resolveWait: function (token, result) {
|
||||||
|
var entry = this.waits.get(token);
|
||||||
|
if (!entry || entry.resolved) return false;
|
||||||
|
entry.resolved = true;
|
||||||
|
this.waitsResolved++;
|
||||||
|
var stack = this.waitStacks[entry.kind];
|
||||||
|
if (stack) {
|
||||||
|
var idx = stack.indexOf(token);
|
||||||
|
if (idx !== -1) stack.splice(idx, 1);
|
||||||
|
}
|
||||||
|
this.waits.delete(token);
|
||||||
|
entry.resolve(result | 0);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
// Resolve the INNERMOST unresolved wait of a kind (wx LIFO semantics).
|
||||||
|
resolveTopWait: function (kind, result) {
|
||||||
|
var stack = this.waitStacks[kind];
|
||||||
|
if (!stack || stack.length === 0) return false;
|
||||||
|
return this.resolveWait(stack[stack.length - 1], result);
|
||||||
|
},
|
||||||
|
pendingWaits: function (kind) {
|
||||||
|
var stack = this.waitStacks[kind];
|
||||||
|
return stack ? stack.length : 0;
|
||||||
|
},
|
||||||
|
|
||||||
// --- S2 scheduler core state -------------------------------------------
|
// --- S2 scheduler core state -------------------------------------------
|
||||||
// Deferred sleep wakes: {deliver, result} queued because a transition was
|
// Deferred sleep wakes: {deliver, result} queued because a transition was
|
||||||
// in flight when the wake arrived. Delivered FIFO from a clean macrotask.
|
// in flight when the wake arrived. Delivered FIFO from a clean macrotask.
|
||||||
|
|
@ -175,7 +233,10 @@ if (typeof Asyncify !== "undefined" && !globalThis.__wxSchedulerInstalled) {
|
||||||
+ " readyWakes=" + this.readyWakes.length
|
+ " readyWakes=" + this.readyWakes.length
|
||||||
+ " deferredWakes=" + this.deferredWakes
|
+ " deferredWakes=" + this.deferredWakes
|
||||||
+ " drainedWakes=" + this.drainedWakes
|
+ " drainedWakes=" + this.drainedWakes
|
||||||
+ " strayWrites=" + this.strayWrites;
|
+ " strayWrites=" + this.strayWrites
|
||||||
|
+ " waits=" + this.waits.size
|
||||||
|
+ " waitsBegun=" + this.waitsBegun
|
||||||
|
+ " waitsResolved=" + this.waitsResolved;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 6c33cafa9c31fba99d4dae4b0d300183751d05dd
|
Subproject commit 1953c18527e92204e1bbc957a90922f211937613
|
||||||
Loading…
Reference in a new issue