| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 332088f commit ada3ce8
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -85,17 +85,16 @@ function lazyMessageChannel() { | |||
| 85 | 85 | } | |
| 86 | 86 | ||
| 87 | 87 | const clearTimeoutRegistry = new SafeFinalizationRegistry(clearTimeout); | |
| 88 | - const dependantSignalsCleanupRegistry = new SafeFinalizationRegistry((signalWeakRef) => { | ||
| 89 | - const signal = signalWeakRef.deref(); | ||
| 90 | - if (signal === undefined) { | ||
| 91 | - return; | ||
| 92 | - } | ||
| 93 | - signal[kDependantSignals].forEach((ref) => { | ||
| 94 | - if (ref.deref() === undefined) { | ||
| 95 | - signal[kDependantSignals].delete(ref); | ||
| 88 | + const dependantSignalsCleanupRegistry = new SafeFinalizationRegistry( | ||
| 89 | + ({ sourceSignalRef, dependantSignalRef, sourceSignalsCleanupToken }) => { | ||
| 90 | + sourceSignalsCleanupRegistry.unregister(sourceSignalsCleanupToken); | ||
| 91 | + | ||
| 92 | + const sourceSignal = sourceSignalRef.deref(); | ||
| 93 | + if (sourceSignal === undefined) { | ||
| 94 | + return; | ||
| 96 | 95 | } | |
| 96 | + sourceSignal[kDependantSignals].delete(dependantSignalRef); | ||
| 97 | 97 | }); | |
| 98 | - }); | ||
| 99 | 98 | ||
| 100 | 99 | const gcPersistentSignals = new SafeSet(); | |
| 101 | 100 | ||
@@ -117,6 +116,8 @@ const kCloneData = Symbol('kCloneData'); | |||
| 117 | 116 | const kTimeout = Symbol('kTimeout'); | |
| 118 | 117 | const kMakeTransferable = Symbol('kMakeTransferable'); | |
| 119 | 118 | const kComposite = Symbol('kComposite'); | |
| 119 | + const kFollowing = Symbol('kFollowing'); | ||
| 120 | + const kResultSignalWeakRef = Symbol('kResultSignalWeakRef'); | ||
| 120 | 121 | const kSourceSignals = Symbol('kSourceSignals'); | |
| 121 | 122 | const kDependantSignals = Symbol('kDependantSignals'); | |
| 122 | 123 | ||
@@ -136,6 +137,60 @@ function validateThisAbortSignal(obj) { | |||
| 136 | 137 | throw new ERR_INVALID_THIS('AbortSignal'); | |
| 137 | 138 | } | |
| 138 | 139 | ||
| 140 | + function refreshCompositeSignal(signal) { | ||
| 141 | + if (!signal[kComposite] || signal[kAborted] || !signal[kSourceSignals]?.size) { | ||
| 142 | + return; | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + for (const sourceSignalWeakRef of signal[kSourceSignals]) { | ||
| 146 | + const sourceSignal = sourceSignalWeakRef.deref(); | ||
| 147 | + if (sourceSignal === undefined) { | ||
| 148 | + signal[kSourceSignals].delete(sourceSignalWeakRef); | ||
| 149 | + continue; | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + if (sourceSignal.aborted) { | ||
| 153 | + abortSignal(signal, sourceSignal.reason); | ||
| 154 | + return; | ||
| 155 | + } | ||
| 156 | + } | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + function followCompositeSignal(signal) { | ||
| 160 | + if (signal[kFollowing] || signal[kAborted] || !signal[kSourceSignals]?.size) { | ||
| 161 | + return; | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + const resultSignalWeakRef = signal[kResultSignalWeakRef] ??= new SafeWeakRef(signal); | ||
| 165 | + | ||
| 166 | + for (const sourceSignalWeakRef of signal[kSourceSignals]) { | ||
| 167 | + const sourceSignal = sourceSignalWeakRef.deref(); | ||
| 168 | + if (sourceSignal === undefined) { | ||
| 169 | + signal[kSourceSignals].delete(sourceSignalWeakRef); | ||
| 170 | + continue; | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + if (sourceSignal.aborted) { | ||
| 174 | + abortSignal(signal, sourceSignal.reason); | ||
| 175 | + return; | ||
| 176 | + } | ||
| 177 | + | ||
| 178 | + sourceSignal[kDependantSignals] ??= new SafeSet(); | ||
| 179 | + sourceSignal[kDependantSignals].add(resultSignalWeakRef); | ||
| 180 | + dependantSignalsCleanupRegistry.register(signal, { | ||
| 181 | + sourceSignalRef: sourceSignalWeakRef, | ||
| 182 | + dependantSignalRef: resultSignalWeakRef, | ||
| 183 | + sourceSignalsCleanupToken: sourceSignalWeakRef, | ||
| 184 | + }); | ||
| 185 | + sourceSignalsCleanupRegistry.register(sourceSignal, { | ||
| 186 | + sourceSignalRef: sourceSignalWeakRef, | ||
| 187 | + composedSignalRef: resultSignalWeakRef, | ||
| 188 | + }, sourceSignalWeakRef); | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + signal[kFollowing] = true; | ||
| 192 | + } | ||
| 193 | + | ||
| 139 | 194 | // Because the AbortSignal timeout cannot be canceled, we don't want the | |
| 140 | 195 | // presence of the timer alone to keep the AbortSignal from being garbage | |
| 141 | 196 | // collected if it otherwise no longer accessible. We also don't want the | |
@@ -148,6 +203,7 @@ function setWeakAbortSignalTimeout(weakRef, delay) { | |||
| 148 | 203 | const timeout = setTimeout(() => { | |
| 149 | 204 | const signal = weakRef.deref(); | |
| 150 | 205 | if (signal !== undefined) { | |
| 206 | + clearTimeoutRegistry.unregister(signal); | ||
| 151 | 207 | gcPersistentSignals.delete(signal); | |
| 152 | 208 | abortSignal( | |
| 153 | 209 | signal, | |
@@ -198,6 +254,7 @@ class AbortSignal extends EventTarget { | |||
| 198 | 254 | */ | |
| 199 | 255 | get aborted() { | |
| 200 | 256 | validateThisAbortSignal(this); | |
| 257 | + refreshCompositeSignal(this); | ||
| 201 | 258 | return !!this[kAborted]; | |
| 202 | 259 | } | |
| 203 | 260 | ||
@@ -206,11 +263,13 @@ class AbortSignal extends EventTarget { | |||
| 206 | 263 | */ | |
| 207 | 264 | get reason() { | |
| 208 | 265 | validateThisAbortSignal(this); | |
| 266 | + refreshCompositeSignal(this); | ||
| 209 | 267 | return this[kReason]; | |
| 210 | 268 | } | |
| 211 | 269 | ||
| 212 | 270 | throwIfAborted() { | |
| 213 | 271 | validateThisAbortSignal(this); | |
| 272 | + refreshCompositeSignal(this); | ||
| 214 | 273 | if (this[kAborted]) { | |
| 215 | 274 | throw this[kReason]; | |
| 216 | 275 | } | |
@@ -241,7 +300,8 @@ class AbortSignal extends EventTarget { | |||
| 241 | 300 | signal[kTimeout] = true; | |
| 242 | 301 | clearTimeoutRegistry.register( | |
| 243 | 302 | signal, | |
| 244 | - setWeakAbortSignalTimeout(new SafeWeakRef(signal), delay)); | ||
| 303 | + setWeakAbortSignalTimeout(new SafeWeakRef(signal), delay), | ||
| 304 | + signal); | ||
| 245 | 305 | return signal; | |
| 246 | 306 | } | |
| 247 | 307 | ||
@@ -260,7 +320,6 @@ class AbortSignal extends EventTarget { | |||
| 260 | 320 | return resultSignal; | |
| 261 | 321 | } | |
| 262 | 322 | ||
| 263 | - const resultSignalWeakRef = new SafeWeakRef(resultSignal); | ||
| 264 | 323 | resultSignal[kSourceSignals] = new SafeSet(); | |
| 265 | 324 | ||
| 266 | 325 | // Track if we have any timeout signals | |
@@ -283,51 +342,51 @@ class AbortSignal extends EventTarget { | |||
| 283 | 342 | return resultSignal; | |
| 284 | 343 | } | |
| 285 | 344 | ||
| 286 | - signal[kDependantSignals] ??= new SafeSet(); | ||
| 287 | 345 | if (!signal[kComposite]) { | |
| 288 | 346 | const signalWeakRef = new SafeWeakRef(signal); | |
| 289 | 347 | resultSignal[kSourceSignals].add(signalWeakRef); | |
| 290 | - signal[kDependantSignals].add(resultSignalWeakRef); | ||
| 291 | - dependantSignalsCleanupRegistry.register(resultSignal, signalWeakRef); | ||
| 292 | - sourceSignalsCleanupRegistry.register(signal, { | ||
| 293 | - sourceSignalRef: signalWeakRef, | ||
| 294 | - composedSignalRef: resultSignalWeakRef, | ||
| 295 | - }); | ||
| 296 | 348 | } else if (!signal[kSourceSignals]) { | |
| 297 | 349 | continue; | |
| 298 | 350 | } else { | |
| 351 | + refreshCompositeSignal(signal); | ||
| 352 | + if (signal.aborted) { | ||
| 353 | + abortSignal(resultSignal, signal.reason); | ||
| 354 | + return resultSignal; | ||
| 355 | + } | ||
| 299 | 356 | for (const sourceSignalWeakRef of signal[kSourceSignals]) { | |
| 300 | 357 | const sourceSignal = sourceSignalWeakRef.deref(); | |
| 301 | 358 | if (!sourceSignal) { | |
| 302 | 359 | continue; | |
| 303 | 360 | } | |
| 304 | - assert(!sourceSignal.aborted); | ||
| 305 | 361 | assert(!sourceSignal[kComposite]); | |
| 306 | 362 | ||
| 363 | + if (sourceSignal.aborted) { | ||
| 364 | + abortSignal(resultSignal, sourceSignal.reason); | ||
| 365 | + return resultSignal; | ||
| 366 | + } | ||
| 367 | + | ||
| 307 | 368 | if (resultSignal[kSourceSignals].has(sourceSignalWeakRef)) { | |
| 308 | 369 | continue; | |
| 309 | 370 | } | |
| 310 | 371 | resultSignal[kSourceSignals].add(sourceSignalWeakRef); | |
| 311 | - sourceSignal[kDependantSignals].add(resultSignalWeakRef); | ||
| 312 | - dependantSignalsCleanupRegistry.register(resultSignal, sourceSignalWeakRef); | ||
| 313 | - sourceSignalsCleanupRegistry.register(signal, { | ||
| 314 | - sourceSignalRef: sourceSignalWeakRef, | ||
| 315 | - composedSignalRef: resultSignalWeakRef, | ||
| 316 | - }); | ||
| 317 | 372 | } | |
| 318 | 373 | } | |
| 319 | 374 | } | |
| 320 | 375 | ||
| 321 | - // If we have any timeout signals, add the composite signal to gcPersistentSignals | ||
| 322 | 376 | if (hasTimeoutSignals && resultSignal[kSourceSignals].size > 0) { | |
| 323 | - gcPersistentSignals.add(resultSignal); | ||
| 377 | + resultSignal[kTimeout] = true; | ||
| 324 | 378 | } | |
| 325 | 379 | ||
| 326 | 380 | return resultSignal; | |
| 327 | 381 | } | |
| 328 | 382 | ||
| 329 | 383 | [kNewListener](size, type, listener, once, capture, passive, weak) { | |
| 330 | 384 | super[kNewListener](size, type, listener, once, capture, passive, weak); | |
| 385 | + | ||
| 386 | + if (this[kComposite] && type === 'abort' && !this.aborted && size === 1) { | ||
| 387 | + followCompositeSignal(this); | ||
| 388 | + } | ||
| 389 | + | ||
| 331 | 390 | const isTimeoutOrNonEmptyCompositeSignal = this[kTimeout] || (this[kComposite] && this[kSourceSignals]?.size); | |
| 332 | 391 | if (isTimeoutOrNonEmptyCompositeSignal && | |
| 333 | 392 | type === 'abort' && | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,7 +23,9 @@ function makeSubsequentCalls(limit, done, holdReferences = false) { | |||
| 23 | 23 | } | |
| 24 | 24 | ||
| 25 | 25 | if (holdReferences) { | |
| 26 | - retainedSignals.push(AbortSignal.any([ac.signal])); | ||
| 26 | + const signal = AbortSignal.any([ac.signal]); | ||
| 27 | + signal.addEventListener('abort', handler); | ||
| 28 | + retainedSignals.push(signal); | ||
| 27 | 29 | } else { | |
| 28 | 30 | // Using a WeakRef to avoid retaining information that will interfere with the test | |
| 29 | 31 | signalRef = new WeakRef(AbortSignal.any([ac.signal])); | |
@@ -119,6 +121,27 @@ describe('when there is a long-lived signal', () => { | |||
| 119 | 121 | done(); | |
| 120 | 122 | }, true); | |
| 121 | 123 | }); | |
| 124 | + | ||
| 125 | + it('does not keep retained dependent signals without listeners', (t, done) => { | ||
| 126 | + const ac = new AbortController(); | ||
| 127 | + const retainedSignals = []; | ||
| 128 | + const kDependantSignals = Object.getOwnPropertySymbols(ac.signal).find( | ||
| 129 | + (s) => s.toString() === 'Symbol(kDependantSignals)' | ||
| 130 | + ); | ||
| 131 | + | ||
| 132 | + function run(iteration) { | ||
| 133 | + if (iteration > limit) { | ||
| 134 | + t.assert.strictEqual(ac.signal[kDependantSignals]?.size ?? 0, 0); | ||
| 135 | + done(); | ||
| 136 | + return; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + retainedSignals.push(AbortSignal.any([ac.signal])); | ||
| 140 | + setImmediate(() => run(iteration + 1)); | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + run(1); | ||
| 144 | + }); | ||
| 122 | 145 | }); | |
| 123 | 146 | ||
| 124 | 147 | it('does not prevent source signal from being GCed if it is short-lived', (t, done) => { | |
@@ -134,10 +157,13 @@ it('does not prevent source signal from being GCed if it is short-lived', (t, do | |||
| 134 | 157 | ||
| 135 | 158 | it('drops settled dependent signals when signal is composite', (t, done) => { | |
| 136 | 159 | const controllers = Array.from({ length: 2 }, () => new AbortController()); | |
| 160 | + const handler = () => {}; | ||
| 137 | 161 | ||
| 138 | 162 | // Using WeakRefs to avoid this test to retain information that will make the test fail | |
| 139 | 163 | const composedSignal1 = new WeakRef(AbortSignal.any([controllers[0].signal])); | |
| 140 | 164 | const composedSignalRef = new WeakRef(AbortSignal.any([composedSignal1.deref(), controllers[1].signal])); | |
| 165 | + composedSignal1.deref().addEventListener('abort', handler); | ||
| 166 | + composedSignalRef.deref().addEventListener('abort', handler); | ||
| 141 | 167 | ||
| 142 | 168 | const kDependantSignals = Object.getOwnPropertySymbols(controllers[0].signal).find( | |
| 143 | 169 | (s) => s.toString() === 'Symbol(kDependantSignals)' | |
@@ -147,6 +173,9 @@ it('drops settled dependent signals when signal is composite', (t, done) => { | |||
| 147 | 173 | t.assert.strictEqual(controllers[1].signal[kDependantSignals].size, 1); | |
| 148 | 174 | ||
| 149 | 175 | setImmediate(mustCall(() => { | |
| 176 | + composedSignal1.deref()?.removeEventListener('abort', handler); | ||
| 177 | + composedSignalRef.deref()?.removeEventListener('abort', handler); | ||
| 178 | + | ||
| 150 | 179 | globalThis.gc({ execution: 'async' }).then(async () => { | |
| 151 | 180 | await gcUntil('all signals are GCed', () => { | |
| 152 | 181 | const totalDependantSignals = Math.max( | |
| Back | FazBrowse Home | New Git URL |
0 commit comments