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;