process.on('SIGINT' multiple termination signals - node.js

i have this node code that detect when Ctrl+C in pressed to that do some stuff before node app is exit
process.on('SIGINT', function() {
/* DO SOME STUFF HERE */
process.exit()
})
Now this works but i would like to add to this process others terminations signals so that when Ctrl+C, Node app exits or server is restarted or shutdown or any other reason node app exits to trigger termination signal and call this process and do stuff in database before exits...
termination signals that i need to add is:
SIGTERM
SIGINT
SIGQUIT
SIGKILL
So i came to idea if is possible to do this:
process.on('SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGKILL', function() {
/* DO SOME STUFF HERE */
process.exit()
})
So to pass multiple termination signals to process function..i added like this above but it does not work...how can this be done in node js?

The easiest method:
['SIGINT', 'SIGTERM', 'SIGQUIT']
.forEach(signal => process.on(signal, () => {
/** do your logic */
process.exit();
}));

I'd rather go with
/**
* Do stuff and exit the process
* #param {NodeJS.SignalsListener} signal
*/
function signalHandler(signal) {
// do some stuff here
process.exit()
}
process.on('SIGINT', signalHandler)
process.on('SIGTERM', signalHandler)
process.on('SIGQUIT', signalHandler)
for style and clarity reasons instead of looping

Related

Node.js child process shutdown delay

I have a node.js process that kicks off a child process (via spawn). When the main process receives a request to shutdown (e.g. SIGTERM) it has to perform some clean-up before the process exits - this can take a few seconds. This clean-up relies on the child process continuing to run - however, the child process is independently responding to the SIGTERM and closing down.
Can I prevent the child process closing until the main process is ready for it to shutdown?
Thanks,
Neil
After spawning child processes in detached mode, you can handle them individually. This can be of use to you: Node child processes: how to intercept signals like SIGINT.
The following assumes your child processes are detached:
process.on('SIGINT', () => {
console.log("Intercepted SIGINT on parent");
// Do your magic here, if you just need to wait for X time, you can use a delay promise:
delay(5000).then(() => {
// Kill/handle your child processes here
process.exit(0); // Then exit the main process
});
});
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

How to perform an async operation on exit

I've been trying to perform an asynchronous operation before my process is terminated.
Saying 'terminated' I mean every possibility of termination:
ctrl+c
Uncaught exception
Crashes
End of code
Anything..
To my knowledge the exit event does that but for synchronous operations.
Reading the Nodejs docs i found the beforeExit event is for the async operations BUT :
The 'beforeExit' event is not emitted for conditions causing explicit termination, such as calling process.exit() or uncaught exceptions.
The 'beforeExit' should not be used as an alternative to the 'exit' event unless the intention is to schedule additional work.
Any suggestions?
You can trap the signals and perform your async task before exiting. Something like this will call terminator() function before exiting (even javascript error in the code):
process.on('exit', function () {
// Do some cleanup such as close db
if (db) {
db.close();
}
});
// catching signals and do something before exit
['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT',
'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2', 'SIGTERM'
].forEach(function (sig) {
process.on(sig, function () {
terminator(sig);
console.log('signal: ' + sig);
});
});
function terminator(sig) {
if (typeof sig === "string") {
// call your async task here and then call process.exit() after async task is done
myAsyncTaskBeforeExit(function() {
console.log('Received %s - terminating server app ...', sig);
process.exit(1);
});
}
console.log('Node server stopped.');
}
Add detail requested in comment:
Signals explained from node's documentation, this link refers to standard POSIX signal names
The signals should be string. However, I've seen others have done the check so there might be some other unexpected signals that I don't know about. Just want to make sure before calling process.exit(). I figure it doesn't take much time to do the check anyway.
for db.close(), I guess it depends on the driver you are using. Whether it's sync of async. Even if it's async, and you don't need to do anything after db closed, then it should be fine because async db.close() just emits close event and the event loop would continue to process it whether your server exited or not.
Using beforeExit hook
The 'beforeExit' event is emitted when Node.js empties its event loop and has no additional work to schedule. Normally, the Node.js process will exit when there is no work scheduled, but a listener registered on the 'beforeExit' event can make asynchronous calls, and thereby cause the Node.js process to continue.
process.on('beforeExit', async () => {
await something()
process.exit(0) // if you don't close yourself this will run forever
});
Here's my take on this. A bit long to post as a code snippet in here, so sharing a Github gist.
https://gist.github.com/nfantone/1eaa803772025df69d07f4dbf5df7e58
It's pretty straightforward. You use it like so:
'use strict';
const beforeShutdown = require('./before-shutdown');
// Register shutdown callbacks: they will be executed in the order they were provided
beforeShutdown(() => db.close());
beforeShutdown(() => server.close());
beforeShutdown(/* Do any async cleanup */);
The above will listen for a certain set of system signals (SIGINT, a.k.a Ctrl + C, and SIGTERM by default) and call each handler in order before shutting down the whole process.
It also,
Supports async callbacks (or returning a Promise).
Warns about failing shutdown handlers, but prevents the error/rejection from bubbling up.
Forces shutdown if handlers do not return after some time (15 seconds, by default).
Callbacks can be registered from any module in your code base.
Combining answers + handling for uncaught exceptions and promise rejections
async function exitHandler(evtOrExitCodeOrError: number | string | Error) {
try {
// await async code here
// Optionally: Handle evtOrExitCodeOrError here
} catch (e) {
console.error('EXIT HANDLER ERROR', e);
}
process.exit(isNaN(+evtOrExitCodeOrError) ? 1 : +evtOrExitCodeOrError);
}
[
'beforeExit', 'uncaughtException', 'unhandledRejection',
'SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP',
'SIGABRT','SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV',
'SIGUSR2', 'SIGTERM',
].forEach(evt => process.on(evt, exitHandler));
You need to trap beforeExit, uncaughtException, and each of the listenable signals that can terminate the process: SIGHUP, SIGINT, SIGTERM, SIGUSR2 (Nodemon), and SIGBREAK (Windows). (You likely don't want to clean up on SIGQUIT, since that signal is used for core dumps.) After your async operations have completed, you then need to explicitly terminate the process using the appropriate mechanism, such as process.exit or process.kill. For SIGINT in particular, it's important to propagate the signal to the parent process (i.e. using process.kill as opposed to process.exit). Also note that you need to stop trapping the signals before calling process.kill. Because this is all relatively tricky, I published a library, async-cleanup, to make adding async exit hooks as easy as function call:
import { addCleanupListener } from "async-cleanup";
import { unlink, writeFile } from "fs/promises";
await writeFile(".lockfile", String(process.pid), { flag: "wx" });
addCleanupListener(async () => {
await unlink(".lockfile");
});

Can’t gracefully shut down a node + Express app (node http server's close function does not complete)

I want the Express.js-based server of the app I am working on to properly close all connections before exiting the node process when a shutdown signal (SIGTERM or SIGINT) is sent.
I have slightly modified the express-graceful-shutdown middleware (which is really simple), so the relevant part of my code looks approximately as follows:
server.js
import gracefulShutdown from './tools/graceful-shutdown';
const server = app.listen(config.port, () => {
console.log(`Server is running`);
});
app.use(gracefulShutdown(server));
graceful-shutdown.js:
function gracefulShutdownMiddleware(server) {
let shuttingDown = false;
const forceTimeout = 10 * 1000; // giving the app 10 seconds to shutdown gracefully
process.on('SIGTERM', gracefulExit); // listen for TERM signal (e.g. kill)
process.on ('SIGINT', gracefulExit); // listen for INT signal (e.g. Ctrl-C)
function gracefulExit() {
if (shuttingDown) return;
shuttingDown = true;
console.log('Received kill signal (SIGTERM), shutting down');
setTimeout(function () {
console.log('Could not close connections in time, forcefully shutting down');
process.exit(1);
}, forceTimeout);
server.close(function () {
console.log('Closed out remaining connections.');
process.exit();
});
}
function middleware(req, res, next) {
if (!shuttingDown) return next()
res.set('Connection', 'close')
res.status(503).send('Server is in the process of restarting.')
}
return middleware
}
export default gracefulShutdownMiddleware;
The problem I am observing is this. If I shut down the node server immediately after it starts, it shuts down as expected (function gracefulExit in the middleware works correctly, server.close executes fine, and the callback to server.close prints out the console message). If, however, I open the browser and actually visit my app on the localhost, so that the server has some work to do, and then try to shut the server down, the server.close function does not seem to finish (so it never calls the callback with process.exit until finally the setTimeout calls its own callback and forcefully shuts down the app.
The server.close function is this function from node’s lib.
Could you help me figure out why node server’s close function may fail to complete?
.close() doesn't actually close any active connections, it will only stop new connections from being made, and it'll wait for the active connections to get closed (by the client).
If clients are connected that keep a connection open (say using a keep-alive connection), the callback may never get called.
There's a package called http-shutdown that will help with gracefully shutting down an HTTP server.

Is it possible to wait for a child_process in process.on('exit', ...) method

I'm trying the following
process.on('exit', function() {
child_process.exec('echo hello', /*...*/);
}
and want to delay exit until the child process has finished.
Is this possible?
Nope, according to doc, exit event is too late to bind any async events. You should instead listen for beforeExit event.
Emitted when the process is about to exit. There is no way to prevent
the exiting of the event loop at this point, and once all exit
listeners have finished running the process will exit. Therefore you
must only perform synchronous operations in this handler.
In beforeExit you can do async operation and exit manually:
process.on('beforeExit', function() {
setTimeout(function(){ //run async code
console.log('beforeExit')
process.exit(0); //exit manually
}, 1000);
});

Doing a cleanup action just before Node.js exits

I want to tell Node.js to always do something just before it exits, for whatever reason — Ctrl+C, an exception, or any other reason.
I tried this:
process.on('exit', function (){
console.log('Goodbye!');
});
I started the process, killed it, and nothing happened. I started it again, pressed Ctrl+C, and still nothing happened...
UPDATE:
You can register a handler for `process.on('exit')` and in any other case(`SIGINT` or unhandled exception) to call `process.exit()`
process.stdin.resume();//so the program will not close instantly
function exitHandler(options, exitCode) {
if (options.cleanup) console.log('clean');
if (exitCode || exitCode === 0) console.log(exitCode);
if (options.exit) process.exit();
}
//do something when app is closing
process.on('exit', exitHandler.bind(null,{cleanup:true}));
//catches ctrl+c event
process.on('SIGINT', exitHandler.bind(null, {exit:true}));
// catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR1', exitHandler.bind(null, {exit:true}));
process.on('SIGUSR2', exitHandler.bind(null, {exit:true}));
//catches uncaught exceptions
process.on('uncaughtException', exitHandler.bind(null, {exit:true}));
This only works if you call synchronous code inside the handler, otherwise it will call the handler indefinitely
The script below allows having a single handler for all exit conditions. It uses an app specific callback function to perform custom cleanup code.
cleanup.js
// Object to capture process exits and call app specific cleanup function
function noOp() {};
exports.Cleanup = function Cleanup(callback) {
// attach user callback to the process event emitter
// if no callback, it will still exit gracefully on Ctrl-C
callback = callback || noOp;
process.on('cleanup',callback);
// do app specific cleaning before exiting
process.on('exit', function () {
process.emit('cleanup');
});
// catch ctrl+c event and exit normally
process.on('SIGINT', function () {
console.log('Ctrl-C...');
process.exit(2);
});
//catch uncaught exceptions, trace, then exit normally
process.on('uncaughtException', function(e) {
console.log('Uncaught Exception...');
console.log(e.stack);
process.exit(99);
});
};
This code intercepts uncaught exceptions, Ctrl+C and normal exit events. It then calls a single optional user cleanup callback function before exiting, handling all exit conditions with a single object.
The module simply extends the process object instead of defining another event emitter. Without an app specific callback the cleanup defaults to a no op function. This was sufficient for my use where child processes were left running when exiting by Ctrl+C.
You can easily add other exit events such as SIGHUP as desired. Note: per NodeJS manual, SIGKILL cannot have a listener. The test code below demonstrates various ways of using cleanup.js
// test cleanup.js on version 0.10.21
// loads module and registers app specific cleanup callback...
var cleanup = require('./cleanup').Cleanup(myCleanup);
//var cleanup = require('./cleanup').Cleanup(); // will call noOp
// defines app specific callback...
function myCleanup() {
console.log('App specific cleanup code...');
};
// All of the following code is only needed for test demo
// Prevents the program from closing instantly
process.stdin.resume();
// Emits an uncaught exception when called because module does not exist
function error() {
console.log('error');
var x = require('');
};
// Try each of the following one at a time:
// Uncomment the next line to test exiting on an uncaught exception
//setTimeout(error,2000);
// Uncomment the next line to test exiting normally
//setTimeout(function(){process.exit(3)}, 2000);
// Type Ctrl-C to test forced exit
This catches every exit event I can find that can be handled. Seems quite reliable and clean so far.
[`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`].forEach((eventType) => {
process.on(eventType, cleanUpServer.bind(null, eventType));
})
"exit" is an event that gets triggered when node finish it's event loop internally, it's not triggered when you terminate the process externally.
What you're looking for is executing something on a SIGINT.
The docs at http://nodejs.org/api/process.html#process_signal_events give an example:
Example of listening for SIGINT:
// Start reading from stdin so we don't exit.
process.stdin.resume();
process.on('SIGINT', function () {
console.log('Got SIGINT. Press Control-D to exit.');
});
Note: this seems to interrupt the sigint and you would need to call process.exit() when you finish with your code.
function fnAsyncTest(callback) {
require('fs').writeFile('async.txt', 'bye!', callback);
}
function fnSyncTest() {
for (var i = 0; i < 10; i++) {}
}
function killProcess() {
if (process.exitTimeoutId) {
return;
}
process.exitTimeoutId = setTimeout(() => process.exit, 5000);
console.log('process will exit in 5 seconds');
fnAsyncTest(function() {
console.log('async op. done', arguments);
});
if (!fnSyncTest()) {
console.log('sync op. done');
}
}
// https://nodejs.org/api/process.html#process_signal_events
process.on('SIGTERM', killProcess);
process.on('SIGINT', killProcess);
process.on('uncaughtException', function(e) {
console.log('[uncaughtException] app will be terminated: ', e.stack);
killProcess();
/**
* #https://nodejs.org/api/process.html#process_event_uncaughtexception
*
* 'uncaughtException' should be used to perform synchronous cleanup before shutting down the process.
* It is not safe to resume normal operation after 'uncaughtException'.
* If you do use it, restart your application after every unhandled exception!
*
* You have been warned.
*/
});
console.log('App is running...');
console.log('Try to press CTRL+C or SIGNAL the process with PID: ', process.pid);
process.stdin.resume();
// just for testing
Just wanted to mention death package here: https://github.com/jprichardson/node-death
Example:
var ON_DEATH = require('death')({uncaughtException: true}); //this is intentionally ugly
ON_DEATH(function(signal, err) {
//clean up code here
})
async-exit-hook seems to be the most up-to-date solution for handling this problem. It's a forked/re-written version of exit-hook that supports async code before exiting.
I need to do an asynchronous cleanup action on exit, none of the answers in this question worked for me.
So I tried it myself, and finally found this:
process.once('uncaughtException', async () => {
await cleanup()
process.exit(0)
})
process.once('SIGINT', () => { throw new Error() })
After playing around with other answer, here is my solution for this task. Implementing this way helps me centralize cleanup in one place, preventing double handling the cleanup.
I would like to route all other exiting codes to 'exit' code.
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
process.on(eventType, exitRouter.bind(null, { exit: true }));
})
What the exitRouter does is calling process.exit()
function exitRouter(options, exitCode) {
if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
if (options.exit) process.exit();
}
On 'exit', handle the clean up with a new function
function exitHandler(exitCode) {
console.log(`ExitCode ${exitCode}`);
console.log('Exiting finally...')
}
process.on('exit', exitHandler)
For the demo purpose, this is link to my gist. In the file, i add a setTimeout to fake the process running.
If you run node node-exit-demo.js and do nothing, then after 2 seconds, you see the log:
The service is finish after a while.
ExitCode 0
Exiting finally...
Else if before the service finish, you terminate by ctrl+C, you'll see:
^CExitCode SIGINT
ExitCode 0
Exiting finally...
What happened is the Node process exited initially with code SIGINT, then it routes to process.exit() and finally exited with exit code 0.
io.js has an exit and a beforeExit event, which do what you want.
In the case where the process was spawned by another node process, like:
var child = spawn('gulp', ['watch'], {
stdio: 'inherit',
});
And you try to kill it later, via:
child.kill();
This is how you handle the event [on the child]:
process.on('SIGTERM', function() {
console.log('Goodbye!');
});
Here's a nice hack for windows
process.on('exit', async () => {
require('fs').writeFileSync('./tmp.js', 'crash', 'utf-8')
});

Resources