Repository navigation
Lower fetch members typed by @types/node, and run the poll phase once per loop iteration - #1435
Merged
Merged
Conversation
…s as properties @types/node's fetch declarations (undici-types) spell the Headers methods and the body mixin as function-typed properties (readonly append: (...) => void), where scriptc's own declarations use methods. The receiver call lowering treated such a standard-library property as a stored callback: Headers.forEach was refused at build time and append/get/set/... compiled to a runtime 'is not a function'. Standard-library members now take the shared receiver dispatch either way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The net unit drained its poller every time the loop came back to the I/O station, including the restarts that drain microtasks. I/O that became ready during the iteration's own callbacks (a loopback connect started by one of them) was delivered before the check phase, so setImmediate callbacks queued earlier ran after it; Node delivers it in the next iteration's poll phase. The poller now drains once per iteration (a full batch keeps draining), reopening after the check phase. Back-to-back undici requests now reuse connections like Node, so the fixtures drop their setImmediate turn between requests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
fetch's input under @types/node is string | URL | Request. A union with a checked-dynamic arm collapses to the checked-dynamic representation only when every sibling survives the collapse; URL did not qualify, so the type (and anything typed as typeof globalThis.fetch, such as a custom fetch cast through unknown) did not compile. URL's handle boxes by reference, keeps its accessors and setters, answers instanceof URL and checks back out to the typed value, so URL arms now join the collapse. A fetch() input typed string | URL also converts through the checked-dynamic input path instead of being narrowed to a string, which threw on the URL arm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A full batch of 64 events polled again without returning to the event loop. When synchronous socket callbacks kept 64 events ready on every drain, timers and setImmediate callbacks never ran. The poll phase now drains at most 48 full batches, the count libuv's uv__io_poll uses, and the budget carries across the reopen that lets microtasks run, so the remaining events wait for the next loop iteration.
Replaces the single-parent "Merge main" squash (6ab9a54) with a two-parent merge of the same tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cramforce
force-pushed
the
opt/native-undici-followups
branch
from
October 20, 2026 09:50
6ab9a54 to
e4f72c6
Compare
A redirect fetch followed or rejected was marked to drain: its body was read and dropped until the server ended it, and the abort listener was already removed. A body that never ends kept the connection busy after cancellation and stalled a pool of one connection. Following or rejecting a redirect now aborts its request, so the connection closes. undici 7 leaves that connection open with its body paused, which stalls the same pool; the follow-up request here gets a new connection.
This branch was successfully deployed
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.
Stacked on #1425.
Summary
Fetch-family members under @types/node. undici-types, which supplies @types/node's fetch declarations, declares the
Headersmethods and theRequest/Responsebody mixin as function-typed properties (readonly append: (name: string, value: string) => void). scriptc's own declarations use methods.The receiver-call lowering took a property-declared member to be a stored callback. Under @types/node:
Headers.forEachwas refused at build time.append,get,set,delete,hasandgetSetCookiebuilt, then threw… is not a functionat run time.A member whose declarations all come from the standard library now takes the shared receiver dispatch, whichever declaration file supplies its type.
The rest of the fetch family behaves the same with either set of declarations:
Responsestatus, headers and body methods,Response.json,Requestreads, andAbortController. Iterating aHeaders(keys,values,entries, for-of) stays refused under both.Poll phase ordering. The event loop restarts its turn whenever a callback queues microtasks, and every restart drained the net poller again. So I/O that became ready during the iteration's own callbacks, such as a loopback connect started by a
'data'handler, ran before the check phase. Immediates queued earlier then ran after it. Node delivers that I/O in the next iteration's poll phase.The net unit now drains its poller once per loop iteration. A full batch keeps draining. The poller reopens after the check phase has run, or when no immediates are pending. Deferred emits still run on every call.
The visible effect: back-to-back
await request()calls on an undiciAgentnow open the same number of connections as Node (2, not 3), so the undici fixtures no longer need asetImmediateturn between requests.Evidence
tests/fixtures/node-types/fetch-handle-members.ts: Headers, Request and Response members typed by undici-types, run against Node throughproject-config.test.ts.6251-net-connect-poll-phase-order: the order of a new connection's'connect'against nextTick, promise jobs andsetImmediate, directly and through awaited promises. It fails with the previous runtime and matches Node with this one.setImmediateworkaround and still match Node, plain and sanitized.scr_loop_runand the newscr_loop_io_open(+40–128 B), which is the intended runtime change.test:ts7 --baselines-only(2 new baselines), and the self-host IR build (exit 0, no diagnostics).🤖 Generated with Claude Code