How Do I Shut Down My Express Server Gracefully When Its Process Is Killed?
The problem you are experiencing is that all modern browsers reuse single connection for multiple requests. This is called keep-alive connections.
The proper way to handle this is to monitor all new connections and requests and to track status of each connection (is it idle or active right now). Then you can forcefully close all idle connections and make sure to close active connections after current request is being processed.
I've implemented the @moebius/http-graceful-shutdown module specifically designed to gracefully shutdown Express applications and Node servers overall. Sadly nor Express, nor Node itself doesn't have this functionality built-in.
Here's how it can be used with any Express application:
const express = require('express');
const GracefulShutdownManager = require('@moebius/http-graceful-shutdown').GracefulShutdownManager;
const app = express();
const server = app.listen(8080);
const shutdownManager = new GracefulShutdownManager(server);
process.on('SIGTERM', () => {
shutdownManager.terminate(() => {
console.log('Server is gracefully terminated');
});
});
Feel free to check-out the module, the GitHub page has more details.
I added a listener for connections opening on the server, storing references to those connections in an array. When the connections are closed, they are removed from the array.
When the server is killed, each of the connection is closed by calling its end
methods. For some browsers (e.g. Chrome), this is not enough, so after a timeout, I call destroy
on each connection.
const express = require('express');
const app = express();
app.get('/', (req, res) => res.json({ ping: true }));
const server = app.listen(3000, () => console.log('Running…'));
setInterval(() => server.getConnections(
(err, connections) => console.log(`${connections} connections currently open`)
), 1000);
process.on('SIGTERM', shutDown);
process.on('SIGINT', shutDown);
let connections = [];
server.on('connection', connection => {
connections.push(connection);
connection.on('close', () => connections = connections.filter(curr => curr !== connection));
});
function shutDown() {
console.log('Received kill signal, shutting down gracefully');
server.close(() => {
console.log('Closed out remaining connections');
process.exit(0);
});
setTimeout(() => {
console.error('Could not close connections in time, forcefully shutting down');
process.exit(1);
}, 10000);
connections.forEach(curr => curr.end());
setTimeout(() => connections.forEach(curr => curr.destroy()), 5000);
}