@@ -42,6 +42,7 @@ const {
4242
4343const {
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
5860const {
5961 messaging_deserialize_symbol : kDeserialize ,
@@ -80,13 +82,16 @@ function lazyMakeTransferable(obj) {
8082}
8183
8284const clearTimeoutRegistry = new SafeFinalizationRegistry ( clearTimeout ) ;
83- const timeOutSignals = new SafeSet ( ) ;
85+ const gcPersistentSignals = new SafeSet ( ) ;
8486
8587const kAborted = Symbol ( 'kAborted' ) ;
8688const kReason = Symbol ( 'kReason' ) ;
8789const kCloneData = Symbol ( 'kCloneData' ) ;
8890const kTimeout = Symbol ( 'kTimeout' ) ;
8991const kMakeTransferable = Symbol ( 'kMakeTransferable' ) ;
92+ const kComposite = Symbol ( 'kComposite' ) ;
93+ const kSourceSignals = Symbol ( 'kSourceSignals' ) ;
94+ const kDependantSignals = Symbol ( 'kDependantSignals' ) ;
9095
9196function 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
0 commit comments