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