Skip to content

[Prototype] Batch consecutive await continuations on one fiber (follow-up to #7) - #9

Draft
jbaczuk-qualia wants to merge 1 commit into
jbaczuk/fiber-await-v18from
jbaczuk/fiber-await-v18-reuse
Draft

jbaczuk-qualia wants to merge 1 commit into
jbaczuk/fiber-await-v18from
jbaczuk/fiber-await-v18-reuse

Conversation

@jbaczuk-qualia

@jbaczuk-qualia jbaczuk-qualia commented Oct 2, 2026 •

Copy link
Copy Markdown

Summary

Optional follow-up to #7 (stacked on it; merge #7 first). Adds one V8 entry point so node-fibers can run a chain of await continuations from fibered code on one fiber instead of switching stacks for each one. Pairs with qualialabs/node-fibers#8.

File Change
deps/v8/include/v8-microtask-dispatch.h RunNextDispatchableMicrotask(isolate) and the unmangled v8_qualia_RunNextDispatchableMicrotask
deps/v8/src/runtime/runtime-promise.cc If the next job in the default microtask queue is a promise reaction job with CPED set, take it off the queue and run it on the current stack (through the same private-queue path as RunDispatchedMicrotask) and return true; otherwise return false and leave the queue alone

The embedder only calls it while the job #7 dispatched is still nested inside the microtask loop, and only takes jobs from the front, so the order of jobs is unchanged. The RunMicrotasks builtin re-reads the queue's start and size before every job, so the drain it's nested in carries on with the job after the ones taken. No builtin, runtime-function or snapshot changes beyond #7.

Results

Same arm64 dev-qualia container as #7, together with qualialabs/node-fibers#8 (FIBERS_AWAIT_REUSE=0 gives the #7 behaviour on the same binary):

stock node 18 #7 #7 + this
chain of awaits in a fiber 0.05–0.07 us ~25–30 us 0.13 us
I/O-style await in a fiber (each resumes from its own macrotask) ~1.1 us ~33 us ~28 us
100k park/resume cycles n/a ~109 us/cycle ~97 us/cycle
microtask drain per macrotask, code that never had a fiber 0.58 us 0.57 us 0.57 us
1e6 awaits outside any fiber ~40 ms ~40 ms ~43 ms

The I/O case is close to the floor for CORO_PTHREAD: a bare run() + yield() round trip is ~20 us in this Docker VM on stock node. In local qli5 qualia ~80% of dispatchable jobs since boot ran batched (46,624 dispatched, 227,679 batched); startup hooks that await and then block still work, and code that never had a fiber still doesn't get one.

🤖 Generated with Claude Code

RunNextDispatchableMicrotask (and the unmangled
v8_qualia_RunNextDispatchableMicrotask) takes the next job off the
default microtask queue if it is a promise reaction job with CPED set,
and runs it on the current stack the way RunDispatchedMicrotask does.

An embedder running a dispatched job on a coroutine can use it to keep
running the dispatchable jobs queued right behind it, instead of
switching back to the main stack and being handed each one separately.
The RunMicrotasks loop re-reads the queue's start and size before every
job, so the drain it is nested in continues with the job after them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant