Skip to content

Commit

Permalink
fixup: multiple sets -> single ordered array
Browse files Browse the repository at this point in the history
  • Loading branch information
Trott committed Sep 26, 2015
1 parent d07e3a7 commit 4510314
Showing 1 changed file with 89 additions and 41 deletions.
130 changes: 89 additions & 41 deletions lib/timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,55 +18,99 @@ const TIMEOUT_MAX = 2147483647; // 2^31-1
// Object containing all sets of timers
// key = time in milliseconds
// value = set
var lists = {};
var triggerTimers = new Map();
var timers = [];
var triggerTimers = [];

function addToTimers(item) {
let maxIndex = timers.length - 1;
// hot path
if (maxIndex === -1 || item._triggerAt >= timers[maxIndex]._triggerAt) {
timers[maxIndex + 1] = item;
return maxIndex + 1;
}

let minIndex = 0;
let currentIndex;
let currentElement;

while (minIndex <= maxIndex) {
currentIndex = (minIndex + maxIndex) / 2 | 0;
currentElement = timers[currentIndex];

if (currentElement._triggerAt < item._triggerAt) {
minIndex = currentIndex + 1;
} else if (currentElement._triggerAt > item._triggerAt) {
maxIndex = currentIndex - 1;
}
else {
break;
}
}
timers.splice(currentIndex, 0, item);

return currentIndex;
}

function setTriggerTimer() {
let triggerTimer = new Timer();
let msecs = timers[0]._triggerAt - Timer.now();
if (msecs < 1) msecs = 1;
triggerTimer.start(msecs, 0);
triggerTimer.msecs = timers[0]._idleTimeout;
triggerTimer[kOnTimeout] = listOnTimeout;
triggerTimers.push(triggerTimer);
}

// the main function - creates lists on demand and the watchers associated
// with them.
// the main function - inserts timers into the right place in the timers array
// and creates watchers associated with them when necessary
function insert(item, msecs) {
if (msecs < 0) return;

item._idleStart = Timer.now();
item._idleTimeout = msecs;
item._triggerAt = item._idleStart + msecs;

if (lists[msecs]) {
lists[msecs].add(item);
return;
}

lists[msecs] = new Set().add(item);
const indexInserted = addToTimers(item);
if (indexInserted === 0)
setTriggerTimer();
}

const timer = new Timer();
timer.start(msecs, 0);
timer.msecs = msecs;
timer[kOnTimeout] = listOnTimeout;
triggerTimers.set(msecs, timer);
function removeFiredTimers() {
timers = timers.filter(function(val) {return ! val._called;});
}

function listOnTimeout() {
var msecs = this.msecs;
var list = this;

var now = Timer.now();

var diff, first, threw;
for (let first of lists[msecs]) {
diff = now - first._idleStart;
if (diff < msecs) {
list.start(msecs - diff, 0);
let i = 0;
let timersCopy = timers.slice(0);
while (i < timersCopy.length) {
first = timersCopy[i];
diff = first._triggerAt - now;
if (diff > 0) {
removeFiredTimers();
list.start(diff, 0);
return;
} else {
lists[msecs].delete(first);
if (!first._onTimeout) continue;
i++;
if (!first._onTimeout) {
first._called = true;
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)
if (domain && domain._disposed) {
first._called = true;
continue;
}

try {
if (domain)
Expand All @@ -85,6 +129,7 @@ function listOnTimeout() {
// 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.
removeFiredTimers();
var oldDomain = process.domain;
process.domain = null;
process.nextTick(listOnTimeoutNT, list);
Expand All @@ -93,11 +138,12 @@ function listOnTimeout() {
}
}
}
if (lists[msecs] && lists[msecs].size === 0) {
delete lists[msecs];
}

list.close();
removeFiredTimers();
if (timers.length !== 0) {
setTriggerTimer();
}
}


Expand All @@ -108,18 +154,17 @@ function listOnTimeoutNT(list) {

const unenroll = exports.unenroll = function(item) {

var list = lists[item._idleTimeout];
if (list) {
list.delete(item);
const myIndex = timers.indexOf(item);
if (myIndex !== -1) {
timers.splice(myIndex, 1);
}

// if empty then stop the watcher
if (list && list.size === 0) {
if (triggerTimers.has(item._idleTimeout)) {
triggerTimers.get(item._idleTimeout).close();
triggerTimers.delete(item._idleTimeout);
}
delete lists[item._idleTimeout];
if (timers.length === 0 && triggerTimers.length !== 0) {
triggerTimers.forEach(function(tt) {
tt.close();
});
triggerTimers = [];
}
// if active is called later, then we want to make sure not to insert again
item._idleTimeout = -1;
Expand All @@ -146,8 +191,7 @@ exports.enroll = function(item, msecs) {
}

item._idleTimeout = msecs;
lists[msecs] = lists[msecs] || new Set();
lists[msecs].add(item);
addToTimers(item);
};


Expand Down Expand Up @@ -273,6 +317,8 @@ exports.setInterval = function(callback, repeat) {
this._handle.start(repeat, 0);
} else {
timer._idleTimeout = repeat;
removeFiredTimers();
timer._called = false;
exports.active(timer);
}
}
Expand Down Expand Up @@ -473,8 +519,9 @@ function _makeTimerTimeout(timer) {
var domain = timer.domain;
var msecs = timer._idleTimeout;

if (lists[msecs]) {
lists[msecs].delete(timer);
const myIndex = timers.indexOf(timer);
if (myIndex !== -1) {
timers.splice(myIndex, 1);
}
unrefList.delete(timer);

Expand Down Expand Up @@ -574,8 +621,9 @@ exports._unrefActive = function(item) {
var msecs = item._idleTimeout;
if (!msecs || msecs < 0) return;

if (lists[msecs]) {
lists[msecs].delete(item);
const myIndex = timers.indexOf(item);
if (myIndex !== -1) {
timers.splice(myIndex, 1);
}
if (!unrefList) {
unrefList = new Set();
Expand Down

0 comments on commit 4510314

Please sign in to comment.