node/lib/timers.js

328 lines
8.2 KiB
JavaScript
Raw Normal View History

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
MathTrunc,
Promise,
} = primordials;
const {
immediateInfo,
toggleImmediateRef
} = internalBinding('timers');
const L = require('internal/linkedlist');
const {
async_id_symbol,
Timeout,
decRefCount,
immediateInfoFields: {
kCount,
kRefCount
},
kRefed,
initAsyncResource,
getTimerDuration,
timerListMap,
timerListQueue,
immediateQueue,
active,
unrefActive,
insert
} = require('internal/timers');
const {
promisify: { custom: customPromisify },
deprecate
} = require('internal/util');
const { ERR_INVALID_CALLBACK } = require('internal/errors').codes;
const debug = require('internal/util/debuglog').debuglog('timer');
const {
destroyHooksExist,
// The needed emit*() functions.
emitDestroy
} = require('internal/async_hooks');
// Remove a timer. Cancels the timeout and resets the relevant timer properties.
function unenroll(item) {
// Fewer checks may be possible, but these cover everything.
if (destroyHooksExist() &&
item[async_id_symbol] !== undefined &&
!item._destroyed) {
emitDestroy(item[async_id_symbol]);
}
item._destroyed = true;
L.remove(item);
// We only delete refed lists because unrefed ones are incredibly likely
// to come from http and be recreated shortly after.
// TODO: Long-term this could instead be handled by creating an internal
// clearTimeout that makes it clear that the list should not be deleted.
// That function could then be used by http and other similar modules.
if (item[kRefed]) {
// Compliment truncation during insert().
const msecs = MathTrunc(item._idleTimeout);
const list = timerListMap[msecs];
if (list !== undefined && L.isEmpty(list)) {
debug('unenroll: list empty');
timerListQueue.removeAt(list.priorityQueuePosition);
delete timerListMap[list.msecs];
}
decRefCount();
2010-10-26 18:56:32 +00:00
}
item[kRefed] = null;
// If active is called later, then we want to make sure not to insert again
item._idleTimeout = -1;
}
// Make a regular object able to act as a timer by setting some properties.
// This function does not start the timer, see `active()`.
// Using existing objects as timers slightly reduces object overhead.
function enroll(item, msecs) {
msecs = getTimerDuration(msecs, 'msecs');
// If this item was already in a list somewhere
2010-10-26 18:56:32 +00:00
// then we should unenroll it from that
if (item._idleNext) unenroll(item);
2010-10-26 18:56:32 +00:00
2011-01-18 22:26:32 +00:00
L.init(item);
item._idleTimeout = msecs;
}
2011-01-13 10:22:09 +00:00
2010-10-26 19:52:31 +00:00
/*
* DOM-style timers
*/
function setTimeout(callback, after, arg1, arg2, arg3) {
if (typeof callback !== 'function') {
throw new ERR_INVALID_CALLBACK(callback);
}
let i, args;
switch (arguments.length) {
// fast cases
case 1:
case 2:
break;
case 3:
args = [arg1];
break;
case 4:
args = [arg1, arg2];
break;
default:
args = [arg1, arg2, arg3];
for (i = 5; i < arguments.length; i++) {
// Extend array dynamically, makes .apply run much faster in v6.0.0
args[i - 2] = arguments[i];
}
break;
}
const timeout = new Timeout(callback, after, args, false, true);
insert(timeout, timeout._idleTimeout);
return timeout;
}
setTimeout[customPromisify] = function(after, value) {
const args = value !== undefined ? [value] : value;
return new Promise((resolve) => {
const timeout = new Timeout(resolve, after, args, false, true);
insert(timeout, timeout._idleTimeout);
});
};
function clearTimeout(timer) {
if (timer && timer._onTimeout) {
timer._onTimeout = null;
unenroll(timer);
2010-10-29 07:00:43 +00:00
}
}
2010-10-26 19:52:31 +00:00
function setInterval(callback, repeat, arg1, arg2, arg3) {
if (typeof callback !== 'function') {
throw new ERR_INVALID_CALLBACK(callback);
}
let i, args;
switch (arguments.length) {
// fast cases
case 1:
case 2:
break;
case 3:
args = [arg1];
break;
case 4:
args = [arg1, arg2];
break;
default:
args = [arg1, arg2, arg3];
for (i = 5; i < arguments.length; i++) {
// Extend array dynamically, makes .apply run much faster in v6.0.0
args[i - 2] = arguments[i];
}
break;
}
const timeout = new Timeout(callback, repeat, args, true, true);
insert(timeout, timeout._idleTimeout);
return timeout;
}
function clearInterval(timer) {
// clearTimeout and clearInterval can be used to clear timers created from
// both setTimeout and setInterval, as specified by HTML Living Standard:
// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-setinterval
clearTimeout(timer);
}
2012-07-13 02:19:01 +00:00
Timeout.prototype.close = function() {
clearTimeout(this);
return this;
2012-07-13 02:19:01 +00:00
};
2012-08-08 02:12:01 +00:00
const Immediate = class Immediate {
constructor(callback, args) {
this._idleNext = null;
this._idlePrev = null;
this._onImmediate = callback;
this._argv = args;
this._destroyed = false;
this[kRefed] = false;
initAsyncResource(this, 'Immediate');
this.ref();
immediateInfo[kCount]++;
immediateQueue.append(this);
}
ref() {
if (this[kRefed] === false) {
this[kRefed] = true;
if (immediateInfo[kRefCount]++ === 0)
toggleImmediateRef(true);
}
return this;
}
unref() {
if (this[kRefed] === true) {
this[kRefed] = false;
if (--immediateInfo[kRefCount] === 0)
toggleImmediateRef(false);
}
return this;
}
hasRef() {
return !!this[kRefed];
}
};
function setImmediate(callback, arg1, arg2, arg3) {
if (typeof callback !== 'function') {
throw new ERR_INVALID_CALLBACK(callback);
}
let i, args;
switch (arguments.length) {
// fast cases
case 1:
break;
case 2:
args = [arg1];
break;
case 3:
args = [arg1, arg2];
break;
default:
args = [arg1, arg2, arg3];
for (i = 4; i < arguments.length; i++) {
// Extend array dynamically, makes .apply run much faster in v6.0.0
args[i - 1] = arguments[i];
}
break;
2012-08-08 02:12:01 +00:00
}
return new Immediate(callback, args);
}
setImmediate[customPromisify] = function(value) {
return new Promise((resolve) => new Immediate(resolve, [value]));
};
function clearImmediate(immediate) {
if (!immediate || immediate._destroyed)
return;
2012-08-08 02:12:01 +00:00
immediateInfo[kCount]--;
immediate._destroyed = true;
timers: cross JS/C++ border less frequently This removes the `process._needImmediateCallback` property and its semantics of having a 1/0 switch that tells C++ whether immediates are currently scheduled. Instead, a counter keeping track of all immediates is created, that can be increased on `setImmediate()` or decreased when an immediate is run or cleared. This is faster, because rather than reading/writing a C++ getter, this operation can be performed as a direct memory read/write via a typed array. The only C++ call that is left to make is activating the native handles upon creation of the first `Immediate` after the queue is empty. One other (good!) side-effect is that `immediate._destroyed` now reliably tells whether an `immediate` is still scheduled to run or not. Also, as a nice extra, this should make it easier to implement an internal variant of `setImmediate` for C++ that piggybacks off the same mechanism, which should be useful at least for async hooks and HTTP/2. Benchmark results: $ ./node benchmark/compare.js --new ./node --old ./node-master-1b093cb93df0 --runs 10 --filter immediate timers | Rscript benchmark/compare.R [00:08:53|% 100| 4/4 files | 20/20 runs | 1/1 configs]: Done improvement confidence p.value timers/immediate.js type="breadth" thousands=2000 25.61 % ** 1.432301e-03 timers/immediate.js type="breadth1" thousands=2000 7.66 % 1.320233e-01 timers/immediate.js type="breadth4" thousands=2000 4.61 % 5.669053e-01 timers/immediate.js type="clear" thousands=2000 311.40 % *** 3.896291e-07 timers/immediate.js type="depth" thousands=2000 17.54 % ** 9.755389e-03 timers/immediate.js type="depth1" thousands=2000 17.09 % *** 7.176229e-04 timers/set-immediate-breadth-args.js millions=5 10.63 % * 4.250034e-02 timers/set-immediate-breadth.js millions=10 20.62 % *** 9.150439e-07 timers/set-immediate-depth-args.js millions=10 17.97 % *** 6.819135e-10 PR-URL: https://github.com/nodejs/node/pull/17064 Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Minwoo Jung <minwoo@nodesource.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
2017-11-15 23:43:12 +00:00
if (immediate[kRefed] && --immediateInfo[kRefCount] === 0)
toggleImmediateRef(false);
immediate[kRefed] = null;
if (destroyHooksExist()) {
emitDestroy(immediate[async_id_symbol]);
}
immediate._onImmediate = null;
2012-08-08 02:12:01 +00:00
immediateQueue.remove(immediate);
}
module.exports = {
setTimeout,
clearTimeout,
setImmediate,
clearImmediate,
setInterval,
clearInterval,
_unrefActive: deprecate(
unrefActive,
'timers._unrefActive() is deprecated.' +
' Please use timeout.refresh() instead.',
'DEP0127'),
active: deprecate(
active,
'timers.active() is deprecated. Please use timeout.refresh() instead.',
'DEP0126'),
unenroll: deprecate(
unenroll,
'timers.unenroll() is deprecated. Please use clearTimeout instead.',
'DEP0096'),
enroll: deprecate(
enroll,
'timers.enroll() is deprecated. Please use setTimeout instead.',
'DEP0095')
2012-08-08 02:12:01 +00:00
};