Skip to content

Commit fd55d3c

Browse files
geeksilva97aduh95
authored andcommitted
lib: clean up persisted signals when they are settled
PR-URL: #56001 Refs: #55328 Fixes: #55328 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Jason Zhang <xzha4350@gmail.com> Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
1 parent 84f98e0 commit fd55d3c

File tree

2 files changed

+77
-0
lines changed

2 files changed

+77
-0
lines changed

lib/internal/abort_controller.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,21 @@ const dependantSignalsCleanupRegistry = new SafeFinalizationRegistry((signalWeak
9696
}
9797
});
9898
});
99+
99100
const gcPersistentSignals = new SafeSet();
100101

102+
const sourceSignalsCleanupRegistry = new SafeFinalizationRegistry(({ sourceSignalRef, composedSignalRef }) => {
103+
const composedSignal = composedSignalRef.deref();
104+
if (composedSignal !== undefined) {
105+
composedSignal[kSourceSignals].delete(sourceSignalRef);
106+
107+
if (composedSignal[kSourceSignals].size === 0) {
108+
// This signal will no longer abort. There's no need to keep it in the gcPersistentSignals set.
109+
gcPersistentSignals.delete(composedSignal);
110+
}
111+
}
112+
});
113+
101114
const kAborted = Symbol('kAborted');
102115
const kReason = Symbol('kReason');
103116
const kCloneData = Symbol('kCloneData');
@@ -260,6 +273,10 @@ class AbortSignal extends EventTarget {
260273
resultSignal[kSourceSignals].add(signalWeakRef);
261274
signal[kDependantSignals].add(resultSignalWeakRef);
262275
dependantSignalsCleanupRegistry.register(resultSignal, signalWeakRef);
276+
sourceSignalsCleanupRegistry.register(signal, {
277+
sourceSignalRef: signalWeakRef,
278+
composedSignalRef: resultSignalWeakRef,
279+
});
263280
} else if (!signal[kSourceSignals]) {
264281
continue;
265282
} else {
@@ -277,6 +294,10 @@ class AbortSignal extends EventTarget {
277294
resultSignal[kSourceSignals].add(sourceSignalWeakRef);
278295
sourceSignal[kDependantSignals].add(resultSignalWeakRef);
279296
dependantSignalsCleanupRegistry.register(resultSignal, sourceSignalWeakRef);
297+
sourceSignalsCleanupRegistry.register(signal, {
298+
sourceSignalRef: sourceSignalWeakRef,
299+
composedSignalRef: resultSignalWeakRef,
300+
});
280301
}
281302
}
282303
}
@@ -436,6 +457,7 @@ class AbortController {
436457
*/
437458
get signal() {
438459
this.#signal ??= new AbortSignal(kDontThrowSymbol);
460+
439461
return this.#signal;
440462
}
441463

test/parallel/test-abortsignal-drop-settled-signals.mjs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,41 @@ function runShortLivedSourceSignal(limit, done) {
6464
run(1);
6565
};
6666

67+
function runWithOrphanListeners(limit, done) {
68+
let composedSignalRef;
69+
const composedSignalRefs = [];
70+
const handler = () => { };
71+
72+
function run(iteration) {
73+
const ac = new AbortController();
74+
if (iteration > limit) {
75+
setImmediate(() => {
76+
global.gc();
77+
setImmediate(() => {
78+
global.gc();
79+
80+
done(composedSignalRefs);
81+
});
82+
});
83+
return;
84+
}
85+
86+
composedSignalRef = new WeakRef(AbortSignal.any([ac.signal]));
87+
composedSignalRef.deref().addEventListener('abort', handler);
88+
89+
const otherComposedSignalRef = new WeakRef(AbortSignal.any([composedSignalRef.deref()]));
90+
otherComposedSignalRef.deref().addEventListener('abort', handler);
91+
92+
composedSignalRefs.push(composedSignalRef, otherComposedSignalRef);
93+
94+
setImmediate(() => {
95+
run(iteration + 1);
96+
});
97+
}
98+
99+
run(1);
100+
}
101+
67102
const limit = 10_000;
68103

69104
describe('when there is a long-lived signal', () => {
@@ -120,3 +155,23 @@ it('drops settled dependant signals when signal is composite', (t, done) => {
120155
});
121156
});
122157
});
158+
159+
it('drops settled signals even when there are listeners', (t, done) => {
160+
runWithOrphanListeners(limit, (signalRefs) => {
161+
setImmediate(() => {
162+
global.gc();
163+
setImmediate(() => {
164+
global.gc(); // One more call needed to clean up the deeper composed signals
165+
setImmediate(() => {
166+
global.gc(); // One more call needed to clean up the deeper composed signals
167+
168+
const unGCedSignals = [...signalRefs].filter((ref) => ref.deref());
169+
170+
t.assert.strictEqual(unGCedSignals.length, 0);
171+
172+
done();
173+
});
174+
});
175+
});
176+
});
177+
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy