Expose link-list from timers.js; add tests
authorRyan Dahl <ry@tinyclouds.org>
Thu, 13 Jan 2011 10:09:03 +0000 (02:09 -0800)
committerRyan Dahl <ry@tinyclouds.org>
Thu, 13 Jan 2011 10:10:09 +0000 (02:10 -0800)
lib/timers.js
test/simple/test-timers-linked-list.js [new file with mode: 0644]

index c1b8a0291fdea558bbf0f640fb639493e8931636..d0ec0c8d9dae3eb486b610f6c84fb49b9c6de617 100644 (file)
@@ -9,24 +9,25 @@ 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
+// 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
@@ -35,22 +36,54 @@ function shift(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
@@ -67,8 +100,7 @@ function insert(item, msecs) {
     list = lists[msecs];
   } else {
     list = new Timer();
-    list._idleNext = list;
-    list._idlePrev = list;
+    init(list);
 
     lists[msecs] = list;
 
@@ -78,6 +110,7 @@ function insert(item, msecs) {
       // just set its repeat
       var now = new Date();
       debug('now: ' + now);
+
       var first;
       while (first = peek(list)) {
         var diff = now - first._idleStart;
@@ -91,8 +124,9 @@ function insert(item, msecs) {
           if (first._onTimeout) first._onTimeout();
         }
       }
+
       debug(msecs + ' list empty');
-      assert(list._idleNext === list); // list is empty
+      assert(isEmpty(list));
       list.stop();
     };
   }
@@ -103,7 +137,7 @@ function insert(item, msecs) {
   }
 
   append(list, item);
-  assert(list._idleNext !== list); // list is not empty
+  assert(!isEmpty(list)); // list is not empty
 }
 
 
@@ -114,7 +148,7 @@ var unenroll = exports.unenroll = function(item) {
     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();
     }
@@ -129,8 +163,7 @@ exports.enroll = function(item, msecs) {
   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)
@@ -142,14 +175,8 @@ exports.active = function(item) {
     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);
     }
   }
 };
diff --git a/test/simple/test-timers-linked-list.js b/test/simple/test-timers-linked-list.js
new file mode 100644 (file)
index 0000000..9caa909
--- /dev/null
@@ -0,0 +1,94 @@
+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));
+