@@ -8,15 +8,20 @@ const {
8
8
ObjectDefineProperties,
9
9
ObjectSetPrototypeOf,
10
10
ObjectDefineProperty,
11
+ SafeFinalizationRegistry,
12
+ SafeSet,
11
13
Symbol,
12
14
SymbolToStringTag,
15
+ WeakRef,
13
16
} = primordials ;
14
17
15
18
const {
16
19
defineEventHandler,
17
20
EventTarget,
18
21
Event,
19
- kTrustEvent
22
+ kTrustEvent,
23
+ kNewListener,
24
+ kRemoveListener,
20
25
} = require ( 'internal/event_target' ) ;
21
26
const {
22
27
customInspectSymbol,
@@ -29,8 +34,26 @@ const {
29
34
}
30
35
} = require ( 'internal/errors' ) ;
31
36
37
+ const {
38
+ validateUint32,
39
+ } = require ( 'internal/validators' ) ;
40
+
41
+ const {
42
+ DOMException,
43
+ } = internalBinding ( 'messaging' ) ;
44
+
45
+ const {
46
+ clearTimeout,
47
+ setTimeout,
48
+ } = require ( 'timers' ) ;
49
+
32
50
const kAborted = Symbol ( 'kAborted' ) ;
33
51
const kReason = Symbol ( 'kReason' ) ;
52
+ const kTimeout = Symbol ( 'kTimeout' ) ;
53
+
54
+ const timeOutSignals = new SafeSet ( ) ;
55
+
56
+ const clearTimeoutRegistry = new SafeFinalizationRegistry ( clearTimeout ) ;
34
57
35
58
function customInspect ( self , obj , depth , options ) {
36
59
if ( depth < 0 )
@@ -48,6 +71,30 @@ function validateAbortSignal(obj) {
48
71
throw new ERR_INVALID_THIS ( 'AbortSignal' ) ;
49
72
}
50
73
74
+ // Because the AbortSignal timeout cannot be canceled, we don't want the
75
+ // presence of the timer alone to keep the AbortSignal from being garbage
76
+ // collected if it otherwise no longer accessible. We also don't want the
77
+ // timer to keep the Node.js process open on it's own. Therefore, we wrap
78
+ // the AbortSignal in a WeakRef and have the setTimeout callback close
79
+ // over the WeakRef rather than directly over the AbortSignal, and we unref
80
+ // the created timer object. Separately, we add the signal to a
81
+ // FinalizerRegistry that will clear the timeout when the signal is gc'd.
82
+ function setWeakAbortSignalTimeout ( weakRef , delay ) {
83
+ const timeout = setTimeout ( ( ) => {
84
+ const signal = weakRef . deref ( ) ;
85
+ if ( signal !== undefined ) {
86
+ timeOutSignals . delete ( signal ) ;
87
+ abortSignal (
88
+ signal ,
89
+ new DOMException (
90
+ 'The operation was aborted due to timeout' ,
91
+ 'TimeoutError' ) ) ;
92
+ }
93
+ } , delay ) ;
94
+ timeout . unref ( ) ;
95
+ return timeout ;
96
+ }
97
+
51
98
class AbortSignal extends EventTarget {
52
99
constructor ( ) {
53
100
throw new ERR_ILLEGAL_CONSTRUCTOR ( ) ;
@@ -82,6 +129,42 @@ class AbortSignal extends EventTarget {
82
129
static abort ( reason ) {
83
130
return createAbortSignal ( true , reason ) ;
84
131
}
132
+
133
+ /**
134
+ * @param {number } delay
135
+ * @returns {AbortSignal }
136
+ */
137
+ static timeout ( delay ) {
138
+ validateUint32 ( delay , 'delay' , true ) ;
139
+ const signal = createAbortSignal ( ) ;
140
+ signal [ kTimeout ] = true ;
141
+ clearTimeoutRegistry . register (
142
+ signal ,
143
+ setWeakAbortSignalTimeout ( new WeakRef ( signal ) , delay ) ) ;
144
+ return signal ;
145
+ }
146
+
147
+ [ kNewListener ] ( size , type , listener , once , capture , passive , weak ) {
148
+ super [ kNewListener ] ( size , type , listener , once , capture , passive , weak ) ;
149
+ if ( this [ kTimeout ] &&
150
+ type === 'abort' &&
151
+ ! this . aborted &&
152
+ ! weak &&
153
+ size === 1 ) {
154
+ // If this is a timeout signal, and we're adding a non-weak abort
155
+ // listener, then we don't want it to be gc'd while the listener
156
+ // is attached and the timer still hasn't fired. So, we retain a
157
+ // strong ref that is held for as long as the listener is registered.
158
+ timeOutSignals . add ( this ) ;
159
+ }
160
+ }
161
+
162
+ [ kRemoveListener ] ( size , type , listener , capture ) {
163
+ super [ kRemoveListener ] ( size , type , listener , capture ) ;
164
+ if ( this [ kTimeout ] && type === 'abort' && size === 0 ) {
165
+ timeOutSignals . delete ( this ) ;
166
+ }
167
+ }
85
168
}
86
169
87
170
ObjectDefineProperties ( AbortSignal . prototype , {
0 commit comments