| 
 | 1 | +// Flags: --expose_gc  | 
 | 2 | +//  | 
 | 3 | +import '../common/index.mjs';  | 
 | 4 | +import { describe, it } from 'node:test';  | 
 | 5 | + | 
 | 6 | +function makeSubsequentCalls(limit, done, holdReferences = false) {  | 
 | 7 | +  let dependantSymbol;  | 
 | 8 | +  let signalRef;  | 
 | 9 | +  const ac = new AbortController();  | 
 | 10 | +  const retainedSignals = [];  | 
 | 11 | +  const handler = () => { };  | 
 | 12 | + | 
 | 13 | +  function run(iteration) {  | 
 | 14 | +    if (iteration > limit) {  | 
 | 15 | +      // This setImmediate is necessary to ensure that in the last iteration the remaining signal is GCed (if not  | 
 | 16 | +      // retained)  | 
 | 17 | +      setImmediate(() => {  | 
 | 18 | +        global.gc();  | 
 | 19 | +        done(ac.signal, dependantSymbol);  | 
 | 20 | +      });  | 
 | 21 | +      return;  | 
 | 22 | +    }  | 
 | 23 | + | 
 | 24 | +    if (holdReferences) {  | 
 | 25 | +      retainedSignals.push(AbortSignal.any([ac.signal]));  | 
 | 26 | +    } else {  | 
 | 27 | +      // Using a WeakRef to avoid retaining information that will interfere with the test  | 
 | 28 | +      signalRef = new WeakRef(AbortSignal.any([ac.signal]));  | 
 | 29 | +      signalRef.deref().addEventListener('abort', handler);  | 
 | 30 | +    }  | 
 | 31 | + | 
 | 32 | +    dependantSymbol ??= Object.getOwnPropertySymbols(ac.signal).find(  | 
 | 33 | +      (s) => s.toString() === 'Symbol(kDependantSignals)'  | 
 | 34 | +    );  | 
 | 35 | + | 
 | 36 | +    setImmediate(() => {  | 
 | 37 | +      // Removing the event listener at some moment in the future  | 
 | 38 | +      // Which will then allow the signal to be GCed  | 
 | 39 | +      signalRef?.deref()?.removeEventListener('abort', handler);  | 
 | 40 | +      run(iteration + 1);  | 
 | 41 | +    });  | 
 | 42 | +  }  | 
 | 43 | + | 
 | 44 | +  run(1);  | 
 | 45 | +};  | 
 | 46 | + | 
 | 47 | +function runShortLivedSourceSignal(limit, done) {  | 
 | 48 | +  const signalRefs = new Set();  | 
 | 49 | + | 
 | 50 | +  function run(iteration) {  | 
 | 51 | +    if (iteration > limit) {  | 
 | 52 | +      global.gc();  | 
 | 53 | +      done(signalRefs);  | 
 | 54 | +      return;  | 
 | 55 | +    }  | 
 | 56 | + | 
 | 57 | +    const ac = new AbortController();  | 
 | 58 | +    signalRefs.add(new WeakRef(ac.signal));  | 
 | 59 | +    AbortSignal.any([ac.signal]);  | 
 | 60 | + | 
 | 61 | +    setImmediate(() => run(iteration + 1));  | 
 | 62 | +  }  | 
 | 63 | + | 
 | 64 | +  run(1);  | 
 | 65 | +};  | 
 | 66 | + | 
 | 67 | +const limit = 10_000;  | 
 | 68 | + | 
 | 69 | +describe('when there is a long-lived signal', () => {  | 
 | 70 | +  it('drops settled dependant signals', (t, done) => {  | 
 | 71 | +    makeSubsequentCalls(limit, (signal, depandantSignalsKey) => {  | 
 | 72 | +      setImmediate(() => {  | 
 | 73 | +        t.assert.strictEqual(signal[depandantSignalsKey].size, 0);  | 
 | 74 | +        done();  | 
 | 75 | +      });  | 
 | 76 | +    });  | 
 | 77 | +  });  | 
 | 78 | + | 
 | 79 | +  it('keeps all active dependant signals', (t, done) => {  | 
 | 80 | +    makeSubsequentCalls(limit, (signal, depandantSignalsKey) => {  | 
 | 81 | +      t.assert.strictEqual(signal[depandantSignalsKey].size, limit);  | 
 | 82 | + | 
 | 83 | +      done();  | 
 | 84 | +    }, true);  | 
 | 85 | +  });  | 
 | 86 | +});  | 
 | 87 | + | 
 | 88 | +it('does not prevent source signal from being GCed if it is short-lived', (t, done) => {  | 
 | 89 | +  runShortLivedSourceSignal(limit, (signalRefs) => {  | 
 | 90 | +    setImmediate(() => {  | 
 | 91 | +      const unGCedSignals = [...signalRefs].filter((ref) => ref.deref());  | 
 | 92 | + | 
 | 93 | +      t.assert.strictEqual(unGCedSignals.length, 0);  | 
 | 94 | +      done();  | 
 | 95 | +    });  | 
 | 96 | +  });  | 
 | 97 | +});  | 
 | 98 | + | 
 | 99 | +it('drops settled dependant signals when signal is composite', (t, done) => {  | 
 | 100 | +  const controllers = Array.from({ length: 2 }, () => new AbortController());  | 
 | 101 | +  const composedSignal1 = AbortSignal.any([controllers[0].signal]);  | 
 | 102 | +  const composedSignalRef = new WeakRef(AbortSignal.any([composedSignal1, controllers[1].signal]));  | 
 | 103 | + | 
 | 104 | +  const kDependantSignals = Object.getOwnPropertySymbols(controllers[0].signal).find(  | 
 | 105 | +    (s) => s.toString() === 'Symbol(kDependantSignals)'  | 
 | 106 | +  );  | 
 | 107 | + | 
 | 108 | +  setImmediate(() => {  | 
 | 109 | +    global.gc();  | 
 | 110 | + | 
 | 111 | +    t.assert.strictEqual(composedSignalRef.deref(), undefined);  | 
 | 112 | +    t.assert.strictEqual(controllers[0].signal[kDependantSignals].size, 2);  | 
 | 113 | +    t.assert.strictEqual(controllers[1].signal[kDependantSignals].size, 1);  | 
 | 114 | + | 
 | 115 | +    setImmediate(() => {  | 
 | 116 | +      t.assert.strictEqual(controllers[0].signal[kDependantSignals].size, 0);  | 
 | 117 | +      t.assert.strictEqual(controllers[1].signal[kDependantSignals].size, 0);  | 
 | 118 | + | 
 | 119 | +      done();  | 
 | 120 | +    });  | 
 | 121 | +  });  | 
 | 122 | +});  | 
0 commit comments