Repository navigation
[Prototype] Run await continuations from fibers on one reused hidden dispatch fiber (needs node#7) - #6
Draft
jbaczuk-qualia wants to merge 2 commits into
Draft
[Prototype] Run await continuations from fibers on one reused hidden dispatch fiber (needs node#7)#6jbaczuk-qualia wants to merge 2 commits into
jbaczuk-qualia wants to merge 2 commits into
Conversation
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>
Instead of a new hidden fiber per dispatched job, keep one long-lived hidden dispatch fiber. After each job it runs the dispatchable jobs queued right behind it (Fiber.__runNextDispatchable, needs the matching node patch), so a chain of awaits in fibered code costs one switch onto the fiber and one back instead of two per await. A job that blocks keeps the fiber and the next dispatch starts a new one; a fiber that was parked and later resumed never pulls in other jobs. FIBERS_AWAIT_REUSE=0 restores one new fiber per job. Fiber.microtasksBatched counts jobs run without a switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
R&D prototype, pairs with qualialabs/node#7 (node 18 V8 patch). Makes the code after an
await(and.thencallbacks) that was registered inside a fiber run on a fiber, so fiber-only calls work there instead of throwingCan't wait without a fiber. Continuations run on one long-lived hidden dispatch fiber, which also runs the dispatchable jobs queued right behind each one, so a chain ofawaits in fibered code costs one switch onto the fiber and one back instead of two perawait. Two commits onasync-resource: hidden-fiber dispatch, then the reused dispatch fiber.src/fibers.ccSwapContext()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. NewFiber.__setMicrotaskDispatcher,Fiber.__runNextDispatchable()andFiber.microtasksDispatched. The V8 API is resolved withdlsym, so this binary still loads (feature off) on stock nodefibers_async.jsreusingDispatcher: runs jobs on the dispatch fiber and, while it is still the dispatch fiber, keeps callingFiber.__runNextDispatchable(). A job that blocks keeps the fiber (it's parked inside that job) and the next dispatch starts a new one, the same approach as znewsham's drain fiber in this repo's #7. A parked fiber that is later resumed never pulls in other jobs. A dispatch that arrives while the dispatch fiber itself is running (nested drain) falls back to a one-off fiber.Fiber.currentreports hidden fibers asundefined; newFiber.currentIncludingHiddenreturns them.Fiber.microtasksBatchedcounts jobs run without a switchfuture.jsFuture.waitusesFiber.currentIncludingHiddenso it can block in a hidden fiberKill switches:
FIBERS_AWAIT_DISPATCH=0disables dispatch (stock behaviour);FIBERS_AWAIT_REUSE=0, or a node withoutv8_qualia_RunNextDispatchableMicrotask, runs each continuation on its own one-off hidden fiber.Why hidden fibers
A first version used ordinary fibers and broke qualia's boot:
Fiber.currentis also used as an API mode switch ("in a fiber, return the result synchronously, otherwise return a Promise"), e.g.@znewsham/meteor-mongo-collection-hookscursor.observeChanges. Async code after anawaitsuddenly got a handle instead of a Promise (platform-data-subscriptions/server.js:96:handlePromise.catch is not a function). Hiding the fiber keeps everyFiber.currentcheck answering exactly as it does today; only blocking primitives that opt in (Future.wait,Promise.awaitvia qualialabs/shared-packages#265) see the fiber.Testing
On the node 18 build from qualialabs/node#7, arm64
dev-qualiacontainer:.then, AsyncLocalStorage across the hop,Promise.alloverlap, errors,Fiber.currentstill undefined after await, a dual-mode API still returns a Promise after await, fiberless code untouched, microtask order; same 45 jobs: 27 dispatched + 18 batched with reuse, 45 dispatched without).FIBERS_AWAIT_DISPATCH=0fails the expected 7.test-microtask-fibers.js(withFiber.currentswapped for the running fiber,poolSize = 1e9): 7/7 behaviour tests pass, including "a parked microtask does not stall other promises" and "resuming a parked fiber does not drain unrelated microtasks mid-callback"; the 100k park/resume cycle test still exceeds its 10 s timeout (~9.7 s for the cycles alone; every park needs its own fiber).__setMicrotaskDispatcherabsent and behaves as before.test/: identical results on stock vs patched node (pool.js/cleanup.jssegfault on both: the pthreadcoro_destroybug fixed ind3260f6onnode24-pthread).await OrdersAsync.countDocuments()thenOrders.find().count()logs both counts; fiberless code still throwsCan't wait without a fiber.FIBERS_AWAIT_REUSE=0)awaits in a fiberawaitin a fiberKnown gaps
@meteorguards that throw without a fiber (accounts, syncMeteor.call) still checkFiber.current, so they throw after anawaitas they do today.awaitstill gets a Promise (unchanged from today).🤖 Generated with Claude Code