Motivation
I want to ensure there is no brute (sub-)process termination and that any open requests are fulfilled when the Express server is closing.
import express, { Express, Request, Response } from 'express';
// Initialize the Express server application
var app = express();
var server = this.app.listen(process.env.PORT || 5000, () => { ... } );
// POST endpoint to process the shopping cart
app.post('/purchase', async (req, res) => {
// Get the items from the request body
try {
const { buyer, items } = req.body;
// Process the items (e.g., calculate total price, initiate payment, etc.)
const totalPrice = calculateTotalPrice(items);
// Charge the buyer for the purchase
const payment = chargeBuyerForPurchase(totalPrice);
// 🥷 I don't want the server to close before it executes this line for every request
// Create shipping for the purchased items
const shipping = carrierAPI.createShipping(items, buyer.address);
res.status(200).json([totalPrice, payment, shipping]);
// 💁♂️ This is now safe to close the server
} catch (error) {
next(error);
}
});
Proposed Solution
See documentation at this link
// ./src/app.ts
import process from 'node:process';
// Begin reading from stdin so the process does not exit.
process.stdin.resume();
process.on('SIGINT', () => {
console.log('Received SIGINT. Press Control-D to exit.');
});
// FYI docker "stop" <container>, a process manager, and most hosts will send SIGTERM signal when it is shutting down.
// server.close stops the server from accepting new connections and closes all connections connected to this server that are not sending a request or waiting for a response
function gracefulClose(signal) {
console.log(`Received ${signal}`);
server.close( () => { log('HTTP(S) server closed') } );
}
process.on('SIGINT', gracefulClose);
process.on('SIGTERM', gracefulClose);
Is it a feature to implement? Is this feature / code redundant?
2
Answers
THESE ARE MY FINDINGS:
When the
SIGTERM
signal is emitted, the Node.JS server will be immediately closed. The Node.Js application / process will still be alive. Moreover, a response will not be sent to any client even if its connection was open before theSIGTERM
signal was received.When the
SIGINT
signal is emitted, the Node.JS (Express) server and application or process will be immediately closed.When a custom signal is emitted,
SIGUSR1
orSIGUSR2
, the Node.JS server will immediately close any open connections. The Node.Js application / process will still be alive. The Node.Js (Express) server will still be alive too and fulfill only new requests and responses.PS: Signals are software interrupts sent to a program to indicate that an important event has occurred. Another common method for delivering signals is to use the
kill
command, the syntax of which is as follows −MY SOLUTION:
I have created an npm package to super-gracefully-shutdown your Node.Js Express server. It ensures a response is sent to every client for any connections that were open before you send a super-graceful `shutdown` message.Usage / Implementation
Request From Host To Shutdown Node.Js Express App
To super-gracefully-shutdown your Node.Js Express application, send the TCP
shutdown
message on port 3000:Handle SIGTERM to permit Docker to cause a graceful exit
With respect to Docker, yes you have to handle the SIGTERM event and gracefully exit. If you do not, then after having signalled your container to exit, Docker will wait for some period of time (10 sec, I believe) for your container to exit, and if it does not do so in time, Docker will forcibly kill your container.
In general, this would be sufficient:
You can’t handle SIGTERM events during synchronous code anyway
In your example code, your post handler is synchronous. JavaScript is single threaded and cannot run your
gracefulClose
handler (in response to theSIGTERM
event) until your synchronous function has completed. So your code is unnecessary in this case. If the post handler function were asynchronous, then you’d have to a different situation.Deferring a graceful exit during an asynchronous operation
If you were to modify your post handler to be asynchronous, such that you could have a reason not to exit and possibly have the SIGTERM handler run before your reason not to exit is resolved, then yes, you’d have to implement something special to handle that.
Such an implementation might go like this:
Any time you create a situation that constitutes a reason to prevent forcibly closing, push an object representing that reason into an array. When the situation no longer exists, remove the reason from the array.
When you get a SIGTERM signal, set a flag saying that you want to exit.
Then write a function that checks to see if it’s ok to exit. The condition will be that you have set the exit flag, and the reasons-not-to-exit array is empty. If it’s ok to exit, then you may call
process.exit
.Call this function after setting the exit flag, and also call it after removing a reason from the array.
Example deferral: (for example, in the body of your post handler.)
Note that this example has explicitly awaited some hypothetical async operation using
await
, which is missing from your original example, in which this code was not necessary.Note that even though you’ve coded up something to handle a graceful exit, after politely asking your container to exit, Docker essentially starts a doomsday timer, and it may forcibly terminate your program after 10 seconds anyway, whether you’ve finished cleaning up or not. Also
docker kill
will ungracefully terminate your program, even if this code is in place.