From 4510314958582bea5193cf6c25662d996c1ef141 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 26 Sep 2015 06:17:13 -0700 Subject: [PATCH] fixup: multiple sets -> single ordered array --- lib/timers.js | 130 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 89 insertions(+), 41 deletions(-) diff --git a/lib/timers.js b/lib/timers.js index 35f9ddee0bd158..fd4214ed53cd20 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -18,46 +18,88 @@ 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, @@ -65,8 +107,10 @@ function listOnTimeout() { // // 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) @@ -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); @@ -93,11 +138,12 @@ function listOnTimeout() { } } } - if (lists[msecs] && lists[msecs].size === 0) { - delete lists[msecs]; - } list.close(); + removeFiredTimers(); + if (timers.length !== 0) { + setTriggerTimer(); + } } @@ -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; @@ -146,8 +191,7 @@ exports.enroll = function(item, msecs) { } item._idleTimeout = msecs; - lists[msecs] = lists[msecs] || new Set(); - lists[msecs].add(item); + addToTimers(item); }; @@ -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); } } @@ -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); @@ -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();