From 1f4a5bcc98fb764daab701cc0e177a2fb1ceabe8 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 11 Mar 2019 19:08:35 +0800 Subject: [PATCH] timers: refactor timer callback initialization This patch: - Moves the timer callback initialization into bootstrap/node.js, documents when they will be called, and make the dependency on process._tickCallback explicit. - Moves the initialization of tick callbacks and timer callbacks to the end of the bootstrap to make sure the operations done before those initializations are synchronous. - Moves more internals into internal/timers.js from timers.js. PR-URL: https://github.com/nodejs/node/pull/26583 Refs: https://github.com/nodejs/node/issues/26546 Reviewed-By: Anna Henningsen --- lib/internal/bootstrap/node.js | 50 ++-- lib/internal/timers.js | 444 +++++++++++++++++++++++++++++++-- lib/timers.js | 401 ++--------------------------- test/message/timeout_throw.out | 4 +- 4 files changed, 469 insertions(+), 430 deletions(-) diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 0176eae88621f5..44996688a5a9fd 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -113,24 +113,6 @@ if (isMainThread) { process.exit = wrapped.exit; } -const { - emitWarning -} = require('internal/process/warning'); - -process.emitWarning = emitWarning; - -const { - nextTick, - runNextTicks -} = require('internal/process/task_queues').setupTaskQueue(); - -process.nextTick = nextTick; -// Used to emulate a tick manually in the JS land. -// A better name for this function would be `runNextTicks` but -// it has been exposed to the process object so we keep this legacy name -// TODO(joyeecheung): either remove it or make it public -process._tickCallback = runNextTicks; - const credentials = internalBinding('credentials'); if (credentials.implementsPosixCredentials) { process.getuid = credentials.getuid; @@ -186,6 +168,11 @@ if (config.hasInspector) { internalBinding('inspector').registerAsyncHook(enable, disable); } +const { + setupTaskQueue, + queueMicrotask +} = require('internal/process/task_queues'); + if (!config.noBrowserGlobals) { // Override global console from the one provided by the VM // to the one implemented by Node.js @@ -317,6 +304,32 @@ Object.defineProperty(process, 'features', { hasUncaughtExceptionCaptureCallback; } +const { emitWarning } = require('internal/process/warning'); +process.emitWarning = emitWarning; + +// We initialize the tick callbacks and the timer callbacks last during +// bootstrap to make sure that any operation done before this are synchronous. +// If any ticks or timers are scheduled before this they are unlikely to work. +{ + const { nextTick, runNextTicks } = setupTaskQueue(); + process.nextTick = nextTick; + // Used to emulate a tick manually in the JS land. + // A better name for this function would be `runNextTicks` but + // it has been exposed to the process object so we keep this legacy name + // TODO(joyeecheung): either remove it or make it public + process._tickCallback = runNextTicks; + + const { getTimerCallbacks } = require('internal/timers'); + const { setupTimers } = internalBinding('timers'); + const { processImmediate, processTimers } = getTimerCallbacks(runNextTicks); + // Sets two per-Environment callbacks that will be run from libuv: + // - processImmediate will be run in the callback of the per-Environment + // check handle. + // - processTimers will be run in the callback of the per-Environment timer. + setupTimers(processImmediate, processTimers); + // Note: only after this point are the timers effective +} + function setupProcessObject() { const EventEmitter = require('events'); const origProcProto = Object.getPrototypeOf(process); @@ -430,7 +443,6 @@ function setupQueueMicrotask() { get() { process.emitWarning('queueMicrotask() is experimental.', 'ExperimentalWarning'); - const { queueMicrotask } = require('internal/process/task_queues'); Object.defineProperty(global, 'queueMicrotask', { value: queueMicrotask, diff --git a/lib/internal/timers.js b/lib/internal/timers.js index cbd8751f1e77b0..eaacc01c205080 100644 --- a/lib/internal/timers.js +++ b/lib/internal/timers.js @@ -1,11 +1,24 @@ 'use strict'; +const { + scheduleTimer, + toggleTimerRef, + getLibuvNow, + immediateInfo +} = internalBinding('timers'); + const { getDefaultTriggerAsyncId, newAsyncId, initHooksExist, - emitInit + destroyHooksExist, + // The needed emit*() functions. + emitInit, + emitBefore, + emitAfter, + emitDestroy } = require('internal/async_hooks'); + // Symbols for storing async id state. const async_id_symbol = Symbol('asyncId'); const trigger_async_id_symbol = Symbol('triggerId'); @@ -16,32 +29,47 @@ const { } = require('internal/errors').codes; const { validateNumber } = require('internal/validators'); -const { inspect } = require('util'); +const L = require('internal/linkedlist'); +const PriorityQueue = require('internal/priority_queue'); + +const { inspect } = require('internal/util/inspect'); +let debuglog; +function debug(...args) { + if (!debuglog) { + debuglog = require('internal/util/debuglog').debuglog('timer'); + } + debuglog(...args); +} + +// *Must* match Environment::ImmediateInfo::Fields in src/env.h. +const kCount = 0; +const kRefCount = 1; +const kHasOutstanding = 2; // Timeout values > TIMEOUT_MAX are set to 1. const TIMEOUT_MAX = 2 ** 31 - 1; +let timerListId = Number.MIN_SAFE_INTEGER; + const kRefed = Symbol('refed'); -module.exports = { - TIMEOUT_MAX, - kTimeout: Symbol('timeout'), // For hiding Timeouts on other internals. - async_id_symbol, - trigger_async_id_symbol, - Timeout, - kRefed, - initAsyncResource, - setUnrefTimeout, - validateTimerDuration -}; +// Create a single linked list instance only once at startup +const immediateQueue = new ImmediateList(); -var timers; -function getTimers() { - if (timers === undefined) { - timers = require('timers'); - } - return timers; -} +let nextExpiry = Infinity; +let 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. +const 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 +const timerListMap = Object.create(null); function initAsyncResource(resource, type) { const asyncId = resource[async_id_symbol] = newAsyncId(); @@ -95,13 +123,157 @@ Timeout.prototype[inspect.custom] = function(_, options) { Timeout.prototype.refresh = function() { if (this[kRefed]) - getTimers().active(this); + active(this); else - getTimers()._unrefActive(this); + unrefActive(this); return this; }; +Timeout.prototype.unref = function() { + if (this[kRefed]) { + this[kRefed] = false; + decRefCount(); + } + return this; +}; + +Timeout.prototype.ref = function() { + if (this[kRefed] === false) { + this[kRefed] = true; + 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, { + ...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 +// previous and next pointers 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 !== null) { + item._idleNext._idlePrev = item._idlePrev; + } + + if (item._idlePrev !== null) { + 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) { + insert(item, true, getLibuvNow()); +} + +// Internal APIs that need timeouts should use `unrefActive()` instead of +// `active()` so that they do not unnecessarily keep the process open. +function unrefActive(item) { + insert(item, false, getLibuvNow()); +} + +// 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 insert(item, refed, start) { + let msecs = item._idleTimeout; + if (msecs < 0 || msecs === undefined) + return; + + // Truncate so that accuracy of sub-milisecond timers is not assumed. + msecs = Math.trunc(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); + const expiry = start + msecs; + timerListMap[msecs] = list = new TimersList(expiry, msecs); + timerListQueue.insert(list); + + if (nextExpiry > expiry) { + scheduleTimer(msecs); + nextExpiry = expiry; + } + } + + if (!item[async_id_symbol] || item._destroyed) { + item._destroyed = false; + initAsyncResource(item, 'Timeout'); + } + + if (refed === !item[kRefed]) { + if (refed) + incRefCount(); + else + decRefCount(); + } + item[kRefed] = refed; + + L.append(list, item); +} + function setUnrefTimeout(callback, after) { // Type checking identical to setTimeout() if (typeof callback !== 'function') { @@ -109,7 +281,7 @@ function setUnrefTimeout(callback, after) { } const timer = new Timeout(callback, after, undefined, false); - getTimers()._unrefActive(timer); + unrefActive(timer); return timer; } @@ -131,3 +303,229 @@ function validateTimerDuration(msecs) { return msecs; } + +function compareTimersLists(a, b) { + const 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). + const outstandingQueue = new ImmediateList(); + + function processImmediate() { + const 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; + } + + let prevImmediate; + let 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; + + const asyncId = immediate[async_id_symbol]; + emitBefore(asyncId, immediate[trigger_async_id_symbol]); + + try { + const argv = immediate._argv; + if (!argv) + immediate._onImmediate(); + else + Reflect.apply(immediate._onImmediate, immediate, 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; + + let list; + let 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) { + const msecs = list.msecs; + + debug('timeout callback %d', msecs); + + var diff, timer; + let ranAtLeastOneTimer = false; + while (timer = L.peek(list)) { + 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 = Math.max(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); + + const asyncId = timer[async_id_symbol]; + + if (!timer._onTimeout) { + if (timer[kRefed]) + refCount--; + timer[kRefed] = null; + + if (destroyHooksExist() && !timer._destroyed) { + emitDestroy(asyncId); + timer._destroyed = true; + } + continue; + } + + emitBefore(asyncId, timer[trigger_async_id_symbol]); + + let start; + if (timer._repeat) + start = getLibuvNow(); + + try { + const args = timer._timerArgs; + if (args === undefined) + timer._onTimeout(); + else + Reflect.apply(timer._onTimeout, timer, args); + } finally { + if (timer._repeat && timer._idleTimeout !== -1) { + timer._idleTimeout = timer._repeat; + if (start === undefined) + start = getLibuvNow(); + insert(timer, timer[kRefed], start); + } else { + if (timer[kRefed]) + refCount--; + timer[kRefed] = null; + + if (destroyHooksExist() && !timer._destroyed) { + emitDestroy(timer[async_id_symbol]); + timer._destroyed = true; + } + } + } + + 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, + processTimers + }; +} + +module.exports = { + TIMEOUT_MAX, + kTimeout: Symbol('timeout'), // For hiding Timeouts on other internals. + async_id_symbol, + trigger_async_id_symbol, + Timeout, + kRefed, + initAsyncResource, + setUnrefTimeout, + validateTimerDuration, + immediateQueue, + getTimerCallbacks, + immediateInfoFields: { + kCount, + kRefCount, + kHasOutstanding + }, + active, + unrefActive, + timerListMap, + timerListQueue, + decRefCount, + incRefCount +}; diff --git a/lib/timers.js b/lib/timers.js index bb82c29b827009..5891c2b7dc279b 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -22,28 +22,31 @@ 'use strict'; const { - getLibuvNow, - setupTimers, - scheduleTimer, - toggleTimerRef, immediateInfo, toggleImmediateRef } = internalBinding('timers'); const L = require('internal/linkedlist'); -const PriorityQueue = require('internal/priority_queue'); const { async_id_symbol, - trigger_async_id_symbol, Timeout, + decRefCount, + immediateInfoFields: { + kCount, + kRefCount + }, kRefed, initAsyncResource, - validateTimerDuration + validateTimerDuration, + timerListMap, + timerListQueue, + immediateQueue, + active, + unrefActive } = require('internal/timers'); const { promisify: { custom: customPromisify }, deprecate } = require('internal/util'); -const { inspect } = require('internal/util/inspect'); const { ERR_INVALID_CALLBACK } = require('internal/errors').codes; let debuglog; @@ -57,20 +60,9 @@ function debug(...args) { const { destroyHooksExist, // The needed emit*() functions. - emitBefore, - emitAfter, emitDestroy } = require('internal/async_hooks'); -// *Must* match Environment::ImmediateInfo::Fields in src/env.h. -const kCount = 0; -const kRefCount = 1; -const kHasOutstanding = 2; - -// Call into C++ to assign callbacks that are responsible for processing -// Immediates and TimerLists. -setupTimers(processImmediate, processTimers); - // 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 @@ -143,236 +135,6 @@ setupTimers(processImmediate, processTimers); // timers within (or creation of a new list). However, these operations combined // have shown to be trivial in comparison to other timers architectures. - -// Object map containing linked lists of timers, keyed and sorted by their -// duration in milliseconds. -// -// - key = time in milliseconds -// - value = linked list -const lists = Object.create(null); - -// 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. -const queue = new PriorityQueue(compareTimersLists, setPosition); - -function compareTimersLists(a, b) { - const 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; -} - -let nextExpiry = Infinity; - -let timerListId = Number.MIN_SAFE_INTEGER; -let refCount = 0; - -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) { - insert(item, true, getLibuvNow()); -} - -// Internal APIs that need timeouts should use `_unrefActive()` instead of -// `active()` so that they do not unnecessarily keep the process open. -function _unrefActive(item) { - insert(item, false, getLibuvNow()); -} - -// 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 insert(item, refed, start) { - let msecs = item._idleTimeout; - if (msecs < 0 || msecs === undefined) - return; - - // Truncate so that accuracy of sub-milisecond timers is not assumed. - msecs = Math.trunc(msecs); - - item._idleStart = start; - - // Use an existing list if there is one, otherwise we need to make a new one. - var list = lists[msecs]; - if (list === undefined) { - debug('no %d list was found in insert, creating a new one', msecs); - const expiry = start + msecs; - lists[msecs] = list = new TimersList(expiry, msecs); - queue.insert(list); - - if (nextExpiry > expiry) { - scheduleTimer(msecs); - nextExpiry = expiry; - } - } - - if (!item[async_id_symbol] || item._destroyed) { - item._destroyed = false; - initAsyncResource(item, 'Timeout'); - } - - if (refed === !item[kRefed]) { - if (refed) - incRefCount(); - else - decRefCount(); - } - item[kRefed] = refed; - - L.append(list, item); -} - -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, { - ...options, - // Only inspect one level. - depth: 0, - // It should not recurse. - customInspect: false - }); -}; - -const { _tickCallback: runNextTicks } = process; -function processTimers(now) { - debug('process timer lists %d', now); - nextExpiry = Infinity; - - let list; - let ranAtLeastOneList = false; - while (list = queue.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) { - const msecs = list.msecs; - - debug('timeout callback %d', msecs); - - var diff, timer; - let ranAtLeastOneTimer = false; - while (timer = L.peek(list)) { - 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 = Math.max(timer._idleStart + msecs, now + 1); - list.id = timerListId++; - queue.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); - - const asyncId = timer[async_id_symbol]; - - if (!timer._onTimeout) { - if (timer[kRefed]) - refCount--; - timer[kRefed] = null; - - if (destroyHooksExist() && !timer._destroyed) { - emitDestroy(asyncId); - timer._destroyed = true; - } - continue; - } - - emitBefore(asyncId, timer[trigger_async_id_symbol]); - - let start; - if (timer._repeat) - start = getLibuvNow(); - - try { - const args = timer._timerArgs; - if (args === undefined) - timer._onTimeout(); - else - Reflect.apply(timer._onTimeout, timer, args); - } finally { - if (timer._repeat && timer._idleTimeout !== -1) { - timer._idleTimeout = timer._repeat; - if (start === undefined) - start = getLibuvNow(); - insert(timer, timer[kRefed], start); - } else { - if (timer[kRefed]) - refCount--; - timer[kRefed] = null; - - if (destroyHooksExist() && !timer._destroyed) { - emitDestroy(timer[async_id_symbol]); - timer._destroyed = true; - } - } - } - - 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 === lists[msecs]) { - delete lists[msecs]; - queue.shift(); - } -} - - // Remove a timer. Cancels the timeout and resets the relevant timer properties. function unenroll(item) { // Fewer checks may be possible, but these cover everything. @@ -393,11 +155,11 @@ function unenroll(item) { if (item[kRefed]) { // Compliment truncation during insert(). const msecs = Math.trunc(item._idleTimeout); - const list = lists[msecs]; + const list = timerListMap[msecs]; if (list !== undefined && L.isEmpty(list)) { debug('unenroll: list empty'); - queue.removeAt(list.priorityQueuePosition); - delete lists[list.msecs]; + timerListQueue.removeAt(list.priorityQueuePosition); + delete timerListMap[list.msecs]; } decRefCount(); @@ -513,144 +275,11 @@ function clearInterval(timer) { clearTimeout(timer); } - -Timeout.prototype.unref = function() { - if (this[kRefed]) { - this[kRefed] = false; - decRefCount(); - } - return this; -}; - -Timeout.prototype.ref = function() { - if (this[kRefed] === false) { - this[kRefed] = true; - incRefCount(); - } - return this; -}; - -Timeout.prototype.hasRef = function() { - return !!this[kRefed]; -}; - Timeout.prototype.close = function() { clearTimeout(this); return this; }; - -// 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 -// previous and next pointers 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 !== null) { - item._idleNext._idlePrev = item._idlePrev; - } - - if (item._idlePrev !== null) { - 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; -}; - -// Create a single linked list instance only once at startup -const immediateQueue = new ImmediateList(); - -// 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). -const outstandingQueue = new ImmediateList(); - - -function processImmediate() { - const 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; - } - - let prevImmediate; - let 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; - - const asyncId = immediate[async_id_symbol]; - emitBefore(asyncId, immediate[trigger_async_id_symbol]); - - try { - const argv = immediate._argv; - if (!argv) - immediate._onImmediate(); - else - Reflect.apply(immediate._onImmediate, immediate, 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; -} - - const Immediate = class Immediate { constructor(callback, args) { this._idleNext = null; @@ -747,7 +376,7 @@ function clearImmediate(immediate) { } module.exports = { - _unrefActive, + _unrefActive: unrefActive, active, setTimeout, clearTimeout, diff --git a/test/message/timeout_throw.out b/test/message/timeout_throw.out index d97f8b1887cfe4..b27ad167710f9d 100644 --- a/test/message/timeout_throw.out +++ b/test/message/timeout_throw.out @@ -3,5 +3,5 @@ ^ ReferenceError: undefined_reference_error_maker is not defined at Timeout._onTimeout (*test*message*timeout_throw.js:*:*) - at listOnTimeout (timers.js:*:*) - at processTimers (timers.js:*:*) + at listOnTimeout (internal/timers.js:*:*) + at processTimers (internal/timers.js:*:*)