wtfnode - Node.js Exit Debugger
wtfnode is a utility designed to help Node.js developers diagnose why their applications are not exiting gracefully. It provides enhanced and human-readable insights into active event loop handles, such as timers, sockets, and servers, which prevent a Node.js process from terminating. Leveraging Node's internal `process._getActiveHandles()`, wtfnode breaks down complex handle information into actionable details, including call site origins for listeners, making it easier to pinpoint the exact code keeping a program alive. The current stable version, 0.10.1, functions as a crucial diagnostic tool for stalled applications, differentiating itself from raw Node.js introspection by offering a higher-level, more interpretable view of the event loop. Its release cadence is driven by the community's need for robust debugging solutions for persistent processes.
Common errors
-
Process never exits / Application hangs indefinitely
cause One or more event loop handles (e.g., timers, network connections, open file descriptors) are still active, preventing Node.js from shutting down.fixAdd `wtfnode` to your application and call `wtf.dump()` when you suspect the process should exit (e.g., on `SIGINT` or at the end of a long-running task) to identify the specific open handles. -
WTF Node? open handles: Timers: - (Xms ~ Ys) wrapper @ /path/to/module.js:Line
cause An `setInterval` or `setTimeout` call is still active, reported as a generic `wrapper` function.fixExamine the `module.js:Line` indicated in the output. This line points to where the timer was originally set. Ensure all long-running timers are explicitly cleared when no longer needed using `clearInterval()` or `clearTimeout()`. -
WTF Node? open handles: Sockets: - A.B.C.D:PORT -> W.X.Y.Z:PORT Listeners: connect: anonymous @ /path/to/connection.js:Line
cause An open network socket or server connection is preventing the process from exiting. The listener shows where the connection handler was defined.fixLocate the code at `/path/to/connection.js:Line` to identify the socket's origin. Ensure all database connections, HTTP servers, or other network resources are explicitly closed or terminated when your application is done with them.
Warnings
- gotcha When `wtfnode` reports on timers, the function name may appear as `wrapper` instead of the original function name. This is a limitation due to how `setInterval` and `setTimeout` create internal wrappers.
- gotcha The `IPC channel to parent` handle provides limited information if your program is spawned by another process (e.g., `child_process.fork`, PM2). `wtfnode` cannot extract more context as it's not based on code executed within the current program.
- breaking In version 0.4.0, a change was introduced for command-line usage (`wtfnode <yourscript>`). If the target script binds SIGINT and enters an infinite loop, Node.js might not exit. `wtfnode` now uses a watchdog proxy, allowing two Ctrl+C presses to force termination, though no output will be available.
- gotcha When using `wtfnode` from a child process on Node.js version 0.12, you might briefly see an `unable to determine callsite` warning due to transient child process handles. This is generally harmless.
Install
-
npm install wtfnode -
yarn add wtfnode -
pnpm add wtfnode
Imports
- wtf
import wtf from 'wtfnode';
const wtf = require('wtfnode'); - dump
require('wtfnode').dump();wtf.dump();
- Options
wtf.dump(true);
wtf.dump({ fullstacks: true });
Quickstart
const wtf = require('wtfnode');
console.log('Starting wtfnode example. Press Ctrl+C to dump handles.');
// Create an interval to keep the process alive
const intervalId = setInterval(() => {
console.log('Interval running...');
}, 2000);
// Create a server that will also keep the process alive
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello wtfnode!\n');
});
server.listen(3000, () => {
console.log('HTTP server listening on port 3000.');
});
// Register SIGINT handler to dump handles before exiting
process.on('SIGINT', () => {
console.log('\n[WTF Node?] Dumping open handles:');
wtf.dump();
// To allow clean exit after dump (optional, depending on desired behavior)
clearInterval(intervalId);
server.close(() => {
console.log('Server closed. Exiting process.');
process.exit(0);
});
});
console.log('Application running. Check http://localhost:3000 and wait for intervals.');