What exactly is a Node.js event loop tick?

Remember that while JavaScript is single-threaded, all of node’s I/O and calls to native APIs are either asynchronous (using platform-specific mechanisms), or run on a separate thread. (This is all handled through libuv.)

So when there’s data available on a socket or a native API function has returned, we need a synchronized way to invoke the JavaScript function that is interested in the particular event that just happened.

It’s not safe to just call the JS function from the thread where the native event happened for the same reasons that you’d encounter in a regular multi-threaded application – race conditions, non-atomic memory access, and so forth.

So what we do is place the event on a queue in a thread-safe manner. In oversimplified psuedocode, something like:

lock (queue) {
    queue.push(event);
}

Then, back on the main JavaScript thread (but on the C side of things), we do something like:

while (true) {
    // this is the beginning of a tick

    lock (queue) {
        var tickEvents = copy(queue); // copy the current queue items into thread-local memory
        queue.empty(); // ..and empty out the shared queue
    }

    for (var i = 0; i < tickEvents.length; i++) {
        InvokeJSFunction(tickEvents[i]);
    }

    // this the end of the tick
}

The while (true) (which doesn’t actually exist in node’s source code; this is purely illustrative) represents the event loop. The inner for invokes the JS function for each event that was on the queue.

This is a tick: the synchronous invocation of zero or more callback functions associated with any external events. Once the queue is emptied out and the last function returns, the tick is over. We go back to the beginning (the next tick) and check for events that were added to the queue from other threads while our JavaScript was running.

What can add things to the queue?

  • process.nextTick
  • setTimeout/setInterval
  • I/O (stuff from fs, net, and so forth)
  • crypto‘s processor-intensive functions like crypto streams, pbkdf2, and the PRNG (which are actually an example of…)
  • any native modules that use the libuv work queue to make synchronous C/C++ library calls look asynchronous

Leave a Comment