Skip to content

Commit eca0fdb

Browse files
committed
events: fix addAbortListener for aborted signals
Signed-off-by: Lazizbek Ergashev <lazerg2@gmail.com>
1 parent 2f469df commit eca0fdb

4 files changed

Lines changed: 43 additions & 1 deletion

File tree

doc/api/events.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,6 +1883,10 @@ added:
18831883
- v20.5.0
18841884
- v18.18.0
18851885
changes:
1886+
- version: REPLACEME
1887+
pr-url: https://github.com/nodejs/node/pull/65640
1888+
description: When the signal is already aborted, the listener now receives an
1889+
`abort` event and disposing cancels the pending call.
18861890
- version:
18871891
- v24.0.0
18881892
- v22.16.0
@@ -1908,6 +1912,9 @@ not prevent the listener from running.
19081912

19091913
Returns a disposable so that it may be unsubscribed from more easily.
19101914

1915+
If `signal` is already aborted, the listener is called with an `abort` event in a
1916+
microtask. Disposing before that microtask runs cancels the call.
1917+
19111918
```cjs
19121919
const { addAbortListener } = require('node:events');
19131920

lib/internal/event_target.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,8 @@ module.exports = {
12431243
kTrustEvent,
12441244
kRemoveListener,
12451245
kEvents,
1246+
kIsBeingDispatched,
1247+
kTarget,
12461248
kWeakHandler,
12471249
kResistStopPropagation,
12481250
isEventTarget,

lib/internal/events/abort_listener.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const {
1717
let queueMicrotask;
1818
let kResistStopPropagation;
1919
let abortListenerOptions;
20+
let eventTarget;
2021

2122
/**
2223
* @param {AbortSignal} signal
@@ -33,7 +34,20 @@ function addAbortListener(signal, listener) {
3334
let removeEventListener;
3435
if (signal.aborted) {
3536
queueMicrotask ??= require('internal/process/task_queues').queueMicrotask;
36-
queueMicrotask(() => listener());
37+
eventTarget ??= require('internal/event_target');
38+
let disposed = false;
39+
queueMicrotask(() => {
40+
if (disposed) return;
41+
const { Event, kIsBeingDispatched, kTarget, kTrustEvent } = eventTarget;
42+
const event = new Event('abort', { [kTrustEvent]: true });
43+
event[kTarget] = signal;
44+
event[kIsBeingDispatched] = true;
45+
listener(event);
46+
event[kIsBeingDispatched] = false;
47+
});
48+
removeEventListener = () => {
49+
disposed = true;
50+
};
3751
} else {
3852
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
3953
abortListenerOptions ??= ObjectFreeze({ __proto__: null, once: true, [kResistStopPropagation]: true });

test/parallel/test-events-add-abort-listener.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,25 @@ describe('events.addAbortListener', () => {
3333
assert.strictEqual(typeof disposable[Symbol.dispose], 'function');
3434
});
3535

36+
it('should pass an abort event to the listener for aborted runners', async () => {
37+
const signal = AbortSignal.abort();
38+
const { promise, resolve } = Promise.withResolvers();
39+
events.addAbortListener(signal, (e) => resolve([e, e?.currentTarget]));
40+
41+
const [event, currentTarget] = await promise;
42+
assert.strictEqual(event.type, 'abort');
43+
assert.strictEqual(event.target, signal);
44+
assert.strictEqual(event.isTrusted, true);
45+
assert.strictEqual(currentTarget, signal);
46+
});
47+
48+
it('should not execute the listener for aborted runners when disposed', async () => {
49+
const disposable = events.addAbortListener(AbortSignal.abort(), common.mustNotCall());
50+
disposable[Symbol.dispose]();
51+
52+
await new Promise(setImmediate);
53+
});
54+
3655
it('should execute the listener even when event propagation stopped', () => {
3756
const controller = new AbortController();
3857
const { signal } = controller;

0 commit comments

Comments
 (0)