| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 60f8c1a commit 67963c8
1 file changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -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'); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments