Commit 261c8a196ce for nodejs
commit 261c8a196cea84c4ffcb119545520a11fc28318e
Author: Lazizbek Ergashev <lazerg2@gmail.com>
Date: Sun Sep 27 01:55:09 2026 +0500
events: fix addAbortListener for aborted signals
Signed-off-by: Lazizbek Ergashev <lazerg2@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65640
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
diff --git a/doc/api/events.md b/doc/api/events.md
index 0c61b178034..508153cf00e 100644
--- a/doc/api/events.md
+++ b/doc/api/events.md
@@ -1883,6 +1883,10 @@ added:
- v20.5.0
- v18.18.0
changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/65640
+ description: When the signal is already aborted, the listener now receives an
+ `abort` event and disposing cancels the pending call.
- version:
- v24.0.0
- v22.16.0
@@ -1908,6 +1912,9 @@ not prevent the listener from running.
Returns a disposable so that it may be unsubscribed from more easily.
+If `signal` is already aborted, the listener is called with an `abort` event in a
+microtask. Disposing before that microtask runs cancels the call.
+
```cjs
const { addAbortListener } = require('node:events');
diff --git a/lib/internal/event_target.js b/lib/internal/event_target.js
index 23a2ef70273..69cdc4aac5b 100644
--- a/lib/internal/event_target.js
+++ b/lib/internal/event_target.js
@@ -1243,6 +1243,8 @@ module.exports = {
kTrustEvent,
kRemoveListener,
kEvents,
+ kIsBeingDispatched,
+ kTarget,
kWeakHandler,
kResistStopPropagation,
isEventTarget,
diff --git a/lib/internal/events/abort_listener.js b/lib/internal/events/abort_listener.js
index 3f91cec5fac..38ad7351cf9 100644
--- a/lib/internal/events/abort_listener.js
+++ b/lib/internal/events/abort_listener.js
@@ -17,6 +17,7 @@ const {
let queueMicrotask;
let kResistStopPropagation;
let abortListenerOptions;
+let eventTarget;
/**
* @param {AbortSignal} signal
@@ -33,7 +34,20 @@ function addAbortListener(signal, listener) {
let removeEventListener;
if (signal.aborted) {
queueMicrotask ??= require('internal/process/task_queues').queueMicrotask;
- queueMicrotask(() => listener());
+ eventTarget ??= require('internal/event_target');
+ let disposed = false;
+ queueMicrotask(() => {
+ if (disposed) return;
+ const { Event, kIsBeingDispatched, kTarget, kTrustEvent } = eventTarget;
+ const event = new Event('abort', { [kTrustEvent]: true });
+ event[kTarget] = signal;
+ event[kIsBeingDispatched] = true;
+ listener(event);
+ event[kIsBeingDispatched] = false;
+ });
+ removeEventListener = () => {
+ disposed = true;
+ };
} else {
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
abortListenerOptions ??= ObjectFreeze({ __proto__: null, once: true, [kResistStopPropagation]: true });
diff --git a/test/parallel/test-events-add-abort-listener.mjs b/test/parallel/test-events-add-abort-listener.mjs
index 69b6a054e39..9f7c04859ef 100644
--- a/test/parallel/test-events-add-abort-listener.mjs
+++ b/test/parallel/test-events-add-abort-listener.mjs
@@ -33,6 +33,25 @@ describe('events.addAbortListener', () => {
assert.strictEqual(typeof disposable[Symbol.dispose], 'function');
});
+ it('should pass an abort event to the listener for aborted runners', async () => {
+ const signal = AbortSignal.abort();
+ const { promise, resolve } = Promise.withResolvers();
+ events.addAbortListener(signal, (e) => resolve([e, e?.currentTarget]));
+
+ const [event, currentTarget] = await promise;
+ assert.strictEqual(event.type, 'abort');
+ assert.strictEqual(event.target, signal);
+ assert.strictEqual(event.isTrusted, true);
+ assert.strictEqual(currentTarget, signal);
+ });
+
+ it('should not execute the listener for aborted runners when disposed', async () => {
+ const disposable = events.addAbortListener(AbortSignal.abort(), common.mustNotCall());
+ disposable[Symbol.dispose]();
+
+ await new Promise(setImmediate);
+ });
+
it('should execute the listener even when event propagation stopped', () => {
const controller = new AbortController();
const { signal } = controller;