Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

timers: improve linked list performance #8961

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/internal/linkedlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ exports.create = create;

// show the most idle item
function peek(list) {
if (list._idlePrev == list) return null;
if (list._idlePrev === list) return null;
return list._idlePrev;
}
exports.peek = peek;
Expand Down
64 changes: 47 additions & 17 deletions lib/timers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
'use strict';

const TimerWrap = process.binding('timer_wrap').Timer;
const L = require('internal/linkedlist');
const assert = require('assert');
const util = require('util');
const debug = util.debuglog('timer');
const kOnTimeout = TimerWrap.kOnTimeout | 0;
Expand Down Expand Up @@ -131,15 +129,28 @@ function insert(item, unrefed) {
lists[msecs] = list = createTimersList(msecs, unrefed);
}

L.append(list, item);
assert(!L.isEmpty(list)); // list is not empty
// put item at end of list
if (item._idleNext) {
item._idleNext._idlePrev = item._idlePrev;
}
if (item._idlePrev) {
item._idlePrev._idleNext = item._idleNext;
}

// items are linked with _idleNext -> (older) and _idlePrev -> (newer)
// TODO: swap the linkage to match the intuitive older items at "prev"
item._idleNext = list._idleNext;
item._idlePrev = list;

// the list _idleNext points to tail (newest) and _idlePrev to head (oldest)
list._idleNext._idlePrev = item;
list._idleNext = item;
}

function createTimersList(msecs, unrefed) {
// Make a new linked list of timers, and create a TimerWrap to schedule
// processing for the list.
const list = new TimersList(msecs, unrefed);
L.init(list);
list._timer._list = list;

if (unrefed === true) list._timer.unref();
Expand All @@ -151,8 +162,8 @@ function createTimersList(msecs, unrefed) {
}

function TimersList(msecs, unrefed) {
this._idleNext = null; // Create the list with the linkedlist properties to
this._idlePrev = null; // prevent any unnecessary hidden class changes.
this._idleNext = this;
this._idlePrev = this;
this._timer = new TimerWrap();
this._unrefed = unrefed;
this.msecs = msecs;
Expand All @@ -168,7 +179,8 @@ function listOnTimeout() {
debug('now: %d', now);

var diff, timer;
while (timer = L.peek(list)) {
while (list._idlePrev !== list) {
timer = list._idlePrev;
diff = now - timer._idleStart;

// Check if this loop iteration is too early for the next timer.
Expand All @@ -185,8 +197,17 @@ function listOnTimeout() {

// The actual logic for when a timeout happens.

L.remove(timer);
assert(timer !== L.peek(list));
// Remove the timer from the linked list
if (timer._idleNext) {
timer._idleNext._idlePrev = timer._idlePrev;
}

if (timer._idlePrev) {
timer._idlePrev._idleNext = timer._idleNext;
}

timer._idleNext = null;
timer._idlePrev = null;

if (!timer._onTimeout) continue;

Expand All @@ -210,11 +231,9 @@ function listOnTimeout() {
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.
// All of the timer timeouts, if any, have been called.
// Remove the list and clean up the TimerWrap C++ handle.
debug('%d list empty', msecs);
assert(L.isEmpty(list));
this.close();

// Either refedLists[msecs] or unrefedLists[msecs] may have been removed and
Expand Down Expand Up @@ -263,11 +282,21 @@ function listOnTimeoutNT(list) {
// 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);
// Remove item from the linked list
if (item._idleNext) {
item._idleNext._idlePrev = item._idlePrev;
}

if (item._idlePrev) {
item._idlePrev._idleNext = item._idleNext;
}

item._idleNext = null;
item._idlePrev = null;

var list = refedLists[item._idleTimeout];
// if empty - reuse the watcher
if (list && L.isEmpty(list)) {
if (list && list._idleNext === list) {
debug('reuse hit');
list._timer.stop();
delete refedLists[item._idleTimeout];
Expand Down Expand Up @@ -313,7 +342,8 @@ exports.enroll = function(item, msecs) {
}

item._idleTimeout = msecs;
L.init(item);
item._idleNext = item;
item._idlePrev = item;
};


Expand Down