1'use strict'; 2 3// HOW and WHY the timers implementation works the way it does. 4// 5// Timers are crucial to Node.js. Internally, any TCP I/O connection creates a 6// timer so that we can time out of connections. Additionally, many user 7// libraries and applications also use timers. As such there may be a 8// significantly large amount of timeouts scheduled at any given time. 9// Therefore, it is very important that the timers implementation is performant 10// and efficient. 11// 12// Note: It is suggested you first read through the lib/internal/linkedlist.js 13// linked list implementation, since timers depend on it extensively. It can be 14// somewhat counter-intuitive at first, as it is not actually a class. Instead, 15// it is a set of helpers that operate on an existing object. 16// 17// In order to be as performant as possible, the architecture and data 18// structures are designed so that they are optimized to handle the following 19// use cases as efficiently as possible: 20 21// - Adding a new timer. (insert) 22// - Removing an existing timer. (remove) 23// - Handling a timer timing out. (timeout) 24// 25// Whenever possible, the implementation tries to make the complexity of these 26// operations as close to constant-time as possible. 27// (So that performance is not impacted by the number of scheduled timers.) 28// 29// Object maps are kept which contain linked lists keyed by their duration in 30// milliseconds. 31// 32/* eslint-disable node-core/non-ascii-character */ 33// 34// ╔════ > Object Map 35// ║ 36// ╠══ 37// ║ lists: { '40': { }, '320': { etc } } (keys of millisecond duration) 38// ╚══ ┌────┘ 39// │ 40// ╔══ │ 41// ║ TimersList { _idleNext: { }, _idlePrev: (self) } 42// ║ ┌────────────────┘ 43// ║ ╔══ │ ^ 44// ║ ║ { _idleNext: { }, _idlePrev: { }, _onTimeout: (callback) } 45// ║ ║ ┌───────────┘ 46// ║ ║ │ ^ 47// ║ ║ { _idleNext: { etc }, _idlePrev: { }, _onTimeout: (callback) } 48// ╠══ ╠══ 49// ║ ║ 50// ║ ╚════ > Actual JavaScript timeouts 51// ║ 52// ╚════ > Linked List 53// 54/* eslint-enable node-core/non-ascii-character */ 55// 56// With this, virtually constant-time insertion (append), removal, and timeout 57// is possible in the JavaScript layer. Any one list of timers is able to be 58// sorted by just appending to it because all timers within share the same 59// duration. Therefore, any timer added later will always have been scheduled to 60// timeout later, thus only needing to be appended. 61// Removal from an object-property linked list is also virtually constant-time 62// as can be seen in the lib/internal/linkedlist.js implementation. 63// Timeouts only need to process any timers currently due to expire, which will 64// always be at the beginning of the list for reasons stated above. Any timers 65// after the first one encountered that does not yet need to timeout will also 66// always be due to timeout at a later time. 67// 68// Less-than constant time operations are thus contained in two places: 69// The PriorityQueue — an efficient binary heap implementation that does all 70// operations in worst-case O(log n) time — which manages the order of expiring 71// Timeout lists and the object map lookup of a specific list by the duration of 72// timers within (or creation of a new list). However, these operations combined 73// have shown to be trivial in comparison to other timers architectures. 74 75const { 76 MathMax, 77 MathTrunc, 78 NumberMIN_SAFE_INTEGER, 79 ObjectCreate, 80 Symbol, 81} = primordials; 82 83const { 84 scheduleTimer, 85 toggleTimerRef, 86 getLibuvNow, 87 immediateInfo 88} = internalBinding('timers'); 89 90const { 91 getDefaultTriggerAsyncId, 92 newAsyncId, 93 initHooksExist, 94 destroyHooksExist, 95 // The needed emit*() functions. 96 emitInit, 97 emitBefore, 98 emitAfter, 99 emitDestroy, 100} = require('internal/async_hooks'); 101 102// Symbols for storing async id state. 103const async_id_symbol = Symbol('asyncId'); 104const trigger_async_id_symbol = Symbol('triggerId'); 105 106const kHasPrimitive = Symbol('kHasPrimitive'); 107 108const { 109 ERR_INVALID_CALLBACK, 110 ERR_OUT_OF_RANGE 111} = require('internal/errors').codes; 112const { validateNumber } = require('internal/validators'); 113 114const L = require('internal/linkedlist'); 115const PriorityQueue = require('internal/priority_queue'); 116 117const { inspect } = require('internal/util/inspect'); 118let debug = require('internal/util/debuglog').debuglog('timer', (fn) => { 119 debug = fn; 120}); 121 122// *Must* match Environment::ImmediateInfo::Fields in src/env.h. 123const kCount = 0; 124const kRefCount = 1; 125const kHasOutstanding = 2; 126 127// Timeout values > TIMEOUT_MAX are set to 1. 128const TIMEOUT_MAX = 2 ** 31 - 1; 129 130let timerListId = NumberMIN_SAFE_INTEGER; 131 132const kRefed = Symbol('refed'); 133 134// Create a single linked list instance only once at startup 135const immediateQueue = new ImmediateList(); 136 137let nextExpiry = Infinity; 138let refCount = 0; 139 140// This is a priority queue with a custom sorting function that first compares 141// the expiry times of two lists and if they're the same then compares their 142// individual IDs to determine which list was created first. 143const timerListQueue = new PriorityQueue(compareTimersLists, setPosition); 144 145// Object map containing linked lists of timers, keyed and sorted by their 146// duration in milliseconds. 147// 148// - key = time in milliseconds 149// - value = linked list 150const timerListMap = ObjectCreate(null); 151 152function initAsyncResource(resource, type) { 153 const asyncId = resource[async_id_symbol] = newAsyncId(); 154 const triggerAsyncId = 155 resource[trigger_async_id_symbol] = getDefaultTriggerAsyncId(); 156 if (initHooksExist()) 157 emitInit(asyncId, type, triggerAsyncId, resource); 158} 159 160// Timer constructor function. 161// The entire prototype is defined in lib/timers.js 162function Timeout(callback, after, args, isRepeat, isRefed) { 163 after *= 1; // Coalesce to number or NaN 164 if (!(after >= 1 && after <= TIMEOUT_MAX)) { 165 if (after > TIMEOUT_MAX) { 166 process.emitWarning(`${after} does not fit into` + 167 ' a 32-bit signed integer.' + 168 '\nTimeout duration was set to 1.', 169 'TimeoutOverflowWarning'); 170 } 171 after = 1; // Schedule on next tick, follows browser behavior 172 } 173 174 this._idleTimeout = after; 175 this._idlePrev = this; 176 this._idleNext = this; 177 this._idleStart = null; 178 // This must be set to null first to avoid function tracking 179 // on the hidden class, revisit in V8 versions after 6.2 180 this._onTimeout = null; 181 this._onTimeout = callback; 182 this._timerArgs = args; 183 this._repeat = isRepeat ? after : null; 184 this._destroyed = false; 185 186 if (isRefed) 187 incRefCount(); 188 this[kRefed] = isRefed; 189 this[kHasPrimitive] = false; 190 191 initAsyncResource(this, 'Timeout'); 192} 193 194// Make sure the linked list only shows the minimal necessary information. 195Timeout.prototype[inspect.custom] = function(_, options) { 196 return inspect(this, { 197 ...options, 198 // Only inspect one level. 199 depth: 0, 200 // It should not recurse. 201 customInspect: false 202 }); 203}; 204 205Timeout.prototype.refresh = function() { 206 if (this[kRefed]) 207 active(this); 208 else 209 unrefActive(this); 210 211 return this; 212}; 213 214Timeout.prototype.unref = function() { 215 if (this[kRefed]) { 216 this[kRefed] = false; 217 if (!this._destroyed) 218 decRefCount(); 219 } 220 return this; 221}; 222 223Timeout.prototype.ref = function() { 224 if (!this[kRefed]) { 225 this[kRefed] = true; 226 if (!this._destroyed) 227 incRefCount(); 228 } 229 return this; 230}; 231 232Timeout.prototype.hasRef = function() { 233 return this[kRefed]; 234}; 235 236function TimersList(expiry, msecs) { 237 this._idleNext = this; // Create the list with the linkedlist properties to 238 this._idlePrev = this; // Prevent any unnecessary hidden class changes. 239 this.expiry = expiry; 240 this.id = timerListId++; 241 this.msecs = msecs; 242 this.priorityQueuePosition = null; 243} 244 245// Make sure the linked list only shows the minimal necessary information. 246TimersList.prototype[inspect.custom] = function(_, options) { 247 return inspect(this, { 248 ...options, 249 // Only inspect one level. 250 depth: 0, 251 // It should not recurse. 252 customInspect: false 253 }); 254}; 255 256// A linked list for storing `setImmediate()` requests 257function ImmediateList() { 258 this.head = null; 259 this.tail = null; 260} 261 262// Appends an item to the end of the linked list, adjusting the current tail's 263// previous and next pointers where applicable 264ImmediateList.prototype.append = function(item) { 265 if (this.tail !== null) { 266 this.tail._idleNext = item; 267 item._idlePrev = this.tail; 268 } else { 269 this.head = item; 270 } 271 this.tail = item; 272}; 273 274// Removes an item from the linked list, adjusting the pointers of adjacent 275// items and the linked list's head or tail pointers as necessary 276ImmediateList.prototype.remove = function(item) { 277 if (item._idleNext !== null) { 278 item._idleNext._idlePrev = item._idlePrev; 279 } 280 281 if (item._idlePrev !== null) { 282 item._idlePrev._idleNext = item._idleNext; 283 } 284 285 if (item === this.head) 286 this.head = item._idleNext; 287 if (item === this.tail) 288 this.tail = item._idlePrev; 289 290 item._idleNext = null; 291 item._idlePrev = null; 292}; 293 294function incRefCount() { 295 if (refCount++ === 0) 296 toggleTimerRef(true); 297} 298 299function decRefCount() { 300 if (--refCount === 0) 301 toggleTimerRef(false); 302} 303 304// Schedule or re-schedule a timer. 305// The item must have been enroll()'d first. 306function active(item) { 307 insertGuarded(item, true); 308} 309 310// Internal APIs that need timeouts should use `unrefActive()` instead of 311// `active()` so that they do not unnecessarily keep the process open. 312function unrefActive(item) { 313 insertGuarded(item, false); 314} 315 316// The underlying logic for scheduling or re-scheduling a timer. 317// 318// Appends a timer onto the end of an existing timers list, or creates a new 319// list if one does not already exist for the specified timeout duration. 320function insertGuarded(item, refed, start) { 321 const msecs = item._idleTimeout; 322 if (msecs < 0 || msecs === undefined) 323 return; 324 325 insert(item, msecs, start); 326 327 const isDestroyed = item._destroyed; 328 if (isDestroyed || !item[async_id_symbol]) { 329 item._destroyed = false; 330 initAsyncResource(item, 'Timeout'); 331 } 332 333 if (isDestroyed) { 334 if (refed) 335 incRefCount(); 336 } else if (refed === !item[kRefed]) { 337 if (refed) 338 incRefCount(); 339 else 340 decRefCount(); 341 } 342 item[kRefed] = refed; 343} 344 345function insert(item, msecs, start = getLibuvNow()) { 346 // Truncate so that accuracy of sub-millisecond timers is not assumed. 347 msecs = MathTrunc(msecs); 348 item._idleStart = start; 349 350 // Use an existing list if there is one, otherwise we need to make a new one. 351 let list = timerListMap[msecs]; 352 if (list === undefined) { 353 debug('no %d list was found in insert, creating a new one', msecs); 354 const expiry = start + msecs; 355 timerListMap[msecs] = list = new TimersList(expiry, msecs); 356 timerListQueue.insert(list); 357 358 if (nextExpiry > expiry) { 359 scheduleTimer(msecs); 360 nextExpiry = expiry; 361 } 362 } 363 364 L.append(list, item); 365} 366 367function setUnrefTimeout(callback, after) { 368 // Type checking identical to setTimeout() 369 if (typeof callback !== 'function') { 370 throw new ERR_INVALID_CALLBACK(callback); 371 } 372 373 const timer = new Timeout(callback, after, undefined, false, false); 374 insert(timer, timer._idleTimeout); 375 376 return timer; 377} 378 379// Type checking used by timers.enroll() and Socket#setTimeout() 380function getTimerDuration(msecs, name) { 381 validateNumber(msecs, name); 382 if (msecs < 0 || !isFinite(msecs)) { 383 throw new ERR_OUT_OF_RANGE(name, 'a non-negative finite number', msecs); 384 } 385 386 // Ensure that msecs fits into signed int32 387 if (msecs > TIMEOUT_MAX) { 388 process.emitWarning(`${msecs} does not fit into a 32-bit signed integer.` + 389 `\nTimer duration was truncated to ${TIMEOUT_MAX}.`, 390 'TimeoutOverflowWarning'); 391 return TIMEOUT_MAX; 392 } 393 394 return msecs; 395} 396 397function compareTimersLists(a, b) { 398 const expiryDiff = a.expiry - b.expiry; 399 if (expiryDiff === 0) { 400 if (a.id < b.id) 401 return -1; 402 if (a.id > b.id) 403 return 1; 404 } 405 return expiryDiff; 406} 407 408function setPosition(node, pos) { 409 node.priorityQueuePosition = pos; 410} 411 412function getTimerCallbacks(runNextTicks) { 413 // If an uncaught exception was thrown during execution of immediateQueue, 414 // this queue will store all remaining Immediates that need to run upon 415 // resolution of all error handling (if process is still alive). 416 const outstandingQueue = new ImmediateList(); 417 418 function processImmediate() { 419 const queue = outstandingQueue.head !== null ? 420 outstandingQueue : immediateQueue; 421 let immediate = queue.head; 422 423 // Clear the linked list early in case new `setImmediate()` 424 // calls occur while immediate callbacks are executed 425 if (queue !== outstandingQueue) { 426 queue.head = queue.tail = null; 427 immediateInfo[kHasOutstanding] = 1; 428 } 429 430 let prevImmediate; 431 let ranAtLeastOneImmediate = false; 432 while (immediate !== null) { 433 if (ranAtLeastOneImmediate) 434 runNextTicks(); 435 else 436 ranAtLeastOneImmediate = true; 437 438 // It's possible for this current Immediate to be cleared while executing 439 // the next tick queue above, which means we need to use the previous 440 // Immediate's _idleNext which is guaranteed to not have been cleared. 441 if (immediate._destroyed) { 442 outstandingQueue.head = immediate = prevImmediate._idleNext; 443 continue; 444 } 445 446 immediate._destroyed = true; 447 448 immediateInfo[kCount]--; 449 if (immediate[kRefed]) 450 immediateInfo[kRefCount]--; 451 immediate[kRefed] = null; 452 453 prevImmediate = immediate; 454 455 const asyncId = immediate[async_id_symbol]; 456 emitBefore(asyncId, immediate[trigger_async_id_symbol], immediate); 457 458 try { 459 const argv = immediate._argv; 460 if (!argv) 461 immediate._onImmediate(); 462 else 463 immediate._onImmediate(...argv); 464 } finally { 465 immediate._onImmediate = null; 466 467 if (destroyHooksExist()) 468 emitDestroy(asyncId); 469 470 outstandingQueue.head = immediate = immediate._idleNext; 471 } 472 473 emitAfter(asyncId); 474 } 475 476 if (queue === outstandingQueue) 477 outstandingQueue.head = null; 478 immediateInfo[kHasOutstanding] = 0; 479 } 480 481 482 function processTimers(now) { 483 debug('process timer lists %d', now); 484 nextExpiry = Infinity; 485 486 let list; 487 let ranAtLeastOneList = false; 488 while (list = timerListQueue.peek()) { 489 if (list.expiry > now) { 490 nextExpiry = list.expiry; 491 return refCount > 0 ? nextExpiry : -nextExpiry; 492 } 493 if (ranAtLeastOneList) 494 runNextTicks(); 495 else 496 ranAtLeastOneList = true; 497 listOnTimeout(list, now); 498 } 499 return 0; 500 } 501 502 function listOnTimeout(list, now) { 503 const msecs = list.msecs; 504 505 debug('timeout callback %d', msecs); 506 507 let ranAtLeastOneTimer = false; 508 let timer; 509 while (timer = L.peek(list)) { 510 const diff = now - timer._idleStart; 511 512 // Check if this loop iteration is too early for the next timer. 513 // This happens if there are more timers scheduled for later in the list. 514 if (diff < msecs) { 515 list.expiry = MathMax(timer._idleStart + msecs, now + 1); 516 list.id = timerListId++; 517 timerListQueue.percolateDown(1); 518 debug('%d list wait because diff is %d', msecs, diff); 519 return; 520 } 521 522 if (ranAtLeastOneTimer) 523 runNextTicks(); 524 else 525 ranAtLeastOneTimer = true; 526 527 // The actual logic for when a timeout happens. 528 L.remove(timer); 529 530 const asyncId = timer[async_id_symbol]; 531 532 if (!timer._onTimeout) { 533 if (!timer._destroyed) { 534 timer._destroyed = true; 535 536 if (timer[kRefed]) 537 refCount--; 538 539 if (destroyHooksExist()) 540 emitDestroy(asyncId); 541 } 542 continue; 543 } 544 545 emitBefore(asyncId, timer[trigger_async_id_symbol], timer); 546 547 let start; 548 if (timer._repeat) 549 start = getLibuvNow(); 550 551 try { 552 const args = timer._timerArgs; 553 if (args === undefined) 554 timer._onTimeout(); 555 else 556 timer._onTimeout(...args); 557 } finally { 558 if (timer._repeat && timer._idleTimeout !== -1) { 559 timer._idleTimeout = timer._repeat; 560 insert(timer, timer._idleTimeout, start); 561 } else if (!timer._idleNext && !timer._idlePrev && !timer._destroyed) { 562 timer._destroyed = true; 563 564 if (timer[kRefed]) 565 refCount--; 566 567 if (destroyHooksExist()) 568 emitDestroy(asyncId); 569 } 570 } 571 572 emitAfter(asyncId); 573 } 574 575 // If `L.peek(list)` returned nothing, the list was either empty or we have 576 // called all of the timer timeouts. 577 // As such, we can remove the list from the object map and 578 // the PriorityQueue. 579 debug('%d list empty', msecs); 580 581 // The current list may have been removed and recreated since the reference 582 // to `list` was created. Make sure they're the same instance of the list 583 // before destroying. 584 if (list === timerListMap[msecs]) { 585 delete timerListMap[msecs]; 586 timerListQueue.shift(); 587 } 588 } 589 590 return { 591 processImmediate, 592 processTimers 593 }; 594} 595 596module.exports = { 597 TIMEOUT_MAX, 598 kTimeout: Symbol('timeout'), // For hiding Timeouts on other internals. 599 async_id_symbol, 600 trigger_async_id_symbol, 601 Timeout, 602 kRefed, 603 kHasPrimitive, 604 initAsyncResource, 605 setUnrefTimeout, 606 getTimerDuration, 607 immediateQueue, 608 getTimerCallbacks, 609 immediateInfoFields: { 610 kCount, 611 kRefCount, 612 kHasOutstanding 613 }, 614 active, 615 unrefActive, 616 insert, 617 timerListMap, 618 timerListQueue, 619 decRefCount, 620 incRefCount 621}; 622