}
-// 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
+// Export the linklist code for testing.
+
+
+exports.linkedList = {};
+
+
+function init(list) {
+ list._idleNext = list;
+ list._idlePrev = list;
+}
+exports.linkedList.init = init;
-// Object containing all lists, timers
-// key = time in milliseconds
-// value = list
-var lists = {};
// show the most idle item
function peek(list) {
if (list._idlePrev == list) return null;
return list._idlePrev;
}
+exports.linkedList.peek = peek;
// remove the most idle item from the list
remove(first);
return first;
}
+exports.linkedList.shift = shift;
// remove a item from its list
function remove(item) {
- item._idleNext._idlePrev = item._idlePrev;
- item._idlePrev._idleNext = item._idleNext;
+ if (item._idleNext) {
+ item._idleNext._idlePrev = item._idlePrev;
+ }
+
+ if (item._idlePrev) {
+ item._idlePrev._idleNext = item._idleNext;
+ }
+
+ item._idleNext = null;
+ item._idlePrev = null;
}
+exports.linkedList.remove = remove;
// remove a item from its list and place at the end.
function append(list, item) {
+ remove(item);
item._idleNext = list._idleNext;
list._idleNext._idlePrev = item;
item._idlePrev = list;
list._idleNext = item;
}
+exports.linkedList.append = append;
+
+
+function isEmpty(list) {
+ return list._idleNext === list;
+}
+exports.linkedList.isEmpty = isEmpty;
+
+
+// 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
list = lists[msecs];
} else {
list = new Timer();
- list._idleNext = list;
- list._idlePrev = list;
+ init(list);
lists[msecs] = list;
// just set its repeat
var now = new Date();
debug('now: ' + now);
+
var first;
while (first = peek(list)) {
var diff = now - first._idleStart;
if (first._onTimeout) first._onTimeout();
}
}
+
debug(msecs + ' list empty');
- assert(list._idleNext === list); // list is empty
+ assert(isEmpty(list));
list.stop();
};
}
}
append(list, item);
- assert(list._idleNext !== list); // list is not empty
+ assert(!isEmpty(list)); // list is not empty
}
var list = lists[item._idleTimeout];
// if empty then stop the watcher
debug('unenroll');
- if (list && list._idlePrev == list) {
+ if (list && isEmpty(list)) {
debug('unenroll: list empty');
list.stop();
}
if (item._idleNext) unenroll(item);
item._idleTimeout = msecs;
- item._idleNext = item;
- item._idlePrev = item;
+ init(item);
};
// call this whenever the item is active (not idle)
if (item._idleNext == item) {
insert(item, msecs);
} else {
- // inline append
item._idleStart = new Date();
- item._idleNext._idlePrev = item._idlePrev;
- item._idlePrev._idleNext = item._idleNext;
- item._idleNext = list._idleNext;
- item._idleNext._idlePrev = item;
- item._idlePrev = list;
- list._idleNext = item;
+ append(list, item);
}
}
};
--- /dev/null
+var common = require('../common');
+var assert = require('assert');
+var L = require('timers').linkedList;
+
+
+var list = { name: "list" };
+var A = { name: "A" };
+var B = { name: "B" };
+var C = { name: "C" };
+var D = { name: "D" };
+
+
+L.init(list);
+assert.ok(L.isEmpty(list));
+assert.equal(null, L.peek(list));
+
+L.append(list, A);
+// list -> A
+assert.equal(A, L.peek(list));
+
+L.append(list, B);
+// list -> A -> B
+assert.equal(A, L.peek(list));
+
+L.append(list, C);
+// list -> A -> B -> C
+assert.equal(A, L.peek(list));
+
+L.append(list, D);
+// list -> A -> B -> C -> D
+assert.equal(A, L.peek(list));
+
+var x = L.shift(list);
+assert.equal(A, x);
+// list -> B -> C -> D
+assert.equal(B, L.peek(list));
+
+x = L.shift(list);
+assert.equal(B, x);
+// list -> C -> D
+assert.equal(C, L.peek(list));
+
+// B is already removed, so removing it again shouldn't hurt.
+L.remove(B);
+// list -> C -> D
+assert.equal(C, L.peek(list));
+
+// Put B back on the list
+L.append(list, B);
+// list -> C -> D -> B
+assert.equal(C, L.peek(list));
+
+L.remove(C);
+// list -> D -> B
+assert.equal(D, L.peek(list));
+
+L.remove(B);
+// list -> D
+assert.equal(D, L.peek(list));
+
+L.remove(D);
+// list
+assert.equal(null, L.peek(list));
+
+
+assert.ok(L.isEmpty(list));
+
+
+L.append(list, D);
+// list -> D
+assert.equal(D, L.peek(list));
+
+L.append(list, C);
+L.append(list, B);
+L.append(list, A);
+// list -> D -> C -> B -> A
+
+// Append should REMOVE C from the list and append it to the end.
+L.append(list, C);
+
+// list -> D -> B -> A -> C
+assert.equal(D, L.shift(list));
+// list -> B -> A -> C
+assert.equal(B, L.peek(list));
+assert.equal(B, L.shift(list));
+// list -> A -> C
+assert.equal(A, L.peek(list));
+assert.equal(A, L.shift(list));
+// list -> C
+assert.equal(C, L.peek(list));
+assert.equal(C, L.shift(list));
+// list
+assert.ok(L.isEmpty(list));
+