tools/node-hermes/nodelib/internal/timers.js (29 lines of code) (raw):
'use strict'; // HOW and WHY the timers implementation works the way it does.
//
// Timers are crucial to Node.js. Internally, any TCP I/O connection creates a
// timer so that we can time out of connections. Additionally, many user
// libraries and applications also use timers. As such there may be a
// significantly large amount of timeouts scheduled at any given time.
// Therefore, it is very important that the timers implementation is performant
// and efficient.
//
// Note: It is suggested you first read through the lib/internal/linkedlist.js
// linked list implementation, since timers depend on it extensively. It can be
// somewhat counter-intuitive at first, as it is not actually a class. Instead,
// it is a set of helpers that operate on an existing object.
//
// In order to be as performant as possible, the architecture and data
// structures are designed so that they are optimized to handle the following
// use cases as efficiently as possible:
// - Adding a new timer. (insert)
// - Removing an existing timer. (remove)
// - Handling a timer timing out. (timeout)
//
// Whenever possible, the implementation tries to make the complexity of these
// operations as close to constant-time as possible.
// (So that performance is not impacted by the number of scheduled timers.)
//
// Object maps are kept which contain linked lists keyed by their duration in
// milliseconds.
//
/* eslint-disable node-core/non-ascii-character */
//
// ╔════ > Object Map
// ║
// ╠══
// ║ lists: { '40': { }, '320': { etc } } (keys of millisecond duration)
// ╚══ ┌────┘
// │
// ╔══ │
// ║ TimersList { _idleNext: { }, _idlePrev: (self) }
// ║ ┌────────────────┘
// ║ ╔══ │ ^
// ║ ║ { _idleNext: { }, _idlePrev: { }, _onTimeout: (callback) }
// ║ ║ ┌───────────┘
// ║ ║ │ ^
// ║ ║ { _idleNext: { etc }, _idlePrev: { }, _onTimeout: (callback) }
// ╠══ ╠══
// ║ ║
// ║ ╚════ > Actual JavaScript timeouts
// ║
// ╚════ > Linked List
//
/* eslint-enable node-core/non-ascii-character */
//
// With this, virtually constant-time insertion (append), removal, and timeout
// is possible in the JavaScript layer. Any one list of timers is able to be
// sorted by just appending to it because all timers within share the same
// duration. Therefore, any timer added later will always have been scheduled to
// timeout later, thus only needing to be appended.
// Removal from an object-property linked list is also virtually constant-time
// as can be seen in the lib/internal/linkedlist.js implementation.
// Timeouts only need to process any timers currently due to expire, which will
// always be at the beginning of the list for reasons stated above. Any timers
// after the first one encountered that does not yet need to timeout will also
// always be due to timeout at a later time.
//
// Less-than constant time operations are thus contained in two places:
// The PriorityQueue — an efficient binary heap implementation that does all
// operations in worst-case O(log n) time — which manages the order of expiring
// Timeout lists and the object map lookup of a specific list by the duration of
// timers within (or creation of a new list). However, these operations combined
// have shown to be trivial in comparison to other timers architectures.
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var _primordials = primordials,
MathMax = _primordials.MathMax,
MathTrunc = _primordials.MathTrunc,
NumberIsFinite = _primordials.NumberIsFinite,
NumberMIN_SAFE_INTEGER = _primordials.NumberMIN_SAFE_INTEGER,
ObjectCreate = _primordials.ObjectCreate,
ReflectApply = _primordials.ReflectApply,
_Symbol = _primordials.Symbol;
// var _internalBinding = internalBinding('timers'),
// scheduleTimer = _internalBinding.scheduleTimer,
// toggleTimerRef = _internalBinding.toggleTimerRef,
// getLibuvNow = _internalBinding.getLibuvNow,
// immediateInfo = _internalBinding.immediateInfo,
// toggleImmediateRef = _internalBinding.toggleImmediateRef;
// var _require = require('internal/async_hooks'),
// getDefaultTriggerAsyncId = _require.getDefaultTriggerAsyncId,
// newAsyncId = _require.newAsyncId,
// initHooksExist = _require.initHooksExist,
// destroyHooksExist = _require.destroyHooksExist,
// emitInit = _require.emitInit,
// emitBefore = _require.emitBefore,
// emitAfter = _require.emitAfter,
// emitDestroy = _require.emitDestroy; // Symbols for storing async id state.
// var async_id_symbol = _Symbol('asyncId');
// var trigger_async_id_symbol = _Symbol('triggerId');
// var kHasPrimitive = _Symbol('kHasPrimitive');
// var ERR_OUT_OF_RANGE = require('internal/errors').codes.ERR_OUT_OF_RANGE;
// var _require2 = require('internal/validators'),
// validateCallback = _require2.validateCallback,
// validateNumber = _require2.validateNumber;
// var L = require('internal/linkedlist');
// var PriorityQueue = require('internal/priority_queue');
var _require3 = require('internal/util/inspect'),
inspect = _require3.inspect;
var debug = require('internal/util/debuglog').debuglog('timer', function (fn) {
debug = fn;
}); // *Must* match Environment::ImmediateInfo::Fields in src/env.h.
// var kCount = 0;
// var kRefCount = 1;
// var kHasOutstanding = 2; // Timeout values > TIMEOUT_MAX are set to 1.
// var TIMEOUT_MAX = Math.pow(2, 31) - 1;
// var timerListId = NumberMIN_SAFE_INTEGER;
// var kRefed = _Symbol('refed'); // Create a single linked list instance only once at startup
// var immediateQueue = new ImmediateList();
// var nextExpiry = Infinity;
// var refCount = 0; // This is a priority queue with a custom sorting function that first compares
// // the expiry times of two lists and if they're the same then compares their
// // individual IDs to determine which list was created first.
// var timerListQueue = new PriorityQueue(compareTimersLists, setPosition); // Object map containing linked lists of timers, keyed and sorted by their
// // duration in milliseconds.
// //
// // - key = time in milliseconds
// // - value = linked list
// var timerListMap = ObjectCreate(null);
// function initAsyncResource(resource, type) {
// var asyncId = resource[async_id_symbol] = newAsyncId();
// var triggerAsyncId = resource[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
// if (initHooksExist()) emitInit(asyncId, type, triggerAsyncId, resource);
// } // Timer constructor function.
// // The entire prototype is defined in lib/timers.js
// function Timeout(callback, after, args, isRepeat, isRefed) {
// after *= 1; // Coalesce to number or NaN
// if (!(after >= 1 && after <= TIMEOUT_MAX)) {
// if (after > TIMEOUT_MAX) {
// process.emitWarning("".concat(after, " does not fit into") + ' a 32-bit signed integer.' + '\nTimeout duration was set to 1.', 'TimeoutOverflowWarning');
// }
// after = 1; // Schedule on next tick, follows browser behavior
// }
// this._idleTimeout = after;
// this._idlePrev = this;
// this._idleNext = this;
// this._idleStart = null; // This must be set to null first to avoid function tracking
// // on the hidden class, revisit in V8 versions after 6.2
// this._onTimeout = null;
// this._onTimeout = callback;
// this._timerArgs = args;
// this._repeat = isRepeat ? after : null;
// this._destroyed = false;
// if (isRefed) incRefCount();
// this[kRefed] = isRefed;
// this[kHasPrimitive] = false;
// initAsyncResource(this, 'Timeout');
// } // Make sure the linked list only shows the minimal necessary information.
// Timeout.prototype[inspect.custom] = function (_, options) {
// return inspect(this, _objectSpread(_objectSpread({}, options), {}, {
// // Only inspect one level.
// depth: 0,
// // It should not recurse.
// customInspect: false
// }));
// };
// Timeout.prototype.refresh = function () {
// if (this[kRefed]) active(this);else unrefActive(this);
// return this;
// };
// Timeout.prototype.unref = function () {
// if (this[kRefed]) {
// this[kRefed] = false;
// if (!this._destroyed) decRefCount();
// }
// return this;
// };
// Timeout.prototype.ref = function () {
// if (!this[kRefed]) {
// this[kRefed] = true;
// if (!this._destroyed) incRefCount();
// }
// return this;
// };
// Timeout.prototype.hasRef = function () {
// return this[kRefed];
// };
// function TimersList(expiry, msecs) {
// this._idleNext = this; // Create the list with the linkedlist properties to
// this._idlePrev = this; // Prevent any unnecessary hidden class changes.
// this.expiry = expiry;
// this.id = timerListId++;
// this.msecs = msecs;
// this.priorityQueuePosition = null;
// } // Make sure the linked list only shows the minimal necessary information.
// TimersList.prototype[inspect.custom] = function (_, options) {
// return inspect(this, _objectSpread(_objectSpread({}, options), {}, {
// // Only inspect one level.
// depth: 0,
// // It should not recurse.
// customInspect: false
// }));
// }; // A linked list for storing `setImmediate()` requests
// function ImmediateList() {
// this.head = null;
// this.tail = null;
// } // Appends an item to the end of the linked list, adjusting the current tail's
// // next pointer and the item's previous pointer where applicable
// ImmediateList.prototype.append = function (item) {
// if (this.tail !== null) {
// this.tail._idleNext = item;
// item._idlePrev = this.tail;
// } else {
// this.head = item;
// }
// this.tail = item;
// }; // Removes an item from the linked list, adjusting the pointers of adjacent
// // items and the linked list's head or tail pointers as necessary
// ImmediateList.prototype.remove = function (item) {
// if (item._idleNext) {
// item._idleNext._idlePrev = item._idlePrev;
// }
// if (item._idlePrev) {
// item._idlePrev._idleNext = item._idleNext;
// }
// if (item === this.head) this.head = item._idleNext;
// if (item === this.tail) this.tail = item._idlePrev;
// item._idleNext = null;
// item._idlePrev = null;
// };
// function incRefCount() {
// if (refCount++ === 0) toggleTimerRef(true);
// }
// function decRefCount() {
// if (--refCount === 0) toggleTimerRef(false);
// } // Schedule or re-schedule a timer.
// // The item must have been enroll()'d first.
// function active(item) {
// insertGuarded(item, true);
// } // Internal APIs that need timeouts should use `unrefActive()` instead of
// // `active()` so that they do not unnecessarily keep the process open.
// function unrefActive(item) {
// insertGuarded(item, false);
// } // The underlying logic for scheduling or re-scheduling a timer.
// //
// // Appends a timer onto the end of an existing timers list, or creates a new
// // list if one does not already exist for the specified timeout duration.
// function insertGuarded(item, refed, start) {
// var msecs = item._idleTimeout;
// if (msecs < 0 || msecs === undefined) return;
// insert(item, msecs, start);
// var isDestroyed = item._destroyed;
// if (isDestroyed || !item[async_id_symbol]) {
// item._destroyed = false;
// initAsyncResource(item, 'Timeout');
// }
// if (isDestroyed) {
// if (refed) incRefCount();
// } else if (refed === !item[kRefed]) {
// if (refed) incRefCount();else decRefCount();
// }
// item[kRefed] = refed;
// }
// function insert(item, msecs) {
// var start = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : getLibuvNow();
// // Truncate so that accuracy of sub-millisecond timers is not assumed.
// msecs = MathTrunc(msecs);
// item._idleStart = start; // Use an existing list if there is one, otherwise we need to make a new one.
// var list = timerListMap[msecs];
// if (list === undefined) {
// debug('no %d list was found in insert, creating a new one', msecs);
// var expiry = start + msecs;
// timerListMap[msecs] = list = new TimersList(expiry, msecs);
// timerListQueue.insert(list);
// if (nextExpiry > expiry) {
// scheduleTimer(msecs);
// nextExpiry = expiry;
// }
// }
// L.append(list, item);
// }
// function setUnrefTimeout(callback, after) {
// // Type checking identical to setTimeout()
// validateCallback(callback);
// var timer = new Timeout(callback, after, undefined, false, false);
// insert(timer, timer._idleTimeout);
// return timer;
// } // Type checking used by timers.enroll() and Socket#setTimeout()
// function getTimerDuration(msecs, name) {
// validateNumber(msecs, name);
// if (msecs < 0 || !NumberIsFinite(msecs)) {
// throw new ERR_OUT_OF_RANGE(name, 'a non-negative finite number', msecs);
// } // Ensure that msecs fits into signed int32
// if (msecs > TIMEOUT_MAX) {
// process.emitWarning("".concat(msecs, " does not fit into a 32-bit signed integer.") + "\nTimer duration was truncated to ".concat(TIMEOUT_MAX, "."), 'TimeoutOverflowWarning');
// return TIMEOUT_MAX;
// }
// return msecs;
// }
// function compareTimersLists(a, b) {
// var expiryDiff = a.expiry - b.expiry;
// if (expiryDiff === 0) {
// if (a.id < b.id) return -1;
// if (a.id > b.id) return 1;
// }
// return expiryDiff;
// }
// function setPosition(node, pos) {
// node.priorityQueuePosition = pos;
// }
// function getTimerCallbacks(runNextTicks) {
// // If an uncaught exception was thrown during execution of immediateQueue,
// // this queue will store all remaining Immediates that need to run upon
// // resolution of all error handling (if process is still alive).
// var outstandingQueue = new ImmediateList();
// function processImmediate() {
// var queue = outstandingQueue.head !== null ? outstandingQueue : immediateQueue;
// var immediate = queue.head; // Clear the linked list early in case new `setImmediate()`
// // calls occur while immediate callbacks are executed
// if (queue !== outstandingQueue) {
// queue.head = queue.tail = null;
// immediateInfo[kHasOutstanding] = 1;
// }
// var prevImmediate;
// var ranAtLeastOneImmediate = false;
// while (immediate !== null) {
// if (ranAtLeastOneImmediate) runNextTicks();else ranAtLeastOneImmediate = true; // It's possible for this current Immediate to be cleared while executing
// // the next tick queue above, which means we need to use the previous
// // Immediate's _idleNext which is guaranteed to not have been cleared.
// if (immediate._destroyed) {
// outstandingQueue.head = immediate = prevImmediate._idleNext;
// continue;
// }
// immediate._destroyed = true;
// immediateInfo[kCount]--;
// if (immediate[kRefed]) immediateInfo[kRefCount]--;
// immediate[kRefed] = null;
// prevImmediate = immediate;
// var asyncId = immediate[async_id_symbol];
// emitBefore(asyncId, immediate[trigger_async_id_symbol], immediate);
// try {
// var _immediate;
// var argv = immediate._argv;
// if (!argv) immediate._onImmediate();else (_immediate = immediate)._onImmediate.apply(_immediate, _toConsumableArray(argv));
// } finally {
// immediate._onImmediate = null;
// if (destroyHooksExist()) emitDestroy(asyncId);
// outstandingQueue.head = immediate = immediate._idleNext;
// }
// emitAfter(asyncId);
// }
// if (queue === outstandingQueue) outstandingQueue.head = null;
// immediateInfo[kHasOutstanding] = 0;
// }
// function processTimers(now) {
// debug('process timer lists %d', now);
// nextExpiry = Infinity;
// var list;
// var ranAtLeastOneList = false;
// while (list = timerListQueue.peek()) {
// if (list.expiry > now) {
// nextExpiry = list.expiry;
// return refCount > 0 ? nextExpiry : -nextExpiry;
// }
// if (ranAtLeastOneList) runNextTicks();else ranAtLeastOneList = true;
// listOnTimeout(list, now);
// }
// return 0;
// }
// function listOnTimeout(list, now) {
// var msecs = list.msecs;
// debug('timeout callback %d', msecs);
// var ranAtLeastOneTimer = false;
// var timer;
// while (timer = L.peek(list)) {
// var diff = now - timer._idleStart; // Check if this loop iteration is too early for the next timer.
// // This happens if there are more timers scheduled for later in the list.
// if (diff < msecs) {
// list.expiry = MathMax(timer._idleStart + msecs, now + 1);
// list.id = timerListId++;
// timerListQueue.percolateDown(1);
// debug('%d list wait because diff is %d', msecs, diff);
// return;
// }
// if (ranAtLeastOneTimer) runNextTicks();else ranAtLeastOneTimer = true; // The actual logic for when a timeout happens.
// L.remove(timer);
// var asyncId = timer[async_id_symbol];
// if (!timer._onTimeout) {
// if (!timer._destroyed) {
// timer._destroyed = true;
// if (timer[kRefed]) refCount--;
// if (destroyHooksExist()) emitDestroy(asyncId);
// }
// continue;
// }
// emitBefore(asyncId, timer[trigger_async_id_symbol], timer);
// var start = void 0;
// if (timer._repeat) start = getLibuvNow();
// try {
// var args = timer._timerArgs;
// if (args === undefined) timer._onTimeout();else ReflectApply(timer._onTimeout, timer, args);
// } finally {
// if (timer._repeat && timer._idleTimeout !== -1) {
// timer._idleTimeout = timer._repeat;
// insert(timer, timer._idleTimeout, start);
// } else if (!timer._idleNext && !timer._idlePrev && !timer._destroyed) {
// timer._destroyed = true;
// if (timer[kRefed]) refCount--;
// if (destroyHooksExist()) emitDestroy(asyncId);
// }
// }
// emitAfter(asyncId);
// } // If `L.peek(list)` returned nothing, the list was either empty or we have
// // called all of the timer timeouts.
// // As such, we can remove the list from the object map and
// // the PriorityQueue.
// debug('%d list empty', msecs); // The current list may have been removed and recreated since the reference
// // to `list` was created. Make sure they're the same instance of the list
// // before destroying.
// if (list === timerListMap[msecs]) {
// delete timerListMap[msecs];
// timerListQueue.shift();
// }
// }
// return {
// processImmediate: processImmediate,
// processTimers: processTimers
// };
// }
// var Immediate = /*#__PURE__*/function () {
// function Immediate(callback, args) {
// _classCallCheck(this, Immediate);
// 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);
// }
// _createClass(Immediate, [{
// key: "ref",
// value: function ref() {
// if (this[kRefed] === false) {
// this[kRefed] = true;
// if (immediateInfo[kRefCount]++ === 0) toggleImmediateRef(true);
// }
// return this;
// }
// }, {
// key: "unref",
// value: function unref() {
// if (this[kRefed] === true) {
// this[kRefed] = false;
// if (--immediateInfo[kRefCount] === 0) toggleImmediateRef(false);
// }
// return this;
// }
// }, {
// key: "hasRef",
// value: function hasRef() {
// return !!this[kRefed];
// }
// }]);
// return Immediate;
// }();
module.exports = {
// TIMEOUT_MAX: TIMEOUT_MAX,
kTimeout: _Symbol('timeout'),
// For hiding Timeouts on other internals.
// async_id_symbol: async_id_symbol,
// trigger_async_id_symbol: trigger_async_id_symbol,
// Timeout: Timeout,
// Immediate: Immediate,
// kRefed: kRefed,
// kHasPrimitive: kHasPrimitive,
// initAsyncResource: initAsyncResource,
// setUnrefTimeout: setUnrefTimeout,
// getTimerDuration: getTimerDuration,
// immediateQueue: immediateQueue,
// getTimerCallbacks: getTimerCallbacks,
// immediateInfoFields: {
// kCount: kCount,
// kRefCount: kRefCount,
// kHasOutstanding: kHasOutstanding
// },
// active: active,
// unrefActive: unrefActive,
// insert: insert,
// timerListMap: timerListMap,
// timerListQueue: timerListQueue,
// decRefCount: decRefCount,
// incRefCount: incRefCount
};