Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fd55d3c

Browse filesBrowse 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
Copy full SHA for fd55d3c

File tree

Expand file treeCollapse file tree

2 files changed

+77
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+77
-0
lines changed
Open diff view settings
Collapse file

‎lib/internal/abort_controller.js‎

Copy file name to clipboardExpand all lines: lib/internal/abort_controller.js
+22Lines 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

Collapse file

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

Copy file name to clipboardExpand all lines: test/parallel/test-abortsignal-drop-settled-signals.mjs
+55Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.