better async model - #153
Open
ceifa wants to merge 2 commits into
Open
Conversation
Quality cleanups from review, no behavior change: - collapse awaitInterruptible into settleOrInterrupt - share one settled-value marshaller (Thread.pushReturnValues; marshalResolved/marshalRejected) - centralize the abort/timeout classification in async.limitError - remove the syncDepth field, derivable from stackCanSuspend via one withSuspensionDisabled helper - extract the shared per-yield handling (throwIfLimitReached, handleHostYield) from the two run loops - drop the dead await-hook L parameter and inline the single-use heap-slice wrappers Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012hBeqdFnHzBUQ7zLRVYQxx
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.
Why
The async model had one primitive — yield the Lua coroutine and let a JS loop sniff the last yielded value — and everything (awaiting, time-slicing, cancellation, callbacks) was squeezed through it. That is why
:await()failed insidetable.sort/gsub/promise:nextcallbacks and in JS→Lua callbacks, why top-levelcoroutine.yieldvalues were dropped, why a parked run couldn't be timed out or aborted, and why a loop of awaits starved the event loop.Full design write-up and the measurements behind it:
docs/async-redesign/README.md.What changed
Two engines, chosen automatically at load (
async: 'auto' | 'jspi' | 'yield').WebAssembly.promising, so an:await()suspends the whole wasm stack and works anywhere: inside atable.sortcomparator, agsubcallback, apromise:nexthandler, a coroutine nothing drives from the host, or a Lua function called back from JS.Highlights
:await()across C-call boundaries under JSPI (previously an error).Promise, one that doesn't stays synchronous with no promise allocated.timeout/AbortSignalinstead of waiting for the promise to settle; the interrupt is not swallowable by a scriptpcall.coroutine.yieldthat isn't an await is a host yield: newrun(..., { onYield })receives its values and decides the resume values (previously silently dropped); an unrepresentable yielded value no longer crashes the run.MessageChannelmacrotask, so timer-driven code no longer starves and browsers avoid the 4 ms nested-setTimeoutclamp.Build
glue.wasmis now built with-sSUPPORT_LONGJMP=wasm(required for JSPI; also ~30–45% faster onpcall/error/yield paths, size-neutral).src/native/wasmoon.cgains a small C trampoline that reaches the suspending import only from a wasm frame, plus the existinglua_Integerdouble-twins.Cost / benefit (measured, Node 26)
Call Lua from JS,Call JS from Luaflat;lua_resume≈lua_pcallkper callback).