Skip to content

Commit d1719ac

Browse files
committed
lib: propagate aborted state to dependent signals before firing events
1 parent 67357ba commit d1719ac

File tree

2 files changed

+78
-4
lines changed

2 files changed

+78
-4
lines changed

lib/internal/abort_controller.js

+39-4
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
// in https://github.com/mysticatea/abort-controller (MIT license)
55

66
const {
7+
ArrayPrototypePush,
78
ObjectAssign,
89
ObjectDefineProperties,
910
ObjectDefineProperty,
1011
PromiseResolve,
1112
SafeFinalizationRegistry,
1213
SafeSet,
14+
SetPrototypeForEach,
1315
Symbol,
1416
SymbolToStringTag,
1517
WeakRef,
@@ -372,18 +374,51 @@ ObjectDefineProperty(AbortSignal.prototype, SymbolToStringTag, {
372374

373375
defineEventHandler(AbortSignal.prototype, 'abort');
374376

377+
// https://dom.spec.whatwg.org/#dom-abortsignal-abort
375378
function abortSignal(signal, reason) {
379+
// 1. If signal is aborted, then return.
380+
// if (signal[kReason]) signal[kAborted] == true;
376381
if (signal[kAborted]) return;
382+
383+
// 2. Set signal's abort reason to reason if it is given;
384+
// otherwise to a new "AbortError" DOMException.
377385
signal[kAborted] = true;
378386
signal[kReason] = reason;
387+
// 3. Let dependentSignalsToAbort be a new list.
388+
const dependentSignalsToAbort = [];
389+
// 4. For each dependentSignal of signal's dependent signals:
390+
if (signal[kDependantSignals]) {
391+
SetPrototypeForEach(signal[kDependantSignals], (s) => {
392+
const dependentSignal = s.deref();
393+
// 1. If dependentSignal is not aborted, then:
394+
if (dependentSignal && !dependentSignal[kAborted]) {
395+
// 1. Set dependentSignal's abort reason to signal's abort reason.
396+
dependentSignal[kReason] = reason;
397+
dependentSignal[kAborted] = true;
398+
// 2. Append dependentSignal to dependentSignalsToAbort.
399+
ArrayPrototypePush(dependentSignalsToAbort, dependentSignal);
400+
}
401+
});
402+
}
403+
// 5. Run the abort steps for signal
404+
runAbort(signal);
405+
// 6. For each dependentSignal of dependentSignalsToAbort,
406+
// run the abort steps for dependentSignal.
407+
for (let i = 0; i < dependentSignalsToAbort.length; i++) {
408+
const dependentSignal = dependentSignalsToAbort[i];
409+
runAbort(dependentSignal);
410+
};
411+
}
412+
413+
// To run the abort steps for an AbortSignal signal
414+
function runAbort(signal) {
415+
// TODO: 1. For each algorithm of signal's abort algorithms: run algorithm.
416+
// TODO: 2. Empty signal's abort algorithms.
417+
// 3. Fire an event named abort at signal.
379418
const event = new Event('abort', {
380419
[kTrustEvent]: true,
381420
});
382421
signal.dispatchEvent(event);
383-
signal[kDependantSignals]?.forEach((s) => {
384-
const signalRef = s.deref();
385-
if (signalRef) abortSignal(signalRef, reason);
386-
});
387422
}
388423

389424
class AbortController {

test/parallel/test-abortsignal-any.mjs

+39
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,43 @@ describe('AbortSignal.any()', { concurrency: !process.env.TEST_PARALLEL }, () =>
118118
controller.abort();
119119
assert.strictEqual(result, 1);
120120
});
121+
122+
it('marks dependent signals aborted before abort events fire', () => {
123+
const controller = new AbortController();
124+
const signal1 = AbortSignal.any([controller.signal]);
125+
const signal2 = AbortSignal.any([signal1]);
126+
let eventFired = false;
127+
128+
controller.signal.addEventListener('abort', () => {
129+
const signal3 = AbortSignal.any([signal2]);
130+
assert(controller.signal.aborted);
131+
assert(signal1.aborted);
132+
assert(signal2.aborted);
133+
assert(signal3.aborted);
134+
eventFired = true;
135+
});
136+
137+
controller.abort();
138+
assert(eventFired, 'event fired');
139+
});
140+
141+
it('aborts dependent signals correctly for reentrant aborts', () => {
142+
const controller1 = new AbortController();
143+
const controller2 = new AbortController();
144+
const signal = AbortSignal.any([controller1.signal, controller2.signal]);
145+
let count = 0;
146+
147+
controller1.signal.addEventListener('abort', () => {
148+
controller2.abort('reason 2');
149+
});
150+
151+
signal.addEventListener('abort', () => {
152+
count++;
153+
});
154+
155+
controller1.abort('reason 1');
156+
assert.strictEqual(count, 1);
157+
assert(signal.aborted);
158+
assert.strictEqual(signal.reason, 'reason 1');
159+
});
121160
});

0 commit comments

Comments
 (0)