mailbox S1: shim delivery tick, embind audit, app-side WasmMailbox
Shim: mailbox FIFO + self-armed delivery tick calling wxWasmMailboxTick (plain export — never inside a pump's awaited ccall); injector sentinel fixed (the old marker also matched evtloop's EM_JS probe text). Doc 18: 79-export embind audit (14+3 production mutators to wrap, 20 pure-read allowlist, asymmetries). web/standalone WasmMailbox: FIFO defer-until- settled keyed on the proxy-safe kicadOpenFileBusy probe, 7 vitest green. Dual-variant wx battery green (28+39+7 both variants). Bump wxwidgets. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfxKn5utcntBSnxz4ZnYKs
This commit is contained in:
parent
94ae4a8a41
commit
61b5f266fb
7 changed files with 419 additions and 15 deletions
|
|
@ -109,7 +109,9 @@ fi
|
|||
# the __wxSchedulerInstalled marker. Default OFF — the legacy build is the
|
||||
# shippable fallback until S5.
|
||||
if [ "${WX_SCHEDULER:-0}" = "1" ]; then
|
||||
if grep -q '__wxSchedulerInstalled' "$JS_FILE"; then
|
||||
# NOTE: must be the shim-source sentinel, not __wxSchedulerInstalled — that
|
||||
# string also appears in evtloop.cpp's EM_JS probe inside every glue.
|
||||
if grep -q '__WX_SCHEDULER_SHIM_SOURCE__' "$JS_FILE"; then
|
||||
echo "asyncify-scheduler already present - skipping"
|
||||
else
|
||||
echo "" >> "$JS_FILE"
|
||||
|
|
|
|||
|
|
@ -1,18 +1,74 @@
|
|||
// === AsyncifyScheduler (S0 scaffolding — observation-only) ===
|
||||
// === AsyncifyScheduler (S1 — mailbox front-end live; scheduler core lands in S2) ===
|
||||
// __WX_SCHEDULER_SHIM_SOURCE__ — injector idempotence sentinel. Must appear ONLY in
|
||||
// this file: the obvious marker (__wxSchedulerInstalled) also occurs in evtloop.cpp's
|
||||
// EM_JS probe text inside every glue, which made the injector skip real injections.
|
||||
// docs/features/async/17-mailbox-scheduler-plan.md · injected only on WX_SCHEDULER=1 builds.
|
||||
//
|
||||
// S0 contract: this file changes NO runtime behavior. It claims the namespace, the
|
||||
// registry data structures, and the build marker so (a) the dual-glue build variant
|
||||
// exists and can run the full suite, and (b) tests can detect which runtime they're on.
|
||||
// S2 turns this into the sole owner of Asyncify.currData/state (doc 13 §1): the four
|
||||
// hooks (handleSleep wrap, fiber-swap tracking, trampoline ownership, deferred drain)
|
||||
// land there, gated on the N1 single-writer tripwire. Until then the legacy
|
||||
// handlesleep.js shim (injected just above) stays authoritative.
|
||||
// S1 state: the MAILBOX is live — deferred browser callbacks (wx timers, via
|
||||
// wx/wasm/private/mailbox.h) are enqueued here on expiry and delivered by the wx
|
||||
// event pump (evtloop.cpp wxWasmMailboxDeliver) from a clean dispatch context,
|
||||
// only when the dispatch interlock is free. The legacy handlesleep.js shim
|
||||
// (injected just above) remains authoritative for currData until S2, when the
|
||||
// scheduler core (registry, deferred drain, fiber tracking) lands here gated on
|
||||
// the N1 single-writer tripwire.
|
||||
if (typeof Asyncify !== "undefined" && !globalThis.__wxSchedulerInstalled) {
|
||||
globalThis.__wxSchedulerInstalled = true;
|
||||
Asyncify.__schedulerBuild = 1;
|
||||
|
||||
var AsyncifyScheduler = {
|
||||
// --- S1 mailbox -------------------------------------------------------
|
||||
// Due messages, FIFO. {fn, arg} are wasm function-pointer / pointer ints;
|
||||
// the C side (wxWasmMailboxDeliver) pops and calls them. Exactly-once:
|
||||
// a message stays queued until the pump delivers it — there is no drop
|
||||
// path, matching the emscripten_async_call contract it replaces.
|
||||
mailbox: [],
|
||||
enqueued: 0,
|
||||
delivered: 0,
|
||||
_tickArmed: false,
|
||||
enqueueAfter: function (fn, arg, ms) {
|
||||
var self = this;
|
||||
setTimeout(function () {
|
||||
self.mailbox.push({ fn: fn, arg: arg });
|
||||
self.enqueued++;
|
||||
self._armDeliveryTick();
|
||||
}, ms);
|
||||
},
|
||||
pop: function () {
|
||||
var m = this.mailbox.shift();
|
||||
if (m) this.delivered++;
|
||||
return m || null;
|
||||
},
|
||||
// Deliver via a dedicated PLAIN export call (wxWasmMailboxTick), never
|
||||
// from inside a pump's awaited ProcessEvents ccall — a fiber swap there
|
||||
// sits on the JS-awaits-a-suspending-export boundary (#13302) and traps.
|
||||
// Re-arms at 17ms while messages remain (the interlock may be held; the
|
||||
// C side skips delivery then). One retry loop for the WHOLE queue — this
|
||||
// replaces the per-timer retry storms of the legacy path.
|
||||
_armDeliveryTick: function () {
|
||||
if (this._tickArmed) return;
|
||||
this._tickArmed = true;
|
||||
var self = this;
|
||||
setTimeout(function tick() {
|
||||
try {
|
||||
if (Module["_wxWasmMailboxTick"]) Module["_wxWasmMailboxTick"]();
|
||||
} catch (e) {
|
||||
self._tickArmed = false;
|
||||
// Mirror wxWasmScheduleProcessEvents' guard: a trapped delivery
|
||||
// must not leave the interlock held or a parked nested DoRun stuck.
|
||||
if (Module["_wx_dispatch_abandon"]) Module["_wx_dispatch_abandon"]();
|
||||
var exits = Module["_wxNestedLoopExit"];
|
||||
if (exits && exits.length) (exits.pop())();
|
||||
throw e;
|
||||
}
|
||||
if (self.mailbox.length > 0) {
|
||||
setTimeout(tick, 17);
|
||||
} else {
|
||||
self._tickArmed = false;
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
|
||||
// --- S2 scheduler core (not yet live) ---------------------------------
|
||||
// ctx = { id, kind: 'main'|'modal'|'nested'|'coroutine'|'sleep',
|
||||
// buffer, status: 'running'|'parked'|'ready', wakeReason, result }
|
||||
contexts: new Map(),
|
||||
|
|
@ -21,22 +77,24 @@ if (typeof Asyncify !== "undefined" && !globalThis.__wxSchedulerInstalled) {
|
|||
transitionRunning: false,
|
||||
trampolineRunning: false,
|
||||
|
||||
// S2 fills these in. They throw today so a premature caller is loud, not silent —
|
||||
// nothing in an S0 build calls them.
|
||||
// S2 fills these in. They throw today so a premature caller is loud, not
|
||||
// silent — nothing in an S1 build calls them.
|
||||
park: function () { throw new Error("[wx-scheduler] park(): not implemented until S2"); },
|
||||
resume: function () { throw new Error("[wx-scheduler] resume(): not implemented until S2"); },
|
||||
drain: function () { throw new Error("[wx-scheduler] drain(): not implemented until S2"); },
|
||||
|
||||
state: function () {
|
||||
return "[wx-scheduler] build=1 impl=S0-observation-only"
|
||||
return "[wx-scheduler] build=1 impl=S1-mailbox"
|
||||
+ " mailbox=" + this.mailbox.length
|
||||
+ " enqueued=" + this.enqueued
|
||||
+ " delivered=" + this.delivered
|
||||
+ " contexts=" + this.contexts.size
|
||||
+ " ready=" + this.readyQueue.length
|
||||
+ " running=" + this.running
|
||||
+ " transition=" + this.transitionRunning;
|
||||
},
|
||||
};
|
||||
|
||||
globalThis.__wxScheduler = AsyncifyScheduler;
|
||||
console.log("[wx-scheduler] scaffolding installed (S0, observation-only)");
|
||||
console.log("[wx-scheduler] scaffolding installed (S1, mailbox live)");
|
||||
}
|
||||
// === End AsyncifyScheduler ===
|
||||
|
|
|
|||
Loading…
Reference in a new issue