Permalink
Browse files

Change nextTick implementation for the better

Use a prepare and idle watcher to execute the nextTick callback more
quickly. Test provided by Matt Ranney.
  • Loading branch information...
1 parent 57edff1 commit 4e7e2f872421e9c89538807ce191cd86e40341ab @ry ry committed Apr 13, 2010
Showing with 90 additions and 7 deletions.
  1. +57 −1 src/node.cc
  2. +2 −6 src/node.js
  3. +31 −0 test/simple/test-next-tick-ordering.js
View
@@ -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;
View
@@ -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();
};
@@ -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]);
+ }
+});

2 comments on commit 4e7e2f8

Is tick_spinner only there to keep the loop alive while there are pending callbacks, since the prepare unrefd?

ry replied Apr 16, 2010

yes

Please sign in to comment.