Change nextTick implementation for the better
authorRyan Dahl <ry@tinyclouds.org>
Tue, 13 Apr 2010 22:39:15 +0000 (15:39 -0700)
committerRyan Dahl <ry@tinyclouds.org>
Tue, 13 Apr 2010 22:43:46 +0000 (15:43 -0700)
Use a prepare and idle watcher to execute the nextTick callback more
quickly. Test provided by Matt Ranney.

src/node.cc
src/node.js
test/simple/test-next-tick-ordering.js [new file with mode: 0644]

index 28b97ce..50e4db5 100644 (file)
@@ -71,6 +71,10 @@ static bool use_debug_agent = false;
 static bool debug_wait_connect = false;
 static int debug_port=5858;
 
+static ev_prepare next_tick_watcher;
+static ev_idle tick_spinner;
+static bool need_tick_cb;
+static Persistent<String> tick_callback_sym;
 
 static ev_async eio_want_poll_notifier;
 static ev_async eio_done_poll_notifier;
@@ -144,6 +148,7 @@ static void Activity(EV_P_ ev_check *watcher, int revents) {
 
   pending -= ev_is_pending(&gc_timer);
   pending -= ev_is_pending(&gc_idle);
+  pending -= ev_is_pending(&next_tick_watcher);
   //if (ev_is_pending(&gc_check)) pending--; // This probably never happens?
 
   //fprintf(stderr, "activity, pending: %d\n", pending);
@@ -162,6 +167,52 @@ static void Activity(EV_P_ ev_check *watcher, int revents) {
 }
 
 
+static Handle<Value> NeedTickCallback(const Arguments& args) {
+  HandleScope scope;
+  need_tick_cb = true;
+  ev_idle_start(EV_DEFAULT_UC_ &tick_spinner);
+  return Undefined();
+}
+
+
+static void Spin(EV_P_ ev_idle *watcher, int revents) {
+  assert(watcher == &tick_spinner);
+  assert(revents == EV_IDLE);
+}
+
+
+static void Tick(EV_P_ ev_prepare *watcher, int revents) {
+  assert(watcher == &next_tick_watcher);
+  assert(revents == EV_PREPARE);
+
+  // Avoid entering a V8 scope.
+  if (!need_tick_cb) return;
+
+  need_tick_cb = false;
+  ev_idle_stop(EV_DEFAULT_UC_ &tick_spinner);
+
+  HandleScope scope;
+
+  if (tick_callback_sym.IsEmpty()) {
+    // Lazily set the symbol
+    tick_callback_sym =
+      Persistent<String>::New(String::NewSymbol("_tickCallback"));
+  }
+
+  Local<Value> cb_v = process->Get(tick_callback_sym);
+  if (!cb_v->IsFunction()) return;
+  Local<Function> cb = Local<Function>::Cast(cb_v);
+
+  TryCatch try_catch;
+
+  cb->Call(process, 0, NULL);
+
+  if (try_catch.HasCaught()) {
+    FatalException(try_catch);
+  }
+}
+
+
 static void DoPoll(EV_P_ ev_idle *watcher, int revents) {
   assert(watcher == &eio_poller);
   assert(revents == EV_IDLE);
@@ -1356,6 +1407,7 @@ static void Load(int argc, char *argv[]) {
   NODE_SET_METHOD(process, "evalcx", EvalCX);
   NODE_SET_METHOD(process, "compile", Compile);
   NODE_SET_METHOD(process, "_byteLength", ByteLength);
+  NODE_SET_METHOD(process, "_needTickCallback", NeedTickCallback);
   NODE_SET_METHOD(process, "reallyExit", Exit);
   NODE_SET_METHOD(process, "chdir", Chdir);
   NODE_SET_METHOD(process, "cwd", Cwd);
@@ -1378,7 +1430,6 @@ static void Load(int argc, char *argv[]) {
                EventEmitter::constructor_template->GetFunction());
 
 
-
   // Initialize the C++ modules..................filename of module
   IOWatcher::Initialize(process);              // io_watcher.cc
   IdleWatcher::Initialize(process);            // idle_watcher.cc
@@ -1534,6 +1585,11 @@ int main(int argc, char *argv[]) {
   ev_default_loop(EVFLAG_AUTO);
 #endif
 
+  ev_prepare_init(&node::next_tick_watcher, node::Tick);
+  ev_prepare_start(EV_DEFAULT_UC_ &node::next_tick_watcher);
+  ev_unref(EV_DEFAULT_UC);
+
+  ev_idle_init(&node::tick_spinner, node::Spin);
 
   ev_init(&node::gc_timer, node::CheckIdleness);
   node::gc_timer.repeat = GC_INTERVAL;
index ee7a805..f09ef32 100644 (file)
@@ -179,22 +179,18 @@ var events = eventsModule.exports;
 // nextTick()
 
 var nextTickQueue = [];
-var nextTickWatcher = new process.IdleWatcher();
-// Only debugger has maximum priority. Below that is the nextTickWatcher.
-nextTickWatcher.setPriority(process.EVMAXPRI-1);
 
-nextTickWatcher.callback = function () {
+process._tickCallback = function () {
   var l = nextTickQueue.length;
   while (l--) {
     var cb = nextTickQueue.shift();
     cb();
   }
-  if (nextTickQueue.length == 0) nextTickWatcher.stop();
 };
 
 process.nextTick = function (callback) {
   nextTickQueue.push(callback);
-  nextTickWatcher.start();
+  process._needTickCallback();
 };
 
 
diff --git a/test/simple/test-next-tick-ordering.js b/test/simple/test-next-tick-ordering.js
new file mode 100644 (file)
index 0000000..ee41f05
--- /dev/null
@@ -0,0 +1,31 @@
+require('../common');
+var sys = require('sys'), i;
+
+var N = 30;
+var done = [];
+
+function get_printer(timeout) {
+  return function () {
+    sys.puts("Running from setTimeout " + timeout);
+    done.push(timeout);
+  };
+}
+
+process.nextTick(function () {
+  sys.puts("Running from nextTick");
+  done.push('nextTick');
+})
+
+for (i = 0; i < N; i += 1) {
+  setTimeout(get_printer(i), i);
+}
+
+sys.puts("Running from main.");
+
+
+process.addListener('exit', function () {
+  assert.equal('nextTick', done[0]);
+  for (i = 0; i < N; i += 1) {
+    assert.equal(i, done[i+1]);
+  }
+});