@@ -97,6 +97,13 @@ class Request {
9797
9898 #state
9999
100+ /**
101+ * Removes the `abort` listener that makes this request's signal follow the
102+ * passed signal. `null` when no such listener was registered.
103+ * @type {(() => void) | null }
104+ */
105+ #abortCleanup = null
106+
100107 // https://fetch.spec.whatwg.org/#dom-request
101108 constructor ( input , init = undefined ) {
102109 webidl . util . markAsUncloneable ( this )
@@ -436,12 +443,23 @@ class Request {
436443 setMaxListeners ( 1500 , signal )
437444 }
438445
439- util . addAbortListener ( signal , abort )
446+ const removeAbortListener = util . addAbortListener ( signal , abort )
440447 // The third argument must be a registry key to be unregistered.
441448 // Without it, you cannot unregister.
442449 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry
443450 // abort is used as the unregister key. (because it is unique)
444451 requestFinalizer . register ( ac , { signal, abort } , abort )
452+
453+ // Allow the listener to be removed deterministically once the fetch
454+ // that owns this request has settled, instead of relying solely on the
455+ // FinalizationRegistry (i.e. garbage collection). Reusing a single
456+ // signal across many requests would otherwise leak listeners.
457+ // See https://github.com/nodejs/undici/issues/5285
458+ this . #abortCleanup = ( ) => {
459+ requestFinalizer . unregister ( abort )
460+ removeAbortListener ( )
461+ this . #abortCleanup = null
462+ }
445463 }
446464 }
447465
@@ -868,15 +886,25 @@ class Request {
868886 static setRequestState ( request , newState ) {
869887 request . #state = newState
870888 }
889+
890+ /**
891+ * Removes the `abort` listener that makes this request's signal follow the
892+ * signal passed to its constructor, if any. Idempotent.
893+ * @param {Request } request
894+ */
895+ static removeRequestAbortListener ( request ) {
896+ request . #abortCleanup?. ( )
897+ }
871898}
872899
873- const { setRequestSignal, getRequestDispatcher, setRequestDispatcher, setRequestHeaders, getRequestState, setRequestState } = Request
900+ const { setRequestSignal, getRequestDispatcher, setRequestDispatcher, setRequestHeaders, getRequestState, setRequestState, removeRequestAbortListener } = Request
874901Reflect . deleteProperty ( Request , 'setRequestSignal' )
875902Reflect . deleteProperty ( Request , 'getRequestDispatcher' )
876903Reflect . deleteProperty ( Request , 'setRequestDispatcher' )
877904Reflect . deleteProperty ( Request , 'setRequestHeaders' )
878905Reflect . deleteProperty ( Request , 'getRequestState' )
879906Reflect . deleteProperty ( Request , 'setRequestState' )
907+ Reflect . deleteProperty ( Request , 'removeRequestAbortListener' )
880908
881909mixinBody ( Request , getRequestState )
882910
@@ -1111,5 +1139,6 @@ module.exports = {
11111139 fromInnerRequest,
11121140 cloneRequest,
11131141 getRequestDispatcher,
1114- getRequestState
1142+ getRequestState,
1143+ removeRequestAbortListener
11151144}
0 commit comments