Search code examples
node.jsmessage-queueevent-loop

What exactly is a Node.js event loop tick?


I've been getting more into the internals of the Node.js architecture, and a term I see coming up a lot is "tick" as in "next tick of the event loop" or the function nextTick().

What I haven't seen is a solid definition of what exactly a "tick" is. Based on various articles (such as this one), I've been able to piece a concept together in my head, but I'm not sure how accurate it is.

Can I get a precise and detailed description of a Node.js event loop tick?


Solution

  • 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