@@ -41,7 +41,7 @@ const {
4141} = require ( 'internal/errors' ) ;
4242
4343const {
44- validateAbortSignal ,
44+ validateAbortSignalArray ,
4545 validateObject,
4646 validateUint32,
4747} = require ( 'internal/validators' ) ;
@@ -54,6 +54,7 @@ const {
5454 clearTimeout,
5555 setTimeout,
5656} = require ( 'timers' ) ;
57+ const assert = require ( 'internal/assert' ) ;
5758
5859const {
5960 messaging_deserialize_symbol : kDeserialize ,
@@ -80,13 +81,16 @@ function lazyMakeTransferable(obj) {
8081}
8182
8283const clearTimeoutRegistry = new SafeFinalizationRegistry ( clearTimeout ) ;
83- const timeOutSignals = new SafeSet ( ) ;
84+ const gcPersistentSignals = new SafeSet ( ) ;
8485
8586const kAborted = Symbol ( 'kAborted' ) ;
8687const kReason = Symbol ( 'kReason' ) ;
8788const kCloneData = Symbol ( 'kCloneData' ) ;
8889const kTimeout = Symbol ( 'kTimeout' ) ;
8990const kMakeTransferable = Symbol ( 'kMakeTransferable' ) ;
91+ const kComposite = Symbol ( 'kComposite' ) ;
92+ const kSourceSignals = Symbol ( 'kSourceSignals' ) ;
93+ const kDependantSignals = Symbol ( 'kDependantSignals' ) ;
9094
9195function customInspect ( self , obj , depth , options ) {
9296 if ( depth < 0 )
@@ -116,7 +120,7 @@ function setWeakAbortSignalTimeout(weakRef, delay) {
116120 const timeout = setTimeout ( ( ) => {
117121 const signal = weakRef . deref ( ) ;
118122 if ( signal !== undefined ) {
119- timeOutSignals . delete ( signal ) ;
123+ gcPersistentSignals . delete ( signal ) ;
120124 abortSignal (
121125 signal ,
122126 new DOMException (
@@ -185,25 +189,68 @@ class AbortSignal extends EventTarget {
185189 return signal ;
186190 }
187191
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+
188233 [ kNewListener ] ( size , type , listener , once , capture , passive , weak ) {
189234 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 &&
191237 type === 'abort' &&
192238 ! this . aborted &&
193239 ! weak &&
194240 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
196242 // listener, then we don't want it to be gc'd while the listener
197243 // is attached and the timer still hasn't fired. So, we retain a
198244 // strong ref that is held for as long as the listener is registered.
199- timeOutSignals . add ( this ) ;
245+ gcPersistentSignals . add ( this ) ;
200246 }
201247 }
202248
203249 [ kRemoveListener ] ( size , type , listener , capture ) {
204250 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 ) ;
207254 }
208255 }
209256
@@ -287,7 +334,8 @@ defineEventHandler(AbortSignal.prototype, 'abort');
287334 * @param {{
288335 * aborted? : boolean,
289336 * reason? : any,
290- * transferable? : boolean
337+ * transferable? : boolean,
338+ * composite? : boolean,
291339 * }} [init]
292340 * @returns {AbortSignal }
293341 */
@@ -296,11 +344,13 @@ function createAbortSignal(init = kEmptyObject) {
296344 aborted = false ,
297345 reason = undefined ,
298346 transferable = false ,
347+ composite = false ,
299348 } = init ;
300349 const signal = new EventTarget ( ) ;
301350 ObjectSetPrototypeOf ( signal , AbortSignal . prototype ) ;
302351 signal [ kAborted ] = aborted ;
303352 signal [ kReason ] = reason ;
353+ signal [ kComposite ] = composite ;
304354 return transferable ? lazyMakeTransferable ( signal ) : signal ;
305355}
306356
@@ -312,6 +362,11 @@ function abortSignal(signal, reason) {
312362 [ kTrustEvent ] : true ,
313363 } ) ;
314364 signal . dispatchEvent ( event ) ;
365+ signal [ kDependantSignals ] ?. forEach ( s => {
366+ const signalRef = s . deref ( ) ;
367+ if ( ! signalRef ) return ;
368+ abortSignal ( signalRef , reason ) ;
369+ } ) ;
315370}
316371
317372class AbortController {
0 commit comments