Repository navigation
Compile events.once with an AbortSignal and over streams, and combine Promise.race entries around it - #1531
Merged
Conversation
events.once(emitter, name, { signal }) lowers to events.onceSignal, served
by a new runtime unit (scr_events_once_signal.c, linked only through the
eventsOnceSignal feature). It follows Node 24's lib/events.js: an
undefined signal is the plain form, a value that is not an AbortSignal
rejects with ERR_INVALID_ARG_TYPE, an aborted signal rejects with an
AbortError whose cause is signal.reason, and otherwise a once,
resist-stop-propagation 'abort' listener removes the event and 'error'
listeners and rejects. The event or the error removes the abort listener.
Options other than an object literal with one signal stay refused.
events.once now accepts stream receivers: a stream's runtime-emitted
events use their fixed tuples, and its 'data' and 'keypress' events stay
refused. A NodeJS stream interface slot that holds a stdio handle requests
the general stream representation.
Node's once is an async function that returns the executor's promise, so
its result adopts that promise through a thenable job. The plain form
settled its result two turns early; both forms now settle on Node's turn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Promise.race portion of 465abda on main, applied unchanged so the race changes that follow build on it and merge with main cleanly: a Promise<never> entry can only reject, so it races under any result type and its fulfillment adapter traps; a Promise<never> executor's one-parameter resolve validates against the void inner. Includes that commit's corpus program 6450. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the checker's combined type has no representation (events.once's any[] arrays, or 'void | any[]'), the race result takes the one type the entries that can fulfill share, or 'unknown' when each of them widens into it; Promise<never> entries do not take part. An entry whose payload widens into the result type converts through the checked promise payload widening, which keeps its settlement turn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A stream subclass that declares `_transform(chunk, encoding, cb: Function)` (or _write, _final, _destroy, _flush with a broad `Function` callback) was rejected because 'Function' has no lowering, which also blocked every construction of the class. The callback parameter now takes the checked-dynamic callable the runtime already passes implicitly typed callbacks, and calls through it are dynamic calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under checked-dynamic NodeJS stream slots, events.once over a NodeJS.ReadableStream or NodeJS.WritableStream slot unboxes a native stream by identity and takes the stream path; any other value throws the not-supported Error that for await and pipeline stages already throw for JavaScript stream objects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent its boundary The fallback declaration of events.once takes NodeJS.ReadableStream and NodeJS.WritableStream emitters like @types/node does. The diagnostics fixture now pins the forms that stay refused (options that are not an object literal, a computed name, a stream's 'data' event), and the limitations page states the supported events.once forms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in races 6490 covers events.once with an AbortSignal: abort, an aborted signal, an undefined signal, abort-listener order, 'removeListener' events, a throwing 'newListener' listener and the settlement turns. 6491 covers stream receivers (file streams, Readable.from, a destroyed PassThrough, a Writable) and a NodeJS.ReadableStream part raced against its destination. 6492 covers races over events.once results with entries that only reject and entries of different value types. 6493 covers stream subclasses whose completion callbacks are typed `Function`. 6494 covers events.once over checked-dynamic stream slots. The node-types fixture runs the race shapes under @types/node, where events.once returns Promise<any[]>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
Base:
opt/node-stream-dyn(#1524, stacked on #1518). The events.once receiver in the motivating shape is aNodeJS.ReadableStreamslot, which these two PRs represent as a runtime stream class or as a checked-dynamic slot.This PR makes the following shape compile and run with Node's behavior:
events.once with
{ signal }events.once(emitter, name, { signal })lowers to a new libCall,events.onceSignal, served by a new runtime unit,scr_events_once_signal.c. It is linked only through the neweventsOnceSignalfeature, so programs that do not pass options link the same objects as before. The unit follows Node 24'slib/events.js:ERR_INVALID_ARG_TYPE;signal.reason, without registering anything;'abort'listener removes the event and'error'listeners (with their'removeListener'events) and rejects with the AbortError;'newListener'listener rejects the promise, as before.The listener closures reach the shared state through link objects that clear the state's pointer when a closure is freed, so removals never compare a stale identity. The abort listener holds the emitter, like the closure Node's abortListener keeps; the cycle through the signal ends when the promise settles or the signal aborts, or at the exit teardown of the signal's listeners.
scr_events_emitter.cgains one exported wrapper,scr_emitter_unadd_quiet, for the'newListener'undo.Options that are not an object literal with one
signalproperty stay refused.Settlement turn of events.once
Node's
onceis an async function that returns the executor's promise, so its result adopts that promise through a thenable job and settles two microtask turns after the executor's promise. The existing plain form returned the executor's promise itself and settled two turns early. Both forms now adopt through a queued job and match Node's order against other promise chains (6490 pins it). This changes the binaries of programs that call the plainevents.once(see the gate below).events.once over streams
Stream receivers no longer fence: the runtime-emitted events (
end,close,finish,error, ...) use their fixed tuples, asstream.ondoes. A stream's'data'and'keypress'events stay refused. A NodeJS stream interface slot that holds a stdio handle requests the general stream representation (#1518). Under checked-dynamic stream slots (#1524), a native stream unboxes by identity; any other value throws the not-supported Error thatfor awaitand pipeline stages already throw for JavaScript stream objects. The fallback declaration ofevents.onceacceptsNodeJS.ReadableStreamandNodeJS.WritableStreamlike@types/nodedoes.Promise.race
The second commit applies the Promise.race part of main's 465abda unchanged (Promise entries race under any result type and their fulfillment adapter traps), so the change that follows merges with main cleanly. On top of it: when the checker's combined type has no representation (events.once's
any[]under@types/node, orvoid | any[]), the result takes the one type the entries that can fulfill share, orunknownwhen each of them widens into it. An entry whose payload widens into the result type converts through the checked promise payload widening, which keeps its settlement turn. Programs whose race already compiled are unchanged.Function-typed stream callbacks
A stream subclass that declares
_transform(chunk, encoding, cb: Function)(or_write,_final,_destroy,_flushwith a broadFunctioncallback) was rejected becauseFunctionhas no lowering, which also blocked everynewof the class. The callback parameter now takes the checked-dynamic callable the runtime already passes implicitly typed callbacks, and calls through it are dynamic calls.Tests
'removeListener'events, throwing'newListener', settlement turns, pending onces at exit).NodeJS.ReadableStreampart raced against its destination.Function-typed completion callbacks.tests/fixtures/node-types/events-once-signal-race.ts: the race shapes under@types/node(new project-config test).tests/diagnostics/events-once-fences.tsnow pins the forms that stay refused.All pass under Node and native in the plain and
SCRIPTC_SAN=1lanes. In the sandbox, the focused differential run (event, emitter, promise, race, stream, abort and signal programs, 260 per lane) fails only on programs that fail the same way on the base (2599, 5342, 5343, 5372-5374, 5423, 5564, 5614, 5761, 5762). The diagnostics and errors suites pass, the node-types tests pass apart from the known environment failures, the self-hosting IR build reports no diagnostics,tests/harness/self-hosting-native-driver.test.tspasses, and the order-parity baselines for the new fixtures are recorded. The docs check passes.Zero-cost gate
scripts/zero-cost-gate.mjs --corpus=allagainstopt/node-stream-dyn(8284e61): 3317 programs identical, 3 differ, 1 fails in both trees (node24-file-url-options.ts, environment). The three that differ are the programs that call the plainevents.once, whosescr_events_oncenow queues the adoption job: 5622-events-once.mjs (+184 B), 5623-events-once.ts (+320 B) and 5885-events-max-listener-helpers.ts (+4.4 KB, it now links the microtask and race-join helpers). Runtime benchmarks: geomean +0.19% (95% CI -0.55%, +0.83%), self-hosted compiler -0.52%, both within tolerance.Not covered
'data'event, and options other than a literal{ signal }stay refused.🤖 Generated with Claude Code