wasm: keep one pending scheduler wakeup instead of leaking a timer chain - #5622
Open
0pcom wants to merge 1 commit into
Open
wasm: keep one pending scheduler wakeup instead of leaking a timer chain#56220pcom wants to merge 1 commit into
0pcom wants to merge 1 commit into
Conversation
sleepTicks armed a fresh setTimeout on every call and never cancelled the previous one. Its callback calls go_scheduler(), which re-enters the scheduler, which calls sleepTicks again whenever it finds nothing runnable but something sleeping — so every armed timer replaces itself, and every JS->Go callback that reaches the scheduler starts another such chain. The pending count grows without bound. The scheduler only needs one pending wakeup, the earliest. Track it, and drop any request that is no sooner than what is already armed. Measured in Chrome with the same wasm binary, 50 sleeping goroutines and a requestAnimationFrame loop, counting setTimeout calls per second at three points: 487/1498/2509 before, 9/5/5 after. Go-side timing is unchanged (a goroutine sleeping in a loop still advances 3s over 3s of wall clock) and frame pacing is unchanged at ~59fps. Fixes tinygo-org#5621
0pcom
added a commit
to 0magnet/tinygo
that referenced
this pull request
Aug 28, 2026
Carries the fix sent upstream as tinygo-org#5622 (issue tinygo-org#5621) onto this branch, because the wasm-visor lane builds against this checkout's TINYGOROOT and so ships this shim. sleepTicks armed a fresh setTimeout on every call and never cancelled the previous one; its callback calls go_scheduler(), which calls sleepTicks again, so every armed timer replaces itself and every JS->Go callback that reaches the scheduler starts another chain. Measured in Chrome on one wasm binary, counting setTimeout calls per second at t+3s/t+20s/t+40s: 487/1498/2509 before, 9/5/5 after, with Go-side timing and frame pacing unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
sleepTicksarmed a freshsetTimeouton every call and never cancelled theprevious one. Its callback calls
go_scheduler(), which re-enters thecooperative scheduler, which calls
sleepTicksagain whenever it finds nothingrunnable but something sleeping (
scheduler_cooperative.go:258, and:285onwasmexport return). So each armed timer replaces itself indefinitely, and every
JS→Go callback that reaches the scheduler starts another such chain. Nothing
cancels one, so the pending count grows without bound.
The scheduler only ever needs one pending wakeup — the earliest deadline. This
tracks it and drops any request that is no sooner than what is already armed.
run()also clears a wakeup left over from a previous run, which wouldotherwise suppress the first one the new run asks for.
Measured in Chrome with the same wasm binary both times — 50 sleeping goroutines
and a
requestAnimationFrameloop — countingsetTimeoutcalls per second:A larger application reached ~2,800/sec inside a minute. Go-side timing is
unaffected — a goroutine sleeping in a loop still advances exactly 3s over 3s of
wall clock — and frame pacing is unchanged at ~59fps with the animation
rendering correctly.
Only
targets/wasm_exec.jschanges, so there is no Go code tomake fmt; Iverified the file parses and exercised it in a browser rather than running the
full suite.
Fixes #5621