Permalink
Browse files

WIP

  • Loading branch information...
1 parent 9434487 commit ad19389aefae629902a4128a8c21e08d3a648038 @bnoordhuis committed Jun 4, 2012
Showing with 91 additions and 177 deletions.
  1. +91 −177 lib/timers.js
View
@@ -20,8 +20,6 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var Timer = process.binding('timer_wrap').Timer;
-var L = require('_linklist');
-var assert = require('assert').ok;
// Timeout values > TIMEOUT_MAX are set to 1.
var TIMEOUT_MAX = 2147483647; // 2^31-1
@@ -34,212 +32,128 @@ if (process.env.NODE_DEBUG && /timer/.test(process.env.NODE_DEBUG)) {
}
-// IDLE TIMEOUTS
-//
-// Because often many sockets will have the same idle timeout we will not
-// use one timeout watcher per item. It is too much overhead. Instead
-// we'll use a single watcher for all sockets with the same timeout value
-// and a linked list. This technique is described in the libev manual:
-// http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts
-
-// Object containing all lists, timers
-// key = time in milliseconds
-// value = list
-var lists = {};
-
-
-// the main function - creates lists on demand and the watchers associated
-// with them.
-function insert(item, msecs) {
- item._idleStart = new Date();
- item._idleTimeout = msecs;
-
- if (msecs < 0) return;
-
- var list;
-
- if (lists[msecs]) {
- list = lists[msecs];
- } else {
- list = new Timer();
- list.start(msecs, 0);
-
- L.init(list);
-
- lists[msecs] = list;
-
- list.ontimeout = function() {
- debug('timeout callback ' + msecs);
-
- var now = new Date();
- debug('now: ' + now);
-
- var first;
- while (first = L.peek(list)) {
- var diff = now - first._idleStart;
- if (diff + 1 < msecs) {
- list.start(msecs - diff, 0);
- debug(msecs + ' list wait because diff is ' + 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 user's
- // uncaughtException handler ignores the exception, other timers that
- // expire on this tick should still run. If #2582 goes through, this
- // hack should be removed.
- //
- // https://github.com/joyent/node/issues/2631
- if (first.domain) {
- if (first.domain._disposed) continue;
- first.domain.enter();
- }
- try {
- first._onTimeout();
- } catch (e) {
- if (!process.listeners('uncaughtException').length) throw e;
- process.emit('uncaughtException', e);
- }
- if (first.domain) first.domain.exit();
- }
- }
-
- debug(msecs + ' list empty');
- assert(L.isEmpty(list));
- list.close();
- delete lists[msecs];
- };
- }
+var timer_handle = new Timer;
+var timer_heap = [];
+var timer_next = -1;
+var timer_now = -1;
- L.append(list, item);
- assert(!L.isEmpty(list)); // list is not empty
-}
+timer_handle.ontimeout = function() {
+ var reqs = pending();
-var unenroll = exports.unenroll = function(item) {
- L.remove(item);
+ timer_now = Date.now();
+ timer_next = -1; // allow rescheduling
- var list = lists[item._idleTimeout];
- // if empty then stop the watcher
- debug('unenroll');
- if (list && L.isEmpty(list)) {
- debug('unenroll: list empty');
- list.close();
- delete lists[item._idleTimeout];
- }
- // if active is called later, then we want to make sure not to insert again
- item._idleTimeout = -1;
+ for (var i = 0, k = reqs.length; i < k; ++i)
+ for (var cb in reqs[i].callbacks)
+ if (cb)
+ cb();
+
+ reschedule();
};
-// Does not start the time, just sets up the members needed.
-exports.enroll = function(item, msecs) {
- // if this item was already in a list somewhere
- // then we should unenroll it from that
- if (item._idleNext) unenroll(item);
+function pending() {
+ var n = 0;
- item._idleTimeout = msecs;
- L.init(item);
-};
+ while (timer_heap[n] && timer_heap[n].msec <= timer_now)
+ n++;
+ return timer_heap.splice(0, n);
+}
-// call this whenever the item is active (not idle)
-// it will reset its timeout.
-exports.active = function(item) {
- var msecs = item._idleTimeout;
- if (msecs >= 0) {
- var list = lists[msecs];
- if (!list || L.isEmpty(list)) {
- insert(item, msecs);
- } else {
- item._idleStart = new Date();
- L.append(list, item);
- }
- }
-};
+function find(msec, add) {
+ var values = timer_heap; // cache in local var == minor speedup
+ var max = values.length;
+ var min = 0;
-/*
- * DOM-style timers
- */
+ if (timer_now === -1)
+ timer_now = Date.now(); // cache result, avoid unnecessary syscalls
+ msec += timer_now;
-exports.setTimeout = function(callback, after) {
- var timer;
+ while (max - min > 1) {
+ var n = min + (max - min >> 1);
+ var v = values[n];
- after = ~~after;
- if (after < 1 || after > TIMEOUT_MAX) {
- after = 1; // schedule on next tick, follows browser behaviour
- }
+ if (v.msec === msec)
+ return v;
- timer = { _idleTimeout: after };
- timer._idlePrev = timer;
- timer._idleNext = timer;
-
- if (arguments.length <= 2) {
- timer._onTimeout = callback;
- } else {
- /*
- * Sometimes setTimeout is called with arguments, EG
- *
- * setTimeout(callback, 2000, "hello", "world")
- *
- * If that's the case we need to call the callback with
- * those args. The overhead of an extra closure is not
- * desired in the normal case.
- */
- var args = Array.prototype.slice.call(arguments, 2);
- timer._onTimeout = function() {
- callback.apply(timer, args);
- }
+ if (v.msec < msec)
+ min = n;
+ else
+ max = n;
}
- if (process.domain) timer.domain = process.domain;
+ if (!add)
+ return null;
- exports.active(timer);
+ var v = { msec: msec, callbacks: [] };
- return timer;
-};
+ if (values[n] && values[n].msec < msec)
+ values.splice(min, 0, v);
+ else
+ values.splice(max, 0, v);
+ return v;
+}
-exports.clearTimeout = function(timer) {
- if (timer && (timer.ontimeout || timer._onTimeout)) {
- timer.ontimeout = timer._onTimeout = null;
- if (timer instanceof Timer) {
- timer.close(); // for after === 0
- } else {
- exports.unenroll(timer);
- }
- }
+
+function insert(cb, msec) {
+ var v = find(msec, true);
+ var n = v.callbacks.length;
+ v.callbacks.push(cb);
+ reschedule();
+ return [v, n];
+}
+
+
+function remove(req) {
+ var v = req[0];
+ var n = req[1];
+ v.callbacks[n] = null;
+}
+
+
+function reschedule() {
+ if (timer_heap.length === 0)
+ return;
+
+ var msec = timer_heap[0].msec;
+
+ if (timer_next != -1 && timer_next <= msec)
+ return;
+
+ timer_next = msec;
+ timer_handle.stop();
+ timer_handle.start(msec - timer_now, 0);
+}
+
+
+exports.setTimeout = function(callback, delay) {
+ return insert(callback, delay);
};
-exports.setInterval = function(callback, repeat) {
- var timer = new Timer();
+exports.clearTimeout = function(timeoutId) {
+ remove(timeoutId);
+};
- if (process.domain) timer.domain = process.domain;
- repeat = ~~repeat;
- if (repeat < 1 || repeat > TIMEOUT_MAX) {
- repeat = 1; // schedule on next tick, follows browser behaviour
- }
+exports.setInterval = function(callback, delay) {
+ var intervalId = { _handle: insert(fn, delay) };
- var args = Array.prototype.slice.call(arguments, 2);
- timer.ontimeout = function() {
- callback.apply(timer, args);
+ function fn() {
+ callback();
+ intervalId._handle = insert(fn, delay);
}
- timer.start(repeat, repeat);
- return timer;
+ return intervalId;
};
-exports.clearInterval = function(timer) {
- if (timer instanceof Timer) {
- timer.ontimeout = null;
- timer.close();
- }
+exports.clearInterval = function(intervalId) {
+ remove(intervalId._handle);
+ intervalId._handle = null;
};

0 comments on commit ad19389

Please sign in to comment.