Commit 818f34833af for nodejs
commit 818f34833afc32da4768374ab035c0d053ad1cd9
Author: James M Snell <jasnell@gmail.com>
Date: Sun Oct 4 02:47:38 2026 +0000
stream: share the once option for stream/iter abort listeners
stream/iter registered its one-time 'abort' listeners with a new
`{ __proto__: null, once: true }` options object every time, which can
be once per chunk (abortableNext()) or per waiting write. Use a single
shared kNullOnceOption instead. It is frozen, because signals can come
from user code and a patched addEventListener() must not be able to
change the options for every later registration.
The difference is small, since the listener registration itself costs
much more: with 300,000 16-byte chunks, pull() with a signal is about
1.7% faster, push() writes that wait with a signal about 3% faster, and
pipeTo() and bytes() with a signal are unchanged.
Assisted-by: OpenCode
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66648
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
diff --git a/lib/internal/streams/iter/broadcast.js b/lib/internal/streams/iter/broadcast.js
index e395b5e84f2..00ed899d653 100644
--- a/lib/internal/streams/iter/broadcast.js
+++ b/lib/internal/streams/iter/broadcast.js
@@ -61,6 +61,7 @@ const {
PendingRequest,
PendingWrite,
kMultiConsumerDefaultBudget,
+ kNullOnceOption,
kResolvedPromise,
convertChunks,
createBatchEntry,
@@ -99,7 +100,7 @@ function raceEndWithSignal(promise, signal) {
const { promise: aborted, reject } = PromiseWithResolvers();
const onAbort = () => reject(signal.reason);
- signal.addEventListener('abort', onAbort, { __proto__: null, once: true });
+ signal.addEventListener('abort', onAbort, kNullOnceOption);
if (signal.aborted) onAbort();
return SafePromisePrototypeFinally(
@@ -873,7 +874,7 @@ function wireBroadcastWriteSignal(entry, signal, resolve, reject, self) {
entry.batch = null;
reject(reason);
};
- signal.addEventListener('abort', onAbort, { __proto__: null, once: true });
+ signal.addEventListener('abort', onAbort, kNullOnceOption);
}
// =============================================================================
diff --git a/lib/internal/streams/iter/classic.js b/lib/internal/streams/iter/classic.js
index 0eafba0182b..c1ed8ad51f5 100644
--- a/lib/internal/streams/iter/classic.js
+++ b/lib/internal/streams/iter/classic.js
@@ -63,6 +63,7 @@ const {
const {
PendingRequest,
+ kNullOnceOption,
convertChunks,
getWriterSignal,
onSignalAbort,
@@ -103,10 +104,7 @@ function raceWithSignal(promise, signal) {
reject,
} = PromiseWithResolvers();
const onAbort = () => reject(signal.reason);
- signal.addEventListener('abort', onAbort, {
- __proto__: null,
- once: true,
- });
+ signal.addEventListener('abort', onAbort, kNullOnceOption);
PromisePrototypeThen(
promise,
(value) => {
diff --git a/lib/internal/streams/iter/consumers.js b/lib/internal/streams/iter/consumers.js
index 8678e8235ac..0751d2cc95f 100644
--- a/lib/internal/streams/iter/consumers.js
+++ b/lib/internal/streams/iter/consumers.js
@@ -54,6 +54,7 @@ const {
} = require('internal/streams/iter/from');
const {
+ kNullOnceOption,
concatBytes,
getProtocolMethod,
recordChunk,
@@ -528,10 +529,7 @@ function merge(...args) {
waitResolve = null;
}
};
- signal.addEventListener('abort', onAbort, {
- __proto__: null,
- once: true,
- });
+ signal.addEventListener('abort', onAbort, kNullOnceOption);
}
// Called when a source's .next() settles. Pushes the result into
diff --git a/lib/internal/streams/iter/duplex.js b/lib/internal/streams/iter/duplex.js
index 95a117d8141..ce5ccadd060 100644
--- a/lib/internal/streams/iter/duplex.js
+++ b/lib/internal/streams/iter/duplex.js
@@ -18,6 +18,7 @@ const {
const {
converters,
} = require('internal/streams/iter/webidl');
+const { kNullOnceOption } = require('internal/streams/iter/utils');
/**
* Create a pair of connected duplex channels for bidirectional communication.
@@ -66,8 +67,7 @@ function duplex(options = { __proto__: null }) {
if (signal.aborted) {
abortBoth();
} else {
- signal.addEventListener('abort', abortBoth,
- { __proto__: null, once: true });
+ signal.addEventListener('abort', abortBoth, kNullOnceOption);
}
}
diff --git a/lib/internal/streams/iter/pull.js b/lib/internal/streams/iter/pull.js
index 35836dd27d6..fc8fa9902c1 100644
--- a/lib/internal/streams/iter/pull.js
+++ b/lib/internal/streams/iter/pull.js
@@ -53,6 +53,7 @@ const {
const {
IterResult,
+ kNullOnceOption,
createBatchEntry,
isTransformObject,
parsePullArgs,
@@ -748,7 +749,7 @@ async function* createAsyncPipeline(source, transforms, signal) {
abortHandler = () => {
abortSignal(controller.signal, signal.reason);
};
- signal.addEventListener('abort', abortHandler, { __proto__: null, once: true });
+ signal.addEventListener('abort', abortHandler, kNullOnceOption);
}
let completed = false;
@@ -992,8 +993,7 @@ function pullWithConsumerCleanup(source, transforms, signal) {
if (signal !== undefined) {
abortHandler = () => closeSource('throw', signal.reason);
- signal.addEventListener('abort', abortHandler,
- { __proto__: null, once: true });
+ signal.addEventListener('abort', abortHandler, kNullOnceOption);
if (signal.aborted) abortHandler();
}
diff --git a/lib/internal/streams/iter/push.js b/lib/internal/streams/iter/push.js
index b5db45a1a32..c99253af43e 100644
--- a/lib/internal/streams/iter/push.js
+++ b/lib/internal/streams/iter/push.js
@@ -32,6 +32,7 @@ const {
IterResult,
PendingRequest,
PendingWrite,
+ kNullOnceOption,
kPushDefaultBudget,
kResolvedPromise,
createBatchEntry,
@@ -72,10 +73,7 @@ function raceEndWithSignal(promise, signal) {
} = PromiseWithResolvers();
const onAbort = () => reject(signal.reason);
- signal.addEventListener('abort', onAbort, {
- __proto__: null,
- once: true,
- });
+ signal.addEventListener('abort', onAbort, kNullOnceOption);
PromisePrototypeThen(
promise,
(value) => {
@@ -320,7 +318,7 @@ class PushQueue {
reject(reason);
};
- signal.addEventListener('abort', onAbort, { __proto__: null, once: true });
+ signal.addEventListener('abort', onAbort, kNullOnceOption);
}
return promise;
diff --git a/lib/internal/streams/iter/transform.js b/lib/internal/streams/iter/transform.js
index 2cd06957b80..c6c315cf6bd 100644
--- a/lib/internal/streams/iter/transform.js
+++ b/lib/internal/streams/iter/transform.js
@@ -40,6 +40,7 @@ const {
} = require('internal/errors');
const { isArrayBufferView, isAnyArrayBuffer } = require('internal/util/types');
const { kValidatedTransform } = require('internal/streams/iter/types');
+const { kNullOnceOption } = require('internal/streams/iter/utils');
const {
checkRangesOrGetDefault,
kValidateObjectAllowArray,
@@ -413,7 +414,7 @@ function makeZlibTransform(createHandleFn, processFlag, finishFlag) {
reject(signal.reason);
}
};
- signal.addEventListener('abort', onAbort, { __proto__: null, once: true });
+ signal.addEventListener('abort', onAbort, kNullOnceOption);
function continueInputAsync() {
const { promise, resolve, reject } = PromiseWithResolvers();
diff --git a/lib/internal/streams/iter/utils.js b/lib/internal/streams/iter/utils.js
index 8b0c074b4e1..8c2835acd8b 100644
--- a/lib/internal/streams/iter/utils.js
+++ b/lib/internal/streams/iter/utils.js
@@ -47,6 +47,11 @@ const {
kValidatedTransform,
} = require('internal/streams/iter/types');
+// Shared `addEventListener()` options for one-time 'abort' listeners. Frozen
+// because signals can come from user code, and a patched addEventListener()
+// must not be able to change the options for every later registration.
+const kNullOnceOption = ObjectFreeze({ __proto__: null, once: true });
+
// Cached resolved promise to avoid allocating a new one on every sync fast-path.
const kResolvedPromise = PromiseResolve();
@@ -98,7 +103,7 @@ function onSignalAbort(signal, handler) {
if (signal.aborted) {
handler();
} else {
- signal.addEventListener('abort', handler, { __proto__: null, once: true });
+ signal.addEventListener('abort', handler, kNullOnceOption);
}
}
@@ -122,7 +127,7 @@ function abortableNext(iterator, signal) {
const next = iterator.next();
const { promise, reject } = PromiseWithResolvers();
const onAbort = getOnAbort(reject, signal);
- signal.addEventListener('abort', onAbort, { __proto__: null, once: true });
+ signal.addEventListener('abort', onAbort, kNullOnceOption);
if (signal.aborted) {
onAbort();
}
@@ -594,6 +599,7 @@ module.exports = {
PendingRequest,
PendingWrite,
kMultiConsumerDefaultBudget,
+ kNullOnceOption,
kPushDefaultBudget,
kResolvedPromise,
concatBytes,