Node.js internally in libuv has some sort of counter of the number of open resources that are supposed to keep the process running. It's not only timers that count here. Any type of open TCP socket or listening server counts too as would other asynchronous operations such as an in-process file I/O operations. You can see calls in the node.js source to uv_ref()
and uv_unref()
. That's how code internal to node.js marks resources that should keep the process running or release them when done.
Whenever the event loop is empty meaning there is no pending event to run, node.js checks this counter in libuv and if it's zero, then it exits the process. If it's not zero, then something is still open that is supposed to keep the process running.
So, let's supposed you have an idle server running with a listening server and an empty event loop. The libuv counter will be non-zero so node.js does not exit the process. Now, some client tries to connect to your server. At the lowest level, the TCP interface of the OS notifies some native code in node.js that there's a client that just connected to your server. This native code then packages that up into a node.js event and adds it to the node.js event queue. That causes the libuv to wake up and process that event. It pulls it from the event queue and calls the JS callback associated with that event, cause some JS code in node.js to run. That will end up emitting an event on that server (of the eventEmitter type) which the JS code monitoring that server will receive and then JS code can start processing that incoming request.
So, at the lowest level, there is native code built into the TCP support in node.js that uses the OS-level TCP interface to get told by the OS that an incoming connection to your server has just been received. That gets translated into an event in the node.js event queue which causes the interpreter to run the Javascript callback associated with that event.
When that callback is done, node.js will again check the counter to see if the process should exit. Assuming the server is still running and has not has .unref()
called on it which removes it from the counter, then node.js will see that there are still things running and the process should not exit.