Summary
On the --target web runtime (perry 0.5.1022), a compiled program's top-level code runs, but timer callbacks scheduled via setTimeout/setInterval never execute. Plain JS timers in the same page tick normally, so it's specific to the WASM→runtime timer bridge, not the browser.
Repro
// repro.ts
console.log('TOP_RAN'); // prints
setTimeout(() => console.log('TIMEOUT_FIRED'), 200); // never prints
setInterval(() => console.log('INTERVAL_FIRED'), 150); // never prints
PERRY_ALLOW_PERRY_FEATURES=1 perry compile repro.ts --target web -o repro.html
# serve + open in Chrome (headless=new, --disable-background-timer-throttling)
Console (captured via a console.log collector installed with Page.addScriptToEvaluateOnNewDocument, so capture timing isn't a factor):
Expected: TOP_RAN, then TIMEOUT_FIRED, then repeated INTERVAL_FIRED.
A plain-JS control in the identical headless setup (setInterval(()=>el.textContent='ticks='+(++n),150)) reaches ticks=8 in 1.5s — so real timers fire; only the perry-compiled callbacks don't.
Likely area
crates/perry-codegen-wasm/src/wasm_runtime.js rt.set_timeout / rt.set_interval:
set_interval: (closureHandle, delay) => {
const cb = getHandle(closureHandle);
if (!cb || !wasmInstance) return 0; // <- if cb is falsy, nothing is scheduled
const id = setInterval(() => {
const fn = wasmInstance.exports.__indirect_function_table?.get(cb.funcIdx | 0);
if (fn) fn(...cb.captures); // <- or fn lookup yields nothing
}, delay);
return id;
},
Either the closure handle passed from compiled code no longer resolves via getHandle() (so cb is falsy and no real timer is scheduled), or cb.funcIdx/cb.captures don't match the current closure ABI. Given the recent spin-throttle / async-scheduler work (#1114, #1181), a closure-representation drift seems plausible.
Impact
This blocks any web app that defers work:
@honeide/editor drives its input/render poll loop with setInterval(_pollAllEditors, 8) — with timers dead, the editor renders once but is non-interactive (no typing, getText() never updates, queued setText() never drains). This is currently blocking playground.perryts.com.
- Any user program using
setTimeout/setInterval/timer-backed async in the playground run sandbox.
Top-level console.log, DOM construction, and synchronous compute all work — it's specifically deferred callbacks.
Ask
Confirm whether this reproduces in a non-headless build too (I can only test headless here), and restore timer-callback dispatch on the web target. Happy to test a branch against the editor poll loop.
Summary
On the
--target webruntime (perry 0.5.1022), a compiled program's top-level code runs, but timer callbacks scheduled viasetTimeout/setIntervalnever execute. Plain JS timers in the same page tick normally, so it's specific to the WASM→runtime timer bridge, not the browser.Repro
Console (captured via a
console.logcollector installed withPage.addScriptToEvaluateOnNewDocument, so capture timing isn't a factor):Expected:
TOP_RAN, thenTIMEOUT_FIRED, then repeatedINTERVAL_FIRED.A plain-JS control in the identical headless setup (
setInterval(()=>el.textContent='ticks='+(++n),150)) reachesticks=8in 1.5s — so real timers fire; only the perry-compiled callbacks don't.Likely area
crates/perry-codegen-wasm/src/wasm_runtime.jsrt.set_timeout/rt.set_interval:Either the closure handle passed from compiled code no longer resolves via
getHandle()(socbis falsy and no real timer is scheduled), orcb.funcIdx/cb.capturesdon't match the current closure ABI. Given the recent spin-throttle / async-scheduler work (#1114, #1181), a closure-representation drift seems plausible.Impact
This blocks any web app that defers work:
@honeide/editordrives its input/render poll loop withsetInterval(_pollAllEditors, 8)— with timers dead, the editor renders once but is non-interactive (no typing,getText()never updates, queuedsetText()never drains). This is currently blockingplayground.perryts.com.setTimeout/setInterval/timer-backed async in the playground run sandbox.Top-level
console.log, DOM construction, and synchronous compute all work — it's specifically deferred callbacks.Ask
Confirm whether this reproduces in a non-headless build too (I can only test headless here), and restore timer-callback dispatch on the web target. Happy to test a branch against the editor poll loop.