Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit efaa073

Browse filesBrowse files
atlowChemidanielleadams
authored andcommitted
lib: implement AbortSignal.any()
PR-URL: #47821 Backport-PR-URL: #48800 Fixes: #47811 Refs: whatwg/dom#1152 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
1 parent d002f9b commit efaa073
Copy full SHA for efaa073

File tree

Expand file treeCollapse file tree

10 files changed

+410
-19
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

10 files changed

+410
-19
lines changed
Open diff view settings
Collapse file

‎doc/api/globals.md‎

Copy file name to clipboardExpand all lines: doc/api/globals.md
+13Lines changed: 13 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ added:
121121

122122
Returns a new `AbortSignal` which will be aborted in `delay` milliseconds.
123123

124+
#### Static method: `AbortSignal.any(signals)`
125+
126+
<!-- YAML
127+
added: REPLACEME
128+
-->
129+
130+
* `signals` {AbortSignal\[]} The `AbortSignal`s of which to compose a new `AbortSignal`.
131+
132+
Returns a new `AbortSignal` which will be aborted if any of the provided
133+
signals are aborted. Its [`abortSignal.reason`][] will be set to whichever
134+
one of the `signals` caused it to be aborted.
135+
124136
#### Event: `'abort'`
125137

126138
<!-- YAML
@@ -906,6 +918,7 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
906918
[`WritableStream`]: webstreams.md#class-writablestream
907919
[`__dirname`]: modules.md#__dirname
908920
[`__filename`]: modules.md#__filename
921+
[`abortSignal.reason`]: #abortsignalreason
909922
[`buffer.atob()`]: buffer.md#bufferatobdata
910923
[`buffer.btoa()`]: buffer.md#bufferbtoadata
911924
[`clearImmediate`]: timers.md#clearimmediateimmediate
Collapse file

‎lib/internal/abort_controller.js‎

Copy file name to clipboardExpand all lines: lib/internal/abort_controller.js
+67-12Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const {
4242

4343
const {
4444
validateAbortSignal,
45+
validateAbortSignalArray,
4546
validateObject,
4647
validateUint32,
4748
} = require('internal/validators');
@@ -54,6 +55,7 @@ const {
5455
clearTimeout,
5556
setTimeout,
5657
} = require('timers');
58+
const assert = require('internal/assert');
5759

5860
const {
5961
messaging_deserialize_symbol: kDeserialize,
@@ -80,13 +82,16 @@ function lazyMakeTransferable(obj) {
8082
}
8183

8284
const clearTimeoutRegistry = new SafeFinalizationRegistry(clearTimeout);
83-
const timeOutSignals = new SafeSet();
85+
const gcPersistentSignals = new SafeSet();
8486

8587
const kAborted = Symbol('kAborted');
8688
const kReason = Symbol('kReason');
8789
const kCloneData = Symbol('kCloneData');
8890
const kTimeout = Symbol('kTimeout');
8991
const kMakeTransferable = Symbol('kMakeTransferable');
92+
const kComposite = Symbol('kComposite');
93+
const kSourceSignals = Symbol('kSourceSignals');
94+
const kDependantSignals = Symbol('kDependantSignals');
9095

9196
function customInspect(self, obj, depth, options) {
9297
if (depth < 0)
@@ -116,7 +121,7 @@ function setWeakAbortSignalTimeout(weakRef, delay) {
116121
const timeout = setTimeout(() => {
117122
const signal = weakRef.deref();
118123
if (signal !== undefined) {
119-
timeOutSignals.delete(signal);
124+
gcPersistentSignals.delete(signal);
120125
abortSignal(
121126
signal,
122127
new DOMException(
@@ -185,25 +190,68 @@ class AbortSignal extends EventTarget {
185190
return signal;
186191
}
187192

193+
/**
194+
* @param {AbortSignal[]} signals
195+
* @returns {AbortSignal}
196+
*/
197+
static any(signals) {
198+
validateAbortSignalArray(signals, 'signals');
199+
const resultSignal = createAbortSignal({ composite: true });
200+
const resultSignalWeakRef = new WeakRef(resultSignal);
201+
resultSignal[kSourceSignals] = new SafeSet();
202+
for (let i = 0; i < signals.length; i++) {
203+
const signal = signals[i];
204+
if (signal.aborted) {
205+
abortSignal(resultSignal, signal.reason);
206+
return resultSignal;
207+
}
208+
signal[kDependantSignals] ??= new SafeSet();
209+
if (!signal[kComposite]) {
210+
resultSignal[kSourceSignals].add(new WeakRef(signal));
211+
signal[kDependantSignals].add(resultSignalWeakRef);
212+
} else if (!signal[kSourceSignals]) {
213+
continue;
214+
} else {
215+
for (const sourceSignal of signal[kSourceSignals]) {
216+
const sourceSignalRef = sourceSignal.deref();
217+
if (!sourceSignalRef) {
218+
continue;
219+
}
220+
assert(!sourceSignalRef.aborted);
221+
assert(!sourceSignalRef[kComposite]);
222+
223+
if (resultSignal[kSourceSignals].has(sourceSignal)) {
224+
continue;
225+
}
226+
resultSignal[kSourceSignals].add(sourceSignal);
227+
sourceSignalRef[kDependantSignals].add(resultSignalWeakRef);
228+
}
229+
}
230+
}
231+
return resultSignal;
232+
}
233+
188234
[kNewListener](size, type, listener, once, capture, passive, weak) {
189235
super[kNewListener](size, type, listener, once, capture, passive, weak);
190-
if (this[kTimeout] &&
191-
type === 'abort' &&
192-
!this.aborted &&
193-
!weak &&
194-
size === 1) {
195-
// If this is a timeout signal, and we're adding a non-weak abort
236+
const isTimeoutOrNonEmptyCompositeSignal = this[kTimeout] || (this[kComposite] && this[kSourceSignals]?.size);
237+
if (isTimeoutOrNonEmptyCompositeSignal &&
238+
type === 'abort' &&
239+
!this.aborted &&
240+
!weak &&
241+
size === 1) {
242+
// If this is a timeout signal, or a non-empty composite signal, and we're adding a non-weak abort
196243
// listener, then we don't want it to be gc'd while the listener
197244
// is attached and the timer still hasn't fired. So, we retain a
198245
// strong ref that is held for as long as the listener is registered.
199-
timeOutSignals.add(this);
246+
gcPersistentSignals.add(this);
200247
}
201248
}
202249

203250
[kRemoveListener](size, type, listener, capture) {
204251
super[kRemoveListener](size, type, listener, capture);
205-
if (this[kTimeout] && type === 'abort' && size === 0) {
206-
timeOutSignals.delete(this);
252+
const isTimeoutOrNonEmptyCompositeSignal = this[kTimeout] || (this[kComposite] && this[kSourceSignals]?.size);
253+
if (isTimeoutOrNonEmptyCompositeSignal && type === 'abort' && size === 0) {
254+
gcPersistentSignals.delete(this);
207255
}
208256
}
209257

@@ -287,7 +335,8 @@ defineEventHandler(AbortSignal.prototype, 'abort');
287335
* @param {{
288336
* aborted? : boolean,
289337
* reason? : any,
290-
* transferable? : boolean
338+
* transferable? : boolean,
339+
* composite? : boolean,
291340
* }} [init]
292341
* @returns {AbortSignal}
293342
*/
@@ -296,11 +345,13 @@ function createAbortSignal(init = kEmptyObject) {
296345
aborted = false,
297346
reason = undefined,
298347
transferable = false,
348+
composite = false,
299349
} = init;
300350
const signal = new EventTarget();
301351
ObjectSetPrototypeOf(signal, AbortSignal.prototype);
302352
signal[kAborted] = aborted;
303353
signal[kReason] = reason;
354+
signal[kComposite] = composite;
304355
return transferable ? lazyMakeTransferable(signal) : signal;
305356
}
306357

@@ -312,6 +363,10 @@ function abortSignal(signal, reason) {
312363
[kTrustEvent]: true,
313364
});
314365
signal.dispatchEvent(event);
366+
signal[kDependantSignals]?.forEach((s) => {
367+
const signalRef = s.deref();
368+
if (signalRef) abortSignal(signalRef, reason);
369+
});
315370
}
316371

317372
// TODO(joyeecheung): use private fields and we'll get invalid access
Collapse file

‎lib/internal/validators.js‎

Copy file name to clipboardExpand all lines: lib/internal/validators.js
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,26 @@ function validateBooleanArray(value, name) {
324324
}
325325
}
326326

327+
/**
328+
* @callback validateAbortSignalArray
329+
* @param {*} value
330+
* @param {string} name
331+
* @returns {asserts value is AbortSignal[]}
332+
*/
333+
334+
/** @type {validateAbortSignalArray} */
335+
function validateAbortSignalArray(value, name) {
336+
validateArray(value, name);
337+
for (let i = 0; i < value.length; i++) {
338+
const signal = value[i];
339+
const indexedName = `${name}[${i}]`;
340+
if (signal == null) {
341+
throw new ERR_INVALID_ARG_TYPE(indexedName, 'AbortSignal', signal);
342+
}
343+
validateAbortSignal(signal, indexedName);
344+
}
345+
}
346+
327347
/**
328348
* @param {*} signal
329349
* @param {string} [name='signal']
@@ -528,6 +548,7 @@ module.exports = {
528548
validateArray,
529549
validateStringArray,
530550
validateBooleanArray,
551+
validateAbortSignalArray,
531552
validateBoolean,
532553
validateBuffer,
533554
validateDictionary,
Collapse file

‎test/common/wpt.js‎

Copy file name to clipboardExpand all lines: test/common/wpt.js
+5-5Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ class WPTRunner {
614614

615615
process.on('exit', () => {
616616
for (const spec of this.inProgress) {
617-
this.fail(spec, { name: 'Unknown' }, kIncomplete);
617+
this.fail(spec, { name: 'Incomplete' }, kIncomplete);
618618
}
619619
inspect.defaultOptions.depth = Infinity;
620620
// Sorts the rules to have consistent output
@@ -738,11 +738,11 @@ class WPTRunner {
738738
* @param {object} harnessStatus - The status object returned by WPT harness.
739739
*/
740740
completionCallback(filename, harnessStatus) {
741+
const status = this.getTestStatus(harnessStatus.status);
742+
741743
// Treat it like a test case failure
742-
if (harnessStatus.status === 2) {
743-
const title = this.getTestTitle(filename);
744-
console.log(`---- ${title} ----`);
745-
this.resultCallback(filename, { status: 2, name: 'Unknown' });
744+
if (status === kTimeout) {
745+
this.fail(filename, { name: 'WPT testharness timeout' }, kTimeout);
746746
}
747747
this.inProgress.delete(filename);
748748
// Always force termination of the worker. Some tests allocate resources
Collapse file

‎test/common/wpt/worker.js‎

Copy file name to clipboardExpand all lines: test/common/wpt/worker.js
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,17 @@ add_result_callback((result) => {
4141
});
4242
});
4343

44+
// Keep the event loop alive
45+
const timeout = setTimeout(() => {
46+
parentPort.postMessage({
47+
type: 'completion',
48+
status: { status: 2 },
49+
});
50+
}, 2 ** 31 - 1); // Max timeout is 2^31-1, when overflown the timeout is set to 1.
51+
4452
// eslint-disable-next-line no-undef
4553
add_completion_callback((_, status) => {
54+
clearTimeout(timeout);
4655
parentPort.postMessage({
4756
type: 'completion',
4857
status,
Collapse file

‎test/fixtures/wpt/README.md‎

Copy file name to clipboardExpand all lines: test/fixtures/wpt/README.md
+1-1Lines changed: 1 addition & 1 deletion
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Last update:
1212

1313
- common: https://github.com/web-platform-tests/wpt/tree/03c5072aff/common
1414
- console: https://github.com/web-platform-tests/wpt/tree/767ae35464/console
15-
- dom/abort: https://github.com/web-platform-tests/wpt/tree/8fadb38120/dom/abort
15+
- dom/abort: https://github.com/web-platform-tests/wpt/tree/d1f1ecbd52/dom/abort
1616
- dom/events: https://github.com/web-platform-tests/wpt/tree/ab8999891c/dom/events
1717
- encoding: https://github.com/web-platform-tests/wpt/tree/0c1b9d1622/encoding
1818
- fetch/data-urls/resources: https://github.com/web-platform-tests/wpt/tree/7c79d998ff/fetch/data-urls/resources
Collapse file
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// META: script=./resources/abort-signal-any-tests.js
2+
3+
abortSignalAnySignalOnlyTests(AbortSignal);
4+
abortSignalAnyTests(AbortSignal, AbortController);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.