Skip to content

[Prototype] Run await continuations from fibers in hidden fibers (needs node#7) - #6

Draft
jbaczuk-qualia wants to merge 1 commit into
async-resourcefrom
jbaczuk/fiber-await-v18
Draft

jbaczuk-qualia wants to merge 1 commit into
async-resourcefrom
jbaczuk/fiber-await-v18

Conversation

@jbaczuk-qualia

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

Copy link
Copy Markdown

Summary

R&D prototype, pairs with qualialabs/node#7 (node 18 V8 patch). Makes the code after an await (and .then callbacks) that was registered inside a fiber run on a fiber, so fiber-only calls work there instead of throwing Can't wait without a fiber. One commit on async-resource:

File Change
src/fibers.cc SwapContext() sets the context's continuation-preserved embedder data (CPED) to a marker while a fiber runs, so V8 tags every promise reaction registered inside it. DispatchMicrotask() hands tagged jobs to a JS dispatcher; runDispatched(token) runs one. New Fiber.__setMicrotaskDispatcher and Fiber.microtasksDispatched. The V8 API is resolved with dlsym, so this binary still loads (feature off) on stock node
fibers_async.js Dispatcher: runs each tagged job in a new hidden fiber. Fiber.current reports hidden fibers as undefined; new Fiber.currentIncludingHidden returns them. FIBERS_AWAIT_DISPATCH=0 disables dispatch
future.js Future.wait uses Fiber.currentIncludingHidden so it can block in a hidden fiber

Why hidden fibers

A first version used ordinary fibers and broke qualia's boot: Fiber.current is also used as an API mode switch ("in a fiber, return the result synchronously, otherwise return a Promise"), e.g. @znewsham/meteor-mongo-collection-hooks cursor.observeChanges. Async code after an await suddenly got a handle instead of a Promise (platform-data-subscriptions/server.js:96: handlePromise.catch is not a function). Hiding the fiber keeps every Fiber.current check answering exactly as it does today; only blocking primitives that opt in (Future.wait, Promise.await via qualialabs/shared-packages#265) see the fiber.

Testing

On the node 18 build from qualialabs/node#7, arm64 dev-qualia container:

  • Standalone checks: 11/11 pass (await then fiber-only call, nested awaits, .then, AsyncLocalStorage across the hop, Promise.all overlap, errors, Fiber.current still undefined after await, a dual-mode API still returns a Promise after await, fiberless code untouched, microtask order). FIBERS_AWAIT_DISPATCH=0 fails the expected 7.
  • On stock node 18 the new binary loads with __setMicrotaskDispatcher absent and behaves as before.
  • test/: identical results on stock vs patched node (pool.js/cleanup.js segfault on both: the pthread coro_destroy bug fixed in d3260f6 on node24-pthread).
  • Local qli5 qualia boots; a startup hook doing await OrdersAsync.countDocuments() then Orders.find().count() logs both counts.

Known gaps

  • ~25 @meteor guards that throw without a fiber (accounts, sync Meteor.call) still check Fiber.current, so they throw after an await as they do today.
  • Legacy sync-style code that calls a dual-mode API after an await still gets a Promise (unchanged from today).
  • Cost: every resumed reaction registered in a fiber hops onto a fiber, including internal ones such as the resolve callback Promise.await registers. meteor-promise's fiber-count test (should not double-wrap callbacks, native Promise) sees one extra fiber on the patched node (passes with dispatch off). Not measured yet.
  • No version bump; nothing published.

🤖 Generated with Claude Code

Prototype, needs the patched node (v8-microtask-dispatch.h).

While a fiber runs, its context's CPED is a marker, so every promise
reaction registered inside it (the code after `await`, `.then`
callbacks) is handed to a dispatcher that runs it in a new fiber.

Those fibers are hidden: `Fiber.current` reports undefined there, as
it does after an `await` today, so Fiber.current-based mode switches
(e.g. cursor.observeChanges returning a Promise) are unchanged.
Future.wait uses the new `Fiber.currentIncludingHidden`, so blocking
calls work. The dispatch API is resolved with dlsym, so this binary
still loads (with the feature off) on stock node.
FIBERS_AWAIT_DISPATCH=0 disables it; Fiber.microtasksDispatched counts
dispatched jobs.

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