31

I need to do some useful things when my Express.js service is stopped by SIGINT. Using Express.js version 3.0.6, I understand that this should work:

var express = require('express');

var app = express();
var server = app.listen(3000);

process.on('SIGINT', function() {
  console.log('Do something useful here.');
  server.close();
});

But the process doesn't give me back the Bash prompt unless I issue SIGINT (Control-C) twice:

$ node problem.js 
^CDo something useful here.
^CDo something useful here.

net.js:1046
    throw new Error('Not running');
          ^
Error: Not running
    at Server.close (net.js:1046:11)
    at process.<anonymous> (/path/to/problem.js:8:10)
    at process.EventEmitter.emit (events.js:93:17)
    at SignalWatcher.startup.processSignalHandlers.process.on.process.addListener.w.callback (node.js:486:45)
$

One more caveat. If I start this Express.js service and don't send any requests then SIGINT terminates properly.

Clearly there is something fundamental that I'm missing here?

Jacob Marble
  • 28,555
  • 22
  • 67
  • 78

3 Answers3

41

By catching the SIGINT handler, you are preventing the default behaviour, which is to quit the process. When you use Ctrl+C the second time, the process dies because server.close throws an uncaught exception.

Just add a process.exit() to the end of your SIGINT handler to quit the process gracefully.

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • 1
    Using `process.exit()` is like sending a ^C after you did one thing and should not be considered gracefully (imho). I think a more complete answer is below (although a little late). – Whyhankee Jun 28 '17 at 05:24
  • 3
    Instead of calling process.exit you should clear the event loop and set process.exitCode in case of an non successful exit. – n1ru4l Jul 07 '19 at 08:16
17

Resurrecting an oldie since the question is not completely (or correctly) answered and people might still find this answer.

The important part of your question is:

One more caveat. If I start this Express.js service and don't send any requests then SIGINT terminates properly. Clearly there is something fundamental that I'm missing here?`

What you are missing is that by default express keeps connections open (HTTP keep-alive) for re-use. So, when there has been a (recent) request there's still something on the event-loop and that's the reason your app is not closing.

Using process.exit() will work, but is like sending another ^C, and may hide the fact that there is still something else open (e.g. database connections). Your app should just close when there's nothing left on the event-loop.

So, I modified your example, setting a 5 second keep-alive on the connection so it will graceful shutdown in a reasonable time.

var express = require('express');

var serverPort = 3000;
var app = express();
var server = app.listen(serverPort);


// HTTP Keep-Alive to a short time to allow graceful shutdown
server.on('connection', function (socket) {
  socket.setTimeout(5 * 1000);
});

// Handle ^C
process.on('SIGINT', shutdown);

// Do graceful shutdown
function shutdown() {
  console.log('graceful shutdown express');
  server.close(function () {
    console.log('closed express');
  });
}

console.log('running server: http://localhost:' + serverPort);
Whyhankee
  • 822
  • 10
  • 14
  • 5
    Due to the long potential delay before shutdown, this may still not be ideal, and it makes keep-alive connections potentially less useful. For a more full solution, it might make sense to go for something like the graceful-exit module: https://www.npmjs.com/package/express-graceful-exit. That module will close keep-alive connections and wait for other activity to cease before exiting. – nneonneo Jun 29 '17 at 01:49
  • This bypasses the default behavior of control-c TWICE in a row. Probably should have a delay in there, waiting for the second press, and only then shutdown. What do you think? – lonix Jan 21 '19 at 20:13
  • The example above only illustrates what's going on with keep-alive on the connections. As @nneonneo mentioned, you should use an existing package to do this. – Whyhankee Jan 23 '19 at 06:21
8

I am using this code:

server.close(() => {
  process.exit(0)
})
Kirill Husiatyn
  • 828
  • 2
  • 12
  • 20