How to properly handle SIGINT with Express.js?
Asked Answered
T

3

35

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?

Taper answered 17/1, 2013 at 4:39 Comment(0)
T
46

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.

Theurgy answered 17/1, 2013 at 4:47 Comment(2)
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).Subway
Instead of calling process.exit you should clear the event loop and set process.exitCode in case of an non successful exit.Lewse
S
19

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);
Subway answered 28/6, 2017 at 5:18 Comment(3)
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: npmjs.com/package/express-graceful-exit. That module will close keep-alive connections and wait for other activity to cease before exiting.Theurgy
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?Babu
The example above only illustrates what's going on with keep-alive on the connections. As @Theurgy mentioned, you should use an existing package to do this.Subway
S
8

I am using this code:

server.close(() => {
  process.exit(0)
})
Saphena answered 26/4, 2017 at 14:19 Comment(2)
Instead of calling process.exit you should clear the event loop and set process.exitCode in case of an non successful exit.Lewse
Agreed, this is better solution.Saphena

© 2022 - 2024 — McMap. All rights reserved.