Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Commit

Permalink
Change nextTick implementation for the better
Browse files Browse the repository at this point in the history
Use a prepare and idle watcher to execute the nextTick callback more
quickly. Test provided by Matt Ranney.
  • Loading branch information
ry committed Apr 13, 2010
1 parent 57edff1 commit 4e7e2f8
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 7 deletions.
58 changes: 57 additions & 1 deletion src/node.cc
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 2 additions & 6 deletions src/node.js
Expand Up @@ -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();
};


Expand Down
31 changes: 31 additions & 0 deletions test/simple/test-next-tick-ordering.js
@@ -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

@noonat
Copy link

@noonat noonat commented on 4e7e2f8 Apr 16, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@ry
Copy link
Author

@ry ry commented on 4e7e2f8 Apr 16, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Please sign in to comment.