@@ -10,16 +10,90 @@ const kOnTimeout = TimerWrap.kOnTimeout | 0;
10
10
// Timeout values > TIMEOUT_MAX are set to 1.
11
11
const TIMEOUT_MAX = 2147483647 ; // 2^31-1
12
12
13
- // IDLE TIMEOUTS
13
+
14
+ // HOW and WHY the timers implementation works the way it does.
15
+ //
16
+ // Timers are crucial to Node.js. Internally, any TCP I/O connection creates a
17
+ // timer so that we can time out of connections. Additionally, many user
18
+ // user libraries and applications also use timers. As such there may be a
19
+ // significantly large amount of timeouts scheduled at any given time.
20
+ // Therefore, it is very important that the timers implementation is performant
21
+ // and efficient.
22
+ //
23
+ // Note: It is suggested you first read though the lib/internal/linkedlist.js
24
+ // linked list implementation, since timers depend on it extensively. It can be
25
+ // somewhat counter-intuitive at first, as it is not actually a class. Instead,
26
+ // it is a set of helpers that operate on an existing object.
27
+ //
28
+ // In order to be as performant as possible, the architecture and data
29
+ // structures are designed so that they are optimized to handle the following
30
+ // use cases as efficiently as possible:
31
+
32
+ // - Adding a new timer. (insert)
33
+ // - Removing an existing timer. (remove)
34
+ // - Handling a timer timing out. (timeout)
35
+ //
36
+ // Whenever possible, the implementation tries to make the complexity of these
37
+ // operations as close to constant-time as possible.
38
+ // (So that performance is not impacted by the number of scheduled timers.)
39
+ //
40
+ // Object maps are kept which contain linked lists keyed by their duration in
41
+ // milliseconds.
42
+ // The linked lists within also have some meta-properties, one of which is a
43
+ // TimerWrap C++ handle, which makes the call after the duration to process the
44
+ // list it is attached to.
45
+ //
46
+ //
47
+ // ╔════ > Object Map
48
+ // ║
49
+ // ╠══
50
+ // ║ refedLists: { '40': { }, '320': { etc } } (keys of millisecond duration)
51
+ // ╚══ ┌─────────┘
52
+ // │
53
+ // ╔══ │
54
+ // ║ TimersList { _idleNext: { }, _idlePrev: (self), _timer: (TimerWrap) }
55
+ // ║ ┌────────────────┘
56
+ // ║ ╔══ │ ^
57
+ // ║ ║ { _idleNext: { }, _idlePrev: { }, _onTimeout: (callback) }
58
+ // ║ ║ ┌───────────┘
59
+ // ║ ║ │ ^
60
+ // ║ ║ { _idleNext: { etc }, _idlePrev: { }, _onTimeout: (callback) }
61
+ // ╠══ ╠══
62
+ // ║ ║
63
+ // ║ ╚════ > Actual JavaScript timeouts
64
+ // ║
65
+ // ╚════ > Linked List
66
+ //
67
+ //
68
+ // With this, virtually constant-time insertion (append), removal, and timeout
69
+ // is possible in the JavaScript layer. Any one list of timers is able to be
70
+ // sorted by just appending to it because all timers within share the same
71
+ // duration. Therefore, any timer added later will always have been scheduled to
72
+ // timeout later, thus only needing to be appended.
73
+ // Removal from an object-property linked list is also virtually constant-time
74
+ // as can be seen in the lib/internal/linkedlist.js implementation.
75
+ // Timeouts only need to process any timers due to currently timeout, which will
76
+ // always be at the beginning of the list for reasons stated above. Any timers
77
+ // after the first one encountered that does not yet need to timeout will also
78
+ // always be due to timeout at a later time.
79
+ //
80
+ // Less-than constant time operations are thus contained in two places:
81
+ // TimerWrap's backing libuv timers implementation (a performant heap-based
82
+ // queue), and the object map lookup of a specific list by the duration of
83
+ // timers within (or creation of a new list).
84
+ // However, these operations combined have shown to be trivial in comparison to
85
+ // other alternative timers architectures.
86
+
87
+
14
88
// Object maps containing linked lists of timers, keyed and sorted by their
15
89
// duration in milliseconds.
16
90
//
17
- // Because often many sockets will have the same idle timeout we will not
18
- // use one timeout watcher per item. It is too much overhead. Instead
19
- // we'll use a single watcher for all sockets with the same timeout value
20
- // and a linked list. This technique is described in the libev manual:
21
- // http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts
22
-
91
+ // The difference between these two objects is that the former contains timers
92
+ // that will keep the process open if they are the only thing left, while the
93
+ // latter will not.
94
+ //
95
+ // - key = time in milliseconds
96
+ // - value = linked list
23
97
const refedLists = { } ;
24
98
const unrefedLists = { } ;
25
99
@@ -38,6 +112,10 @@ exports._unrefActive = function(item) {
38
112
39
113
40
114
// The underlying logic for scheduling or re-scheduling a timer.
115
+ //
116
+ // Appends a timer onto the end of an existing timers list, or creates a new
117
+ // TimerWrap backed list if one does not already exist for the specified timeout
118
+ // duration.
41
119
function insert ( item , unrefed ) {
42
120
const msecs = item . _idleTimeout ;
43
121
if ( msecs < 0 || msecs === undefined ) return ;
@@ -46,9 +124,12 @@ function insert(item, unrefed) {
46
124
47
125
const lists = unrefed === true ? unrefedLists : refedLists ;
48
126
127
+ // Use an existing list if there is one, otherwise we need to make a new one.
49
128
var list = lists [ msecs ] ;
50
129
if ( ! list ) {
51
130
debug ( 'no %d list was found in insert, creating a new one' , msecs ) ;
131
+ // Make a new linked list of timers, and create a TimerWrap to schedule
132
+ // processing for the list.
52
133
list = new TimersList ( msecs , unrefed ) ;
53
134
L . init ( list ) ;
54
135
list . _timer . _list = list ;
@@ -85,12 +166,15 @@ function listOnTimeout() {
85
166
while ( timer = L . peek ( list ) ) {
86
167
diff = now - timer . _idleStart ;
87
168
169
+ // Check if this loop iteration is too early for the next timer.
170
+ // This happens if there are more timers scheduled for later in the list.
88
171
if ( diff < msecs ) {
89
172
this . start ( msecs - diff , 0 ) ;
90
173
debug ( '%d list wait because diff is %d' , msecs , diff ) ;
91
174
return ;
92
175
}
93
176
177
+ // The actual logic for when a timeout happens.
94
178
95
179
L . remove ( timer ) ;
96
180
assert ( timer !== L . peek ( list ) ) ;
@@ -117,6 +201,9 @@ function listOnTimeout() {
117
201
domain . exit ( ) ;
118
202
}
119
203
204
+ // If `L.peek(list)` returned nothing, the list was either empty or we have
205
+ // called all of the timer timeouts.
206
+ // As such, we can remove the list and clean up the TimerWrap C++ handle.
120
207
debug ( '%d list empty' , msecs ) ;
121
208
assert ( L . isEmpty ( list ) ) ;
122
209
this . close ( ) ;
@@ -144,6 +231,7 @@ function tryOnTimeout(timer, list) {
144
231
// when the timeout threw its exception.
145
232
const domain = process . domain ;
146
233
process . domain = null ;
234
+ // If we threw, we need to process the rest of the list in nextTick.
147
235
process . nextTick ( listOnTimeoutNT , list ) ;
148
236
process . domain = domain ;
149
237
}
@@ -155,6 +243,12 @@ function listOnTimeoutNT(list) {
155
243
}
156
244
157
245
246
+ // A convenience function for re-using TimerWrap handles more easily.
247
+ //
248
+ // This mostly exists to fix https://github.com/nodejs/node/issues/1264.
249
+ // Handles in libuv take at least one `uv_run` to be registered as unreferenced.
250
+ // Re-using an existing handle allows us to skip that, so that a second `uv_run`
251
+ // will return no active handles, even when running `setTimeout(fn).unref()`.
158
252
function reuse ( item ) {
159
253
L . remove ( item ) ;
160
254
@@ -171,6 +265,7 @@ function reuse(item) {
171
265
}
172
266
173
267
268
+ // Remove a timer. Cancels the timeout and resets the relevant timer properties.
174
269
const unenroll = exports . unenroll = function ( item ) {
175
270
var handle = reuse ( item ) ;
176
271
if ( handle ) {
@@ -182,7 +277,9 @@ const unenroll = exports.unenroll = function(item) {
182
277
} ;
183
278
184
279
185
- // Does not start the time, just sets up the members needed.
280
+ // Make a regular object able to act as a timer by setting some properties.
281
+ // This function does not start the timer, see `active()`.
282
+ // Using existing objects as timers slightly reduces object overhead.
186
283
exports . enroll = function ( item , msecs ) {
187
284
if ( typeof msecs !== 'number' ) {
188
285
throw new TypeError ( '"msecs" argument must be a number' ) ;
0 commit comments