Detecting Node.js active handles with wtfnode
Not long ago I was running some tests for a Node.js project and for some reason they run forever. There was something preventing the process from terminating. It took me some time to realise that it was an interval not being properly cleared, but I learned a lot in the debugging process.
How can we find out what bits of code are causing our programs to starve forever? Why is this happening?
The event loop
Node.js uses an event-driven architecture. The core of the event loop is implemented using a C library called libuv, which provides support for asynchronous I/O polling. Node.js also has some event queues of its own to implement things like process.nextTick
and Promises.
If you dig into libuv’s code, you can find this function:
This bit is what keeps node running forever. Basically, it says “if there are any active handles, any active requests or any closing handles, we must keep running”. But what are handles and requests?
Handles and requests
Handles and requests are two abstractions provided by libuv. A handle is an object that can do something while it is active. Examples of handles are I/O devices, signals, timers or processes.
Requests represent short-lived operations. Requests can operate over handles. An example could be a write request operating over a file handle.
Debugging our code
Node.js exposes two (undocumented) functions to query active handles and requests. The functions are process._getActiveHandles()
and process._getActiveRequests()
. They provide detailed information but they are quite cryptic.
You can check the output of this snippet in this gist. Since it is a bit long I won’t inline it here.
> Since Node.js 11 timers are not even included in getActiveHandles()
results.
Wtf, node?
wtfnode is a small package that pretty-prints the information of getActiveHandles()
. For newer versions of Node.js it also uses the async_hooks API to track timers.
The usage is quite simple:
The dump function will output something like this:
Node.js always has the stdio file descriptor open. In the second dump we can see the timer and also the file and line number where the timer was set.
With this information you should be able to locate the precise line of code that is causing you troubles!