25

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.

Madd0g
  • 3,841
  • 5
  • 37
  • 59
  • Try out [node-inspector](https://github.com/node-inspector/node-inspector) – Jake Sellers Feb 18 '14 at 20:37
  • This is usually because some event listeners haven't been unregistered. That's pretty broad, but for any more information you'll need to post some code. – qubyte Feb 18 '14 at 23:25
  • @MarkS.Everitt - any kind of pointer would be appreciated - what kind of event listeners could cause the process to stay alive after SIGINT? – Madd0g Feb 19 '14 at 21:56
  • Without seeing code, there's no way I can know. Perhaps try closing server objects on the signal, although this should be automatic. – qubyte Feb 19 '14 at 22:07

1 Answers1

45

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();
});
TimWolla
  • 31,849
  • 8
  • 63
  • 96
  • 4
    huh, amazingly simple. I had a SIGINT catcher that closed services and processes. I must've had `process.exit` in the code before and I guess I just assumed it wasn't needed so I removed that :) Thank you – Madd0g Feb 22 '14 at 21:26
  • 1
    This is great. I wish that https://nodejs.org/api/process.html had this information. – Nick Heiner Dec 09 '16 at 19:00