@@ -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,68 @@ 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
+ 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
+
188
234
[ kNewListener ] ( size , type , listener , once , capture , passive , weak ) {
189
235
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
196
243
// listener, then we don't want it to be gc'd while the listener
197
244
// is attached and the timer still hasn't fired. So, we retain a
198
245
// strong ref that is held for as long as the listener is registered.
199
- timeOutSignals . add ( this ) ;
246
+ gcPersistentSignals . add ( this ) ;
200
247
}
201
248
}
202
249
203
250
[ kRemoveListener ] ( size , type , listener , capture ) {
204
251
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 ) ;
207
255
}
208
256
}
209
257
@@ -287,7 +335,8 @@ defineEventHandler(AbortSignal.prototype, 'abort');
287
335
* @param {{
288
336
* aborted? : boolean,
289
337
* reason? : any,
290
- * transferable? : boolean
338
+ * transferable? : boolean,
339
+ * composite? : boolean,
291
340
* }} [init]
292
341
* @returns {AbortSignal }
293
342
*/
@@ -296,11 +345,13 @@ function createAbortSignal(init = kEmptyObject) {
296
345
aborted = false ,
297
346
reason = undefined ,
298
347
transferable = false ,
348
+ composite = false ,
299
349
} = init ;
300
350
const signal = new EventTarget ( ) ;
301
351
ObjectSetPrototypeOf ( signal , AbortSignal . prototype ) ;
302
352
signal [ kAborted ] = aborted ;
303
353
signal [ kReason ] = reason ;
354
+ signal [ kComposite ] = composite ;
304
355
return transferable ? lazyMakeTransferable ( signal ) : signal ;
305
356
}
306
357
@@ -312,6 +363,10 @@ function abortSignal(signal, reason) {
312
363
[ kTrustEvent ] : true ,
313
364
} ) ;
314
365
signal . dispatchEvent ( event ) ;
366
+ signal [ kDependantSignals ] ?. forEach ( ( s ) => {
367
+ const signalRef = s . deref ( ) ;
368
+ if ( signalRef ) abortSignal ( signalRef , reason ) ;
369
+ } ) ;
315
370
}
316
371
317
372
// TODO(joyeecheung): use private fields and we'll get invalid access
0 commit comments