3 const Timer = process.binding('timer_wrap').Timer;
4 const L = require('_linklist');
5 const assert = require('assert').ok;
6 const util = require('util');
7 const debug = util.debuglog('timer');
8 const kOnTimeout = Timer.kOnTimeout | 0;
10 // Timeout values > TIMEOUT_MAX are set to 1.
11 const TIMEOUT_MAX = 2147483647; // 2^31-1
15 // Because often many sockets will have the same idle timeout we will not
16 // use one timeout watcher per item. It is too much overhead. Instead
17 // we'll use a single watcher for all sockets with the same timeout value
18 // and a linked list. This technique is described in the libev manual:
19 // http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts
21 // Object containing all lists, timers
22 // key = time in milliseconds
27 // call this whenever the item is active (not idle)
28 // it will reset its timeout.
29 // the main function - creates lists on demand and the watchers associated
31 exports.active = function(item) {
32 const msecs = item._idleTimeout;
33 if (msecs < 0 || msecs === undefined) return;
35 item._idleStart = Timer.now();
49 list[kOnTimeout] = listOnTimeout;
53 assert(!L.isEmpty(list)); // list is not empty
56 function listOnTimeout() {
57 var msecs = this.msecs;
60 debug('timeout callback %d', msecs);
62 var now = Timer.now();
63 debug('now: %s', now);
65 var diff, first, threw;
66 while (first = L.peek(list)) {
67 diff = now - first._idleStart;
69 list.start(msecs - diff, 0);
70 debug('%d list wait because diff is %d', msecs, diff);
74 assert(first !== L.peek(list));
76 if (!first._onTimeout) continue;
78 // v0.4 compatibility: if the timer callback throws and the
79 // domain or uncaughtException handler ignore the exception,
80 // other timers that expire on this tick should still run.
82 // https://github.com/joyent/node/issues/2631
83 var domain = first.domain;
84 if (domain && domain._disposed)
98 // We need to continue processing after domain error handling
99 // is complete, but not by using whatever domain was left over
100 // when the timeout threw its exception.
101 var oldDomain = process.domain;
102 process.domain = null;
103 process.nextTick(listOnTimeoutNT, list);
104 process.domain = oldDomain;
110 debug('%d list empty', msecs);
111 assert(L.isEmpty(list));
117 function listOnTimeoutNT(list) {
122 function reuse(item) {
125 var list = lists[item._idleTimeout];
126 // if empty - reuse the watcher
127 if (list && L.isEmpty(list)) {
130 delete lists[item._idleTimeout];
138 const unenroll = exports.unenroll = function(item) {
139 var list = reuse(item);
141 debug('unenroll: list empty');
144 // if active is called later, then we want to make sure not to insert again
145 item._idleTimeout = -1;
149 // Does not start the time, just sets up the members needed.
150 exports.enroll = function(item, msecs) {
151 if (typeof msecs !== 'number') {
152 throw new TypeError('msecs must be a number');
155 if (msecs < 0 || !isFinite(msecs)) {
156 throw new RangeError('msecs must be a non-negative finite number');
159 // if this item was already in a list somewhere
160 // then we should unenroll it from that
161 if (item._idleNext) unenroll(item);
163 // Ensure that msecs fits into signed int32
164 if (msecs > TIMEOUT_MAX) {
168 item._idleTimeout = msecs;
178 exports.setTimeout = function(callback, after) {
179 after *= 1; // coalesce to number or NaN
181 if (!(after >= 1 && after <= TIMEOUT_MAX)) {
182 after = 1; // schedule on next tick, follows browser behaviour
185 var timer = new Timeout(after);
186 var length = arguments.length;
187 var ontimeout = callback;
195 ontimeout = () => callback.call(timer, arguments[2]);
198 ontimeout = () => callback.call(timer, arguments[2], arguments[3]);
202 () => callback.call(timer, arguments[2], arguments[3], arguments[4]);
206 var args = new Array(length - 2);
207 for (var i = 2; i < length; i++)
208 args[i - 2] = arguments[i];
209 ontimeout = () => callback.apply(timer, args);
212 timer._onTimeout = ontimeout;
214 if (process.domain) timer.domain = process.domain;
216 exports.active(timer);
222 exports.clearTimeout = function(timer) {
223 if (timer && (timer[kOnTimeout] || timer._onTimeout)) {
224 timer[kOnTimeout] = timer._onTimeout = null;
225 if (timer instanceof Timeout) {
226 timer.close(); // for after === 0
228 exports.unenroll(timer);
234 exports.setInterval = function(callback, repeat) {
235 repeat *= 1; // coalesce to number or NaN
237 if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) {
238 repeat = 1; // schedule on next tick, follows browser behaviour
241 var timer = new Timeout(repeat);
242 var length = arguments.length;
243 var ontimeout = callback;
250 ontimeout = () => callback.call(timer, arguments[2]);
253 ontimeout = () => callback.call(timer, arguments[2], arguments[3]);
257 () => callback.call(timer, arguments[2], arguments[3], arguments[4]);
260 var args = new Array(length - 2);
261 for (var i = 2; i < length; i += 1)
262 args[i - 2] = arguments[i];
263 ontimeout = () => callback.apply(timer, args);
266 timer._onTimeout = wrapper;
267 timer._repeat = ontimeout;
269 if (process.domain) timer.domain = process.domain;
270 exports.active(timer);
277 // Timer might be closed - no point in restarting it
281 // If timer is unref'd (or was - it's permanently removed from the list.)
283 this._handle.start(repeat, 0);
285 timer._idleTimeout = repeat;
286 exports.active(timer);
292 exports.clearInterval = function(timer) {
293 if (timer && timer._repeat) {
294 timer._repeat = null;
300 const Timeout = function(after) {
301 this._called = false;
302 this._idleTimeout = after;
303 this._idlePrev = this;
304 this._idleNext = this;
305 this._idleStart = null;
306 this._onTimeout = null;
311 function unrefdHandle() {
312 this.owner._onTimeout();
313 if (!this.owner._repeat)
318 Timeout.prototype.unref = function() {
320 this._handle.unref();
321 } else if (typeof(this._onTimeout) === 'function') {
322 var now = Timer.now();
323 if (!this._idleStart) this._idleStart = now;
324 var delay = this._idleStart + this._idleTimeout - now;
325 if (delay < 0) delay = 0;
327 // Prevent running cb again when unref() is called during the same cb
328 if (this._called && !this._repeat) {
329 exports.unenroll(this);
333 var handle = reuse(this);
335 this._handle = handle || new Timer();
336 this._handle.owner = this;
337 this._handle[kOnTimeout] = unrefdHandle;
338 this._handle.start(delay, 0);
339 this._handle.domain = this.domain;
340 this._handle.unref();
345 Timeout.prototype.ref = function() {
351 Timeout.prototype.close = function() {
352 this._onTimeout = null;
354 this._handle[kOnTimeout] = null;
355 this._handle.close();
357 exports.unenroll(this);
363 var immediateQueue = {};
364 L.init(immediateQueue);
367 function processImmediate() {
368 var queue = immediateQueue;
369 var domain, immediate;
372 L.init(immediateQueue);
374 while (L.isEmpty(queue) === false) {
375 immediate = L.shift(queue);
376 domain = immediate.domain;
383 immediate._onImmediate();
387 if (!L.isEmpty(queue)) {
388 // Handle any remaining on next tick, assuming we're still
390 while (!L.isEmpty(immediateQueue)) {
391 L.append(queue, L.shift(immediateQueue));
393 immediateQueue = queue;
394 process.nextTick(processImmediate);
403 // Only round-trip to C++ land if we have to. Calling clearImmediate() on an
404 // immediate that's in |queue| is okay. Worst case is we make a superfluous
405 // call to NeedImmediateCallbackSetter().
406 if (L.isEmpty(immediateQueue)) {
407 process._needImmediateCallback = false;
412 function Immediate() { }
414 Immediate.prototype.domain = undefined;
415 Immediate.prototype._onImmediate = undefined;
416 Immediate.prototype._idleNext = undefined;
417 Immediate.prototype._idlePrev = undefined;
420 exports.setImmediate = function(callback, arg1, arg2, arg3) {
422 var len = arguments.length;
423 var immediate = new Immediate();
431 immediate._onImmediate = callback;
434 immediate._onImmediate = function() {
435 callback.call(immediate, arg1);
439 immediate._onImmediate = function() {
440 callback.call(immediate, arg1, arg2);
444 immediate._onImmediate = function() {
445 callback.call(immediate, arg1, arg2, arg3);
450 args = new Array(len - 1);
451 for (i = 1; i < len; i++)
452 args[i - 1] = arguments[i];
454 immediate._onImmediate = function() {
455 callback.apply(immediate, args);
460 if (!process._needImmediateCallback) {
461 process._needImmediateCallback = true;
462 process._immediateCallback = processImmediate;
466 immediate.domain = process.domain;
468 L.append(immediateQueue, immediate);
474 exports.clearImmediate = function(immediate) {
475 if (!immediate) return;
477 immediate._onImmediate = undefined;
481 if (L.isEmpty(immediateQueue)) {
482 process._needImmediateCallback = false;
487 // Internal APIs that need timeouts should use timers._unrefActive instead of
488 // timers.active as internal timeouts shouldn't hold the loop open
490 var unrefList, unrefTimer;
492 function _makeTimerTimeout(timer) {
493 var domain = timer.domain;
494 var msecs = timer._idleTimeout;
498 // Timer has been unenrolled by another timer that fired at the same time,
499 // so don't make it timeout.
503 if (!timer._onTimeout)
507 if (domain._disposed)
513 debug('unreftimer firing timeout');
514 timer._called = true;
515 _runOnTimeout(timer);
521 function _runOnTimeout(timer) {
527 if (threw) process.nextTick(unrefTimeout);
531 function unrefTimeout() {
532 var now = Timer.now();
534 debug('unrefTimer fired');
536 var timeSinceLastActive;
538 var nextTimeoutDuration;
539 var minNextTimeoutTime = TIMEOUT_MAX;
540 var timersToTimeout = [];
542 // The actual timer fired and has not yet been rearmed,
543 // let's consider its next firing time is invalid for now.
544 // It may be set to a relevant time in the future once
545 // we scanned through the whole list of timeouts and if
546 // we find a timeout that needs to expire.
547 unrefTimer.when = -1;
549 // Iterate over the list of timeouts,
550 // call the onTimeout callback for those expired,
551 // and rearm the actual timer if the next timeout to expire
552 // will expire before the current actual timer.
553 var cur = unrefList._idlePrev;
554 while (cur !== unrefList) {
555 timeSinceLastActive = now - cur._idleStart;
557 if (timeSinceLastActive < cur._idleTimeout) {
558 // This timer hasn't expired yet, but check if its expiring time is
559 // earlier than the actual timer's expiring time
561 nextTimeoutDuration = cur._idleTimeout - timeSinceLastActive;
562 nextTimeoutTime = now + nextTimeoutDuration;
563 if (minNextTimeoutTime === TIMEOUT_MAX ||
564 (nextTimeoutTime < minNextTimeoutTime)) {
565 // We found a timeout that will expire earlier,
566 // store its next timeout time now so that we
567 // can rearm the actual timer accordingly when
568 // we scanned through the whole list.
569 minNextTimeoutTime = nextTimeoutTime;
572 // We found a timer that expired. Do not call its _onTimeout callback
573 // right now, as it could mutate any item of the list (including itself).
574 // Instead, add it to another list that will be processed once the list
575 // of current timers has been fully traversed.
576 timersToTimeout.push(cur);
582 var nbTimersToTimeout = timersToTimeout.length;
583 for (var timerIdx = 0; timerIdx < nbTimersToTimeout; ++timerIdx)
584 _makeTimerTimeout(timersToTimeout[timerIdx]);
587 // Rearm the actual timer with the timeout delay
588 // of the earliest timeout found.
589 if (minNextTimeoutTime !== TIMEOUT_MAX) {
590 unrefTimer.start(minNextTimeoutTime - now, 0);
591 unrefTimer.when = minNextTimeoutTime;
592 debug('unrefTimer rescheduled');
593 } else if (L.isEmpty(unrefList)) {
594 debug('unrefList is empty');
599 exports._unrefActive = function(item) {
600 var msecs = item._idleTimeout;
601 if (!msecs || msecs < 0) return;
607 debug('unrefList initialized');
611 debug('unrefTimer initialized');
612 unrefTimer = new Timer();
614 unrefTimer.when = -1;
615 unrefTimer[kOnTimeout] = unrefTimeout;
618 var now = Timer.now();
619 item._idleStart = now;
621 var when = now + msecs;
623 // If the actual timer is set to fire too late, or not set to fire at all,
624 // we need to make it fire earlier
625 if (unrefTimer.when === -1 || unrefTimer.when > when) {
626 unrefTimer.start(msecs, 0);
627 unrefTimer.when = when;
628 debug('unrefTimer scheduled');
631 debug('unrefList append to end');
632 L.append(unrefList, item);