Nodejs - process hangs on exit (Ctrl+C)

Madd0g picture Madd0g · Feb 18, 2014 · Viewed 9.3k times · Source

I have a node.js project which does many things, it spawns child processes, it opens an http and socket.io server, etc..

When I run it from the console, closing it with Ctrl+C, it just hangs. From webstorm, stopping the process is a two-step process, first I hit stop, then I need to hit the button again, only the second time the button is a skull icon.

Now, I understand it leaves something open or hanging, but I just can't figure out what, I tried to track all the places where I start a process and made sure I'm killing them properly.

Is there a way to debug this and find out what's making my process hang? Could it be logging that open a write stream and never closes? I'm not even sure what kind of things will make a process hang on SIGINT.

EDIT: I've downloaded pstree to see if any of the child processes that the main process spawns stay alive. It looks like they all terminate properly - the main node process is the only one left.

Answer

TimWolla picture TimWolla · Feb 22, 2014

Scripts are themselves responsible for properly shutting down once they listen to the SIGINT event, as the default handler (killing the process) is disabled then.

Check out this example program:

process.on('SIGINT', function() {
    console.log('SIGINT');
});
console.log('PID: ', process.pid);

var http = require('http'); // HTTP server to keep the script up long enough
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

Execute it and then try killing it: It won't work. The SIGINT signal will always get passed to your custom build signal handler. In order to properly shut the process down you will have to manually call process.exit():

process.on('SIGINT', function() {
    console.log('SIGINT');
    process.exit();
});
console.log('PID: ', process.pid);

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

process.exit() will:

  1. Set some internal flags
  2. Call the process.on('exit') handlers
  3. Call process.reallyExit
  4. Which will call the C++ exit() function, therefore process.exit() is final and will cause a shutdown (unless you block execution by an endless loop in your on('exit') handler).

Long story short: Your code probably listens to SIGINT somewhere. You can fetch a list of those listeners via:

var listeners = process.listeners('SIGINT');

You can even pretty print them on the console:

for (var i = 0; i < listeners.length; i++) {
    console.log(listeners[i].toString());
}

Using the information I gave above you can easily compile yet another SIGINT-handler that will list all the handlers and then cleanly exit the process, hopefully leading your path to the naughty ones:

process.on('SIGINT', function() {
    console.log('Nice SIGINT-handler');
    var listeners = process.listeners('SIGINT');
    for (var i = 0; i < listeners.length; i++) {
        console.log(listeners[i].toString());
    }

    process.exit();
});

Complete program for testing:

process.on('SIGINT', function() {
    console.log('Naughty SIGINT-handler');
});
process.on('exit', function () {
    console.log('exit');
});
console.log('PID: ', process.pid);

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

process.on('SIGINT', function() {
    console.log('Nice SIGINT-handler');
    var listeners = process.listeners('SIGINT');
    for (var i = 0; i < listeners.length; i++) {
        console.log(listeners[i].toString());
    }

    process.exit();
});