lib: fix AbortSignal.any() observed-composite leak - #64481
Conversation
|
Hi @geeksilva97 @atlowChemi, wanted to keep this on your radar since you reviewed #62367. This is a direct follow-up: transitively-aborted observed composites stay pinned in |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #64481 +/- ##
==========================================
- Coverage 90.15% 90.14% -0.01%
==========================================
Files 744 744
Lines 242517 242524 +7
Branches 45688 45688
==========================================
- Hits 218642 218635 -7
- Misses 15358 15365 +7
- Partials 8517 8524 +7
🚀 New features to boost your workflow:
|
|
Thanks, @bitpshr |
|
@bitpshr could you try to rebase? I think the Jenkins failure would be fixed by a rebase |
An observed composite signal (one with an `abort` listener) is added to gcPersistentSignals so it stays alive long enough to fire. When one of its sources aborted, the composite was marked aborted but never removed from that set, so it was retained forever and its WeakRef was never pruned from a long-lived source's kDependantSignals. That set therefore grew without bound. Drop each transitively-aborted dependent from gcPersistentSignals, the same way the directly-aborted signal is already removed, so it can be collected and its dependant entries pruned. Fixes: nodejs#64476 Signed-off-by: Paul Bouchon <mail@bitpshr.net>
1204b62 to
157c19a
Compare
|
Rebased onto main, thanks @atlowChemi. |
|
Rebased, but the two red checks are unrelated flakes rather than anything from this change. |
Fixes a memory leak in
AbortSignal.any()where an observed composite that follows a long-lived source keeps accumulating in that source'skDependantSignals, even across a forcedglobal.gc().The retainer is
gcPersistentSignals. When you add anabortlistener, the observed composite is added there so it stays alive long enough to fire. When one of its sources aborts,abortSignal()marks the composite aborted and runs its abort steps, but it never removes the composite fromgcPersistentSignals. That set is only pruned when the listener is explicitly removed or a source is GC'd, so with a long-lived source the aborted composite is retained forever, never collected, and itsWeakRefis never pruned from the source's dependant set.An aborted composite can never fire again, so this drops each transitively-aborted dependent from
gcPersistentSignalsin the abort path, mirroring the removal that already happens for the directly-aborted signal a few lines down. Once it's collectable, the existingFinalizationRegistryprunes its entry from every source it followed.The added test observes a long-lived source across many aborted composites and asserts the dependant set drains to zero. It fails before this change (the set stays at the iteration count) and passes after.
Fixes: #64476