From ed41d04fc599df1dcac362caceff1f477ede1b1a Mon Sep 17 00:00:00 2001 From: lazerg Date: Thu, 8 Oct 2026 22:53:59 +0500 Subject: [PATCH] lib: keep AbortSignal.any() timeout sources alive Signed-off-by: lazerg Assisted-by: Claude --- lib/internal/abort_controller.js | 5 ++- .../test-abort-controller-any-timeout.js | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index 55be0b6f3669..02d0ad66116f 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -412,7 +412,10 @@ class AbortSignal extends EventTarget { [kRemoveListener](size, type, listener, capture) { super[kRemoveListener](size, type, listener, capture); const isTimeoutOrNonEmptyCompositeSignal = this[kTimeout] || (this[kComposite] && this[kSourceSignals]?.size); - if (isTimeoutOrNonEmptyCompositeSignal && type === 'abort' && size === 0) { + if (isTimeoutOrNonEmptyCompositeSignal && + type === 'abort' && + size === 0 && + !this[kDependantSignals]?.size) { gcPersistentSignals.delete(this); } } diff --git a/test/parallel/test-abort-controller-any-timeout.js b/test/parallel/test-abort-controller-any-timeout.js index 675be3af703c..cb7eb0a96a12 100644 --- a/test/parallel/test-abort-controller-any-timeout.js +++ b/test/parallel/test-abort-controller-any-timeout.js @@ -39,4 +39,39 @@ describe('AbortSignal.any() with timeout signals', () => { clearTimeout(timeout); } }); + + it('should abort after a listener is removed from the timeout signal', async () => { + const signal = (() => { + const timeoutSignal = AbortSignal.timeout(common.platformTimeout(100)); + const anySignal = AbortSignal.any([timeoutSignal]); + const listener = () => {}; + timeoutSignal.addEventListener('abort', listener); + timeoutSignal.removeEventListener('abort', listener); + return anySignal; + })(); + let timeout; + + const abortPromise = Promise.race([ + once(signal, 'abort').then(() => { + throw signal.reason; + }), + new Promise((resolve) => { + timeout = setTimeout(resolve, common.platformTimeout(10000)); + }), + ]); + + setImmediate(common.mustCall(() => globalThis.gc())); + + try { + await assert.rejects( + () => abortPromise, + { + name: 'TimeoutError', + message: 'The operation was aborted due to timeout' + } + ); + } finally { + clearTimeout(timeout); + } + }); });