@@ -41,7 +41,7 @@ const {
41
41
} = require ( 'internal/errors' ) ;
42
42
43
43
const {
44
- validateAbortSignal ,
44
+ validateAbortSignalArray ,
45
45
validateObject,
46
46
validateUint32,
47
47
} = require ( 'internal/validators' ) ;
@@ -54,6 +54,7 @@ const {
54
54
clearTimeout,
55
55
setTimeout,
56
56
} = require ( 'timers' ) ;
57
+ const assert = require ( 'internal/assert' ) ;
57
58
58
59
const {
59
60
messaging_deserialize_symbol : kDeserialize ,
@@ -80,13 +81,16 @@ function lazyMakeTransferable(obj) {
80
81
}
81
82
82
83
const clearTimeoutRegistry = new SafeFinalizationRegistry ( clearTimeout ) ;
83
- const timeOutSignals = new SafeSet ( ) ;
84
+ const gcPersistentSignals = new SafeSet ( ) ;
84
85
85
86
const kAborted = Symbol ( 'kAborted' ) ;
86
87
const kReason = Symbol ( 'kReason' ) ;
87
88
const kCloneData = Symbol ( 'kCloneData' ) ;
88
89
const kTimeout = Symbol ( 'kTimeout' ) ;
89
90
const kMakeTransferable = Symbol ( 'kMakeTransferable' ) ;
91
+ const kComposite = Symbol ( 'kComposite' ) ;
92
+ const kSourceSignals = Symbol ( 'kSourceSignals' ) ;
93
+ const kDependantSignals = Symbol ( 'kDependantSignals' ) ;
90
94
91
95
function customInspect ( self , obj , depth , options ) {
92
96
if ( depth < 0 )
@@ -116,7 +120,7 @@ function setWeakAbortSignalTimeout(weakRef, delay) {
116
120
const timeout = setTimeout ( ( ) => {
117
121
const signal = weakRef . deref ( ) ;
118
122
if ( signal !== undefined ) {
119
- timeOutSignals . delete ( signal ) ;
123
+ gcPersistentSignals . delete ( signal ) ;
120
124
abortSignal (
121
125
signal ,
122
126
new DOMException (
@@ -185,25 +189,68 @@ class AbortSignal extends EventTarget {
185
189
return signal ;
186
190
}
187
191
192
+ /**
193
+ * @param {AbortSignal[] } signals
194
+ * @returns {AbortSignal }
195
+ */
196
+ static any ( signals ) {
197
+ validateAbortSignalArray ( signals , 'signals' ) ;
198
+ const resultSignal = createAbortSignal ( { composite : true } ) ;
199
+ const resultSignalWeakRef = new WeakRef ( resultSignal ) ;
200
+ for ( const signal of signals ) {
201
+ if ( signal . aborted ) {
202
+ abortSignal ( resultSignal , signal . reason ) ;
203
+ return resultSignal ;
204
+ }
205
+ resultSignal [ kSourceSignals ] ??= new SafeSet ( ) ;
206
+ signal [ kDependantSignals ] ??= new SafeSet ( ) ;
207
+ if ( ! signal [ kComposite ] ) {
208
+ resultSignal [ kSourceSignals ] . add ( new WeakRef ( signal ) ) ;
209
+ signal [ kDependantSignals ] . add ( resultSignalWeakRef ) ;
210
+ } else {
211
+ if ( ! signal [ kSourceSignals ] ) {
212
+ continue ;
213
+ }
214
+ for ( const sourceSignal of signal [ kSourceSignals ] ) {
215
+ const sourceSignalRef = sourceSignal . deref ( ) ;
216
+ if ( ! sourceSignalRef ) {
217
+ continue ;
218
+ }
219
+ assert ( ! sourceSignalRef . aborted ) ;
220
+ assert ( ! sourceSignalRef [ kComposite ] ) ;
221
+
222
+ if ( resultSignal [ kSourceSignals ] . has ( sourceSignal ) ) {
223
+ continue ;
224
+ }
225
+ resultSignal [ kSourceSignals ] . add ( sourceSignal ) ;
226
+ sourceSignalRef [ kDependantSignals ] . add ( resultSignalWeakRef ) ;
227
+ }
228
+ }
229
+ }
230
+ return resultSignal ;
231
+ }
232
+
188
233
[ kNewListener ] ( size , type , listener , once , capture , passive , weak ) {
189
234
super [ kNewListener ] ( size , type , listener , once , capture , passive , weak ) ;
190
- if ( this [ kTimeout ] &&
235
+ const isTimeoutOrNonEmptyCompositeSignal = this [ kTimeout ] || ( this [ kComposite ] && this [ kSourceSignals ] ?. size ) ;
236
+ if ( isTimeoutOrNonEmptyCompositeSignal &&
191
237
type === 'abort' &&
192
238
! this . aborted &&
193
239
! weak &&
194
240
size === 1 ) {
195
- // If this is a timeout signal, and we're adding a non-weak abort
241
+ // If this is a timeout signal, or a non-empty composite signal, and we're adding a non-weak abort
196
242
// listener, then we don't want it to be gc'd while the listener
197
243
// is attached and the timer still hasn't fired. So, we retain a
198
244
// strong ref that is held for as long as the listener is registered.
199
- timeOutSignals . add ( this ) ;
245
+ gcPersistentSignals . add ( this ) ;
200
246
}
201
247
}
202
248
203
249
[ kRemoveListener ] ( size , type , listener , capture ) {
204
250
super [ kRemoveListener ] ( size , type , listener , capture ) ;
205
- if ( this [ kTimeout ] && type === 'abort' && size === 0 ) {
206
- timeOutSignals . delete ( this ) ;
251
+ const isTimeoutOrNonEmptyCompositeSignal = this [ kTimeout ] || ( this [ kComposite ] && this [ kSourceSignals ] ?. size ) ;
252
+ if ( isTimeoutOrNonEmptyCompositeSignal && type === 'abort' && size === 0 ) {
253
+ gcPersistentSignals . delete ( this ) ;
207
254
}
208
255
}
209
256
@@ -287,7 +334,8 @@ defineEventHandler(AbortSignal.prototype, 'abort');
287
334
* @param {{
288
335
* aborted? : boolean,
289
336
* reason? : any,
290
- * transferable? : boolean
337
+ * transferable? : boolean,
338
+ * composite? : boolean,
291
339
* }} [init]
292
340
* @returns {AbortSignal }
293
341
*/
@@ -296,11 +344,13 @@ function createAbortSignal(init = kEmptyObject) {
296
344
aborted = false ,
297
345
reason = undefined ,
298
346
transferable = false ,
347
+ composite = false ,
299
348
} = init ;
300
349
const signal = new EventTarget ( ) ;
301
350
ObjectSetPrototypeOf ( signal , AbortSignal . prototype ) ;
302
351
signal [ kAborted ] = aborted ;
303
352
signal [ kReason ] = reason ;
353
+ signal [ kComposite ] = composite ;
304
354
return transferable ? lazyMakeTransferable ( signal ) : signal ;
305
355
}
306
356
@@ -312,6 +362,11 @@ function abortSignal(signal, reason) {
312
362
[ kTrustEvent ] : true ,
313
363
} ) ;
314
364
signal . dispatchEvent ( event ) ;
365
+ signal [ kDependantSignals ] ?. forEach ( s => {
366
+ const signalRef = s . deref ( ) ;
367
+ if ( ! signalRef ) return ;
368
+ abortSignal ( signalRef , reason ) ;
369
+ } ) ;
315
370
}
316
371
317
372
class AbortController {
0 commit comments