Skip to content

[Prototype] Node 18: let node-fibers run await continuations on a fiber (V8 microtask dispatch) - #7

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

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

Conversation

@jbaczuk-qualia

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

Copy link
Copy Markdown

Summary

R&D prototype: lets node-fibers run the code after an await (and .then callbacks) on a fiber when that code was registered inside a fiber. Today it resumes on the main stack, so a fiber-only call after await fails with Can't wait without a fiber:

Meteor.startup(async () => {                       // runs inside withFiber
  await OrdersAsync.countDocuments({});
  Orders.find().count();                           // stock node: "Can't wait without a fiber"
});

Based on znewsham/custom-v18 (f031bb860e, the source of nodejs-dev:f031bb860e, v18.16.1, V8 10.2). One commit, V8 only:

File Change
deps/v8/include/v8-microtask-dispatch.h (new) SetMicrotaskDispatchCallback / RunDispatchedMicrotask, plus unmangled v8_qualia_* C entry points so an addon can find them with dlsym and still load on unpatched node
deps/v8/src/builtins/builtins-microtask-queue-gen.cc Before running a promise fulfill/reject reaction job whose continuation-preserved embedder data (CPED, captured when the reaction was registered) is not undefined, call Runtime::kDispatchMicrotask; if the embedder took the job, skip it. After a reaction job, restore the previous CPED instead of clearing it, so a job run on a fiber leaves the fiber's marker in place
deps/v8/src/runtime/runtime-promise.cc, runtime.h Runtime_DispatchMicrotask: moves the job into a private single-job MicrotaskQueue (a GC root) and offers it to the embedder callback. RunDispatchedMicrotask runs that queue through the normal RunMicrotasks builtin, so context entry, CPED, promise hooks (async_hooks / AsyncLocalStorage) and exception reporting are unchanged

No effect unless an embedder registers a callback and something sets CPED (nothing in node 18 does). The fibers side is qualialabs/node-fibers#6 (and the Promise.await side, qualialabs/shared-packages#265).

Testing

Built in a dev-qualia:latest container (arm64, glibc of the qli5 image), with the fibers PR:

  • Standalone checks (await then fiber-only call, nested awaits, .then, AsyncLocalStorage across the hop, Promise.all overlap, errors, fiberless code untouched, microtask order): 11/11 pass; FIBERS_AWAIT_DISPATCH=0 fails the expected 7 (same as stock node).
  • node-fibers test/: identical results on stock vs patched node (pool.js/cleanup.js segfault on both, the known pthread coro_destroy bug).
  • Local qli5 qualia boots on this build and the scratch startup hook above logs both counts.

Risk / open questions

  • Prototype: not load-tested, no Lucky run yet. Every resume of fibered async code now costs a fiber switch (OS-thread handoff on CORO_PTHREAD).
  • Patches the microtask runner builtin, so it has to be re-ported on each node/V8 upgrade (node 24 would also need a non-ACF CPED story; servers run --no-async-context-frame).
  • PromiseResolveThenableJobTask is not dispatched (the reaction it creates carries the outer promise's CPED, so user continuations are still tagged).

🤖 Generated with Claude Code

Prototype for running the code after an `await` inside a fiber.

The microtask runner now offers fulfill/reject reaction jobs whose
continuation-preserved embedder data (CPED) is set to the embedder via
a new callback (include/v8-microtask-dispatch.h). The embedder can run
the job later on another stack with RunDispatchedMicrotask, which keeps
the usual bookkeeping (context, CPED, promise hooks, exceptions).
Unmangled C entry points let addons find the API with dlsym and still
load on unpatched node.

After a reaction job V8 now restores the previous CPED instead of
clearing it, so a job run on a fiber leaves the fiber's marker in place.

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