| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 539f5dc commit 06b54fe
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -522,6 +522,13 @@ function abortSignal(signal, reason) { | |||
| 522 | 522 | for (let i = 0; i < dependentSignalsToAbort.length; i++) { | |
| 523 | 523 | const dependentSignal = dependentSignalsToAbort[i]; | |
| 524 | 524 | runAbort(dependentSignal); | |
| 525 | + // A transitively-aborted dependent signal can never abort again, so there | ||
| 526 | + // is no longer any reason to keep it alive. Dropping it from | ||
| 527 | + // gcPersistentSignals lets it be collected, which in turn prunes its | ||
| 528 | + // WeakRef from its sources' kDependantSignals sets. Otherwise, an observed | ||
| 529 | + // composite that follows a long-lived source stays retained forever and | ||
| 530 | + // its entry accumulates in that source's kDependantSignals. | ||
| 531 | + gcPersistentSignals.delete(dependentSignal); | ||
| 525 | 532 | } | |
| 526 | 533 | ||
| 527 | 534 | // Clean up the signal from gcPersistentSignals | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -142,6 +142,37 @@ describe('when there is a long-lived signal', () => { | |||
| 142 | 142 | ||
| 143 | 143 | run(1); | |
| 144 | 144 | }); | |
| 145 | + | ||
| 146 | + it('drops observed dependent signals once they are transitively aborted', async () => { | ||
| 147 | + const longLived = new AbortController(); | ||
| 148 | + const handler = () => {}; | ||
| 149 | + const size = () => { | ||
| 150 | + const sym = Object.getOwnPropertySymbols(longLived.signal).find( | ||
| 151 | + (s) => s.toString() === 'Symbol(kDependantSignals)' | ||
| 152 | + ); | ||
| 153 | + return sym ? longLived.signal[sym].size : 0; | ||
| 154 | + }; | ||
| 155 | + | ||
| 156 | + // Each composite observes the long-lived source and a per-request source, | ||
| 157 | + // then is aborted through the per-request source without ever removing its | ||
| 158 | + // listener. The aborted composites can never fire again, so the long-lived | ||
| 159 | + // source's dependant set must not accumulate them. Using a helper keeps the | ||
| 160 | + // last iteration's signals from lingering on the stack for the assertion. | ||
| 161 | + const createObservedAbortedComposite = () => { | ||
| 162 | + const perReq = new AbortController(); | ||
| 163 | + const composite = AbortSignal.any([perReq.signal, longLived.signal]); | ||
| 164 | + composite.addEventListener('abort', handler); | ||
| 165 | + perReq.abort(); | ||
| 166 | + }; | ||
| 167 | + for (let i = 0; i < limit; i++) { | ||
| 168 | + createObservedAbortedComposite(); | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + await gcUntil( | ||
| 172 | + 'observed dependents are dropped after transitive abort', | ||
| 173 | + () => size() === 0, | ||
| 174 | + ); | ||
| 175 | + }); | ||
| 145 | 176 | }); | |
| 146 | 177 | ||
| 147 | 178 | it('does not prevent source signal from being GCed if it is short-lived', (t, done) => { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments