function processImmediate() { const queue = immediateQueue; var domain, immediate; immediateQueue = L.create(); while (L.isEmpty(queue) === false) { immediate = L.shift(queue); domain = immediate.domain; if (!immediate._onImmediate) continue; if (domain) domain.enter(); immediate._callback = immediate._onImmediate; tryOnImmediate(immediate, queue); if (domain) domain.exit(); } // Only round-trip to C++ land if we have to. Calling clearImmediate() on an // immediate that's in |queue| is okay. Worst case is we make a superfluous // call to NeedImmediateCallbackSetter(). if (L.isEmpty(immediateQueue)) { process._needImmediateCallback = false; } }
// 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 = Math.trunc(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(); } item[kRefed] = null; // If active is called later, then we want to make sure not to insert again item._idleTimeout = -1; }
// 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 // TimerWrap backed list if one does not already exist for the specified timeout // duration. function insert(item, unrefed) { const msecs = item._idleTimeout; if (msecs < 0 || msecs === undefined) return; item._idleStart = TimerWrap.now(); const lists = unrefed === true ? unrefedLists : refedLists; // Use an existing list if there is one, otherwise we need to make a new one. var list = lists[msecs]; if (!list) { debug('no %d list was found in insert, creating a new one', msecs); lists[msecs] = list = createTimersList(msecs, unrefed); } if (!item[async_id_symbol] || item._destroyed) { item._destroyed = false; item[async_id_symbol] = ++async_id_fields[kAsyncIdCounter]; item[trigger_async_id_symbol] = initTriggerId(); if (async_hook_fields[kInit] > 0) { emitInit(item[async_id_symbol], 'Timeout', item[trigger_async_id_symbol], item); } } L.append(list, item); assert(!L.isEmpty(list)); // list is not empty }
// An optimization so that the try/finally only de-optimizes (since at least v8 // 4.7) what is in this smaller function. function tryOnImmediate(immediate, queue) { var threw = true; try { immediate._onImmediate(); threw = false; } finally { if (threw && !L.isEmpty(queue)) { // Handle any remaining on next tick, assuming we're still alive to do so. while (!L.isEmpty(immediateQueue)) { L.append(queue, L.shift(immediateQueue)); } immediateQueue = queue; process.nextTick(processImmediate); } } }
// 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 // TimerWrap backed list if one does not already exist for the specified timeout // duration. function insert(item, unrefed) { const msecs = item._idleTimeout; if (msecs < 0 || msecs === undefined) return; item._idleStart = TimerWrap.now(); const lists = unrefed === true ? unrefedLists : refedLists; // Use an existing list if there is one, otherwise we need to make a new one. var list = lists[msecs]; if (!list) { debug('no %d list was found in insert, creating a new one', msecs); // Make a new linked list of timers, and create a TimerWrap to schedule // processing for the list. list = new TimersList(msecs, unrefed); L.init(list); list._timer._list = list; if (unrefed === true) list._timer.unref(); list._timer.start(msecs); lists[msecs] = list; list._timer[kOnTimeout] = listOnTimeout; } L.append(list, item); assert(!L.isEmpty(list)); // list is not empty }
function listOnTimeout() { var list = this._list; var msecs = list.msecs; debug('timeout callback %d', msecs); var now = TimerWrap.now(); debug('now: %s', now); var diff, timer; 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) { this.start(msecs - diff, 0); debug('%d list wait because diff is %d', msecs, diff); return; } // The actual logic for when a timeout happens. L.remove(timer); assert(timer !== L.peek(list)); if (!timer._onTimeout) continue; var domain = timer.domain; if (domain) { // If the timer callback throws and the // domain or uncaughtException handler ignore the exception, // other timers that expire on this tick should still run. // // https://github.com/nodejs/node-v0.x-archive/issues/2631 if (domain._disposed) continue; domain.enter(); } tryOnTimeout(timer, list); if (domain) domain.exit(); } // 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 and clean up the TimerWrap C++ handle. debug('%d list empty', msecs); assert(L.isEmpty(list)); this.close(); if (list._unrefed === true) { delete unrefedLists[msecs]; } else { delete refedLists[msecs]; } }
function listOnTimeout() { var msecs = this.msecs; var list = this; debug('timeout callback %d', msecs); var now = Timer.now(); debug('now: %s', now); var diff, first, threw; while (first = L.peek(list)) { diff = now - first._idleStart; if (diff < msecs) { list.start(msecs - diff, 0); debug('%d list wait because diff is %d', msecs, diff); return; } else { L.remove(first); assert(first !== L.peek(list)); if (!first._onTimeout) continue; // v0.4 compatibility: if the timer callback throws and the // domain or uncaughtException handler ignore the exception, // other timers that expire on this tick should still run. // // https://github.com/joyent/node/issues/2631 var domain = first.domain; if (domain && domain._disposed) continue; try { if (domain) domain.enter(); threw = true; first._called = true; first._onTimeout(); if (domain) domain.exit(); threw = false; } finally { if (threw) { // We need to continue processing after domain error handling // is complete, but not by using whatever domain was left over // when the timeout threw its exception. var oldDomain = process.domain; process.domain = null; process.nextTick(listOnTimeoutNT, list); process.domain = oldDomain; } } } } debug('%d list empty', msecs); assert(L.isEmpty(list)); list.close(); delete lists[msecs]; }
// An optimization so that the try/finally only de-optimizes (since at least v8 // 4.7) what is in this smaller function. function tryOnImmediate(immediate, queue) { var threw = true; try { // make the actual call outside the try/catch to allow it to be optimized runCallback(immediate); threw = false; } finally { if (threw && !L.isEmpty(queue)) { // Handle any remaining on next tick, assuming we're still alive to do so. while (!L.isEmpty(immediateQueue)) { L.append(queue, L.shift(immediateQueue)); } immediateQueue = queue; process.nextTick(processImmediate); } } }
function processImmediate() { var queue = immediateQueue; var domain, immediate; immediateQueue = {}; L.init(immediateQueue); while (L.isEmpty(queue) === false) { immediate = L.shift(queue); domain = immediate.domain; if (domain) domain.enter(); var threw = true; try { immediate._onImmediate(); threw = false; } finally { if (threw) { if (!L.isEmpty(queue)) { // Handle any remaining on next tick, assuming we're still // alive to do so. while (!L.isEmpty(immediateQueue)) { L.append(queue, L.shift(immediateQueue)); } immediateQueue = queue; process.nextTick(processImmediate); } } } if (domain) domain.exit(); } // Only round-trip to C++ land if we have to. Calling clearImmediate() on an // immediate that's in |queue| is okay. Worst case is we make a superfluous // call to NeedImmediateCallbackSetter(). if (L.isEmpty(immediateQueue)) { process._needImmediateCallback = false; } }
exports.clearImmediate = function(immediate) { if (!immediate) return; immediate._onImmediate = undefined; L.remove(immediate); if (L.isEmpty(immediateQueue)) { process._needImmediateCallback = false; } };
// A convenience function for re-using TimerWrap handles more easily. // // This mostly exists to fix https://github.com/nodejs/node/issues/1264. // Handles in libuv take at least one `uv_run` to be registered as unreferenced. // Re-using an existing handle allows us to skip that, so that a second `uv_run` // will return no active handles, even when running `setTimeout(fn).unref()`. function reuse(item) { L.remove(item); var list = refedLists[item._idleTimeout]; // if empty - reuse the watcher if (list && L.isEmpty(list)) { debug('reuse hit'); list._timer.stop(); delete refedLists[item._idleTimeout]; return list._timer; } return null; }
// 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 // TimerWrap backed list if one does not already exist for the specified timeout // duration. function insert(item, unrefed) { const msecs = item._idleTimeout; if (msecs < 0 || msecs === undefined) return; item._idleStart = TimerWrap.now(); const lists = unrefed === true ? unrefedLists : refedLists; // Use an existing list if there is one, otherwise we need to make a new one. var list = lists[msecs]; if (!list) { debug('no %d list was found in insert, creating a new one', msecs); lists[msecs] = list = createTimersList(msecs, unrefed); } L.append(list, item); assert(!L.isEmpty(list)); // list is not empty }
[kGetEntries](name, type) { const ret = []; const list = this[kEntries]; if (!L.isEmpty(list)) { let item = L.peek(list); while (item && item !== list) { const entry = item.entry; if ((name && entry.name !== name) || (type && entry.entryType !== type)) { item = item._idlePrev; continue; } sortedInsert(ret, entry); item = item._idlePrev; } } return ret; }
exports.active = function(item) { const msecs = item._idleTimeout; if (msecs < 0 || msecs === undefined) return; item._idleStart = Timer.now(); var list; if (lists[msecs]) { list = lists[msecs]; } else { list = new Timer(); list.start(msecs, 0); L.init(list); lists[msecs] = list; list.msecs = msecs; list[kOnTimeout] = listOnTimeout; } L.append(list, item); assert(!L.isEmpty(list)); // list is not empty };
function listOnTimeout() { var list = this._list; var msecs = list.msecs; if (list.nextTick) { list.nextTick = false; process.nextTick(listOnTimeoutNT, list); return; } debug('timeout callback %d', msecs); var now = TimerWrap.now(); debug('now: %d', now); var diff, timer; 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) { var timeRemaining = msecs - (TimerWrap.now() - timer._idleStart); if (timeRemaining < 0) { timeRemaining = 1; } this.start(timeRemaining); debug('%d list wait because diff is %d', msecs, diff); return; } // The actual logic for when a timeout happens. L.remove(timer); assert(timer !== L.peek(list)); if (!timer._onTimeout) { if (async_hook_fields[kDestroy] > 0 && !timer._destroyed && typeof timer[async_id_symbol] === 'number') { emitDestroy(timer[async_id_symbol]); timer._destroyed = true; } continue; } var domain = timer.domain; if (domain) { domain.enter(); } tryOnTimeout(timer, list); if (domain) domain.exit(); } // 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 and clean up the TimerWrap C++ handle. debug('%d list empty', msecs); assert(L.isEmpty(list)); // Either refedLists[msecs] or unrefedLists[msecs] 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._unrefed === true && list === unrefedLists[msecs]) { delete unrefedLists[msecs]; } else if (list === refedLists[msecs]) { delete refedLists[msecs]; } // Do not close the underlying handle if its ownership has changed // (e.g it was unrefed in its callback). if (this.owner) return; this.close(); }
function unrefTimeout() { var now = Timer.now(); debug('unrefTimer fired'); var timeSinceLastActive; var nextTimeoutTime; var nextTimeoutDuration; var minNextTimeoutTime = TIMEOUT_MAX; var timersToTimeout = []; // The actual timer fired and has not yet been rearmed, // let's consider its next firing time is invalid for now. // It may be set to a relevant time in the future once // we scanned through the whole list of timeouts and if // we find a timeout that needs to expire. unrefTimer.when = -1; // Iterate over the list of timeouts, // call the onTimeout callback for those expired, // and rearm the actual timer if the next timeout to expire // will expire before the current actual timer. var cur = unrefList._idlePrev; while (cur !== unrefList) { timeSinceLastActive = now - cur._idleStart; if (timeSinceLastActive < cur._idleTimeout) { // This timer hasn't expired yet, but check if its expiring time is // earlier than the actual timer's expiring time nextTimeoutDuration = cur._idleTimeout - timeSinceLastActive; nextTimeoutTime = now + nextTimeoutDuration; if (minNextTimeoutTime === TIMEOUT_MAX || (nextTimeoutTime < minNextTimeoutTime)) { // We found a timeout that will expire earlier, // store its next timeout time now so that we // can rearm the actual timer accordingly when // we scanned through the whole list. minNextTimeoutTime = nextTimeoutTime; } } else { // We found a timer that expired. Do not call its _onTimeout callback // right now, as it could mutate any item of the list (including itself). // Instead, add it to another list that will be processed once the list // of current timers has been fully traversed. timersToTimeout.push(cur); } cur = cur._idlePrev; } var nbTimersToTimeout = timersToTimeout.length; for (var timerIdx = 0; timerIdx < nbTimersToTimeout; ++timerIdx) _makeTimerTimeout(timersToTimeout[timerIdx]); // Rearm the actual timer with the timeout delay // of the earliest timeout found. if (minNextTimeoutTime !== TIMEOUT_MAX) { unrefTimer.start(minNextTimeoutTime - now, 0); unrefTimer.when = minNextTimeoutTime; debug('unrefTimer rescheduled'); } else if (L.isEmpty(unrefList)) { debug('unrefList is empty'); } }