Doing a cleanup action just before Node.js exits - node.js

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')
});

Related

process.on('SIGINT' multiple termination signals

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

How to log stack trace on node.js process error event

My node process is dying and I can't seem to log to a file when the process exits. It is a long running process invoked directly with node index.js:
// index.js
const fs = require('fs');
exports.getAllCars = (process => {
if (require.main === module) {
console.log(`Running process: ${process.getgid()}.`);
let out = fs.createWriteStream(`${__dirname}/process.log`);
// trying to handle process events here:
process.on('exit', code => out.write(`Exit: ${code}`));
return require('./lib/cars').getAllCars();
} else {
return require('./lib/cars').getAllCars;
}
})(process);
Also tried creating event handlers for error, uncaughtException. Nothing works when killing my process manually (with kill {pid}). The file process.log is created but nothing is there. Do writeable streams require a stream.end() to be called on completion?
According to Node.js documentation:
The 'exit' event is emitted when the Node.js process is about to exit
as a result of either:
The process.exit() method being called explicitly.
The Node.js event loop no longer having any additional work to perform.
So, if you start a process that should never end, it will never trigger.
Also, writable streams do not require to be closed:
If autoClose(an option from createWriteStream) is set to true (default
behavior) on error or end the file descriptor will be closed
automatically.
however, the createWriteStream function opens the file with flag 'w' by default, which means that the file will be overwritten every time (maybe this is the reason why you always see it empty). I suggest to use
fs.appendFileSync(file, data)
Here are the events that want to listen:
//catches ctrl+c event
//NOTE:
//If SIGINT has a listener installed, its default behavior will be removed (Node.js will no longer exit).
process.on('SIGINT', () => {
fs.appendFileSync(`${__dirname}/process.log`, `Received SIGINT\n`);
process.exit()
});
//emitted when an uncaught JavaScript exception bubbles
process.on('uncaughtException', (err) => {
fs.appendFileSync(`${__dirname}/process.log`, `Caught exception: ${err}\n`);
});
//emitted whenever a Promise is rejected and no error handler is attached to it
process.on('unhandledRejection', (reason, p) => {
fs.appendFileSync(`${__dirname}/process.log`, `Unhandled Rejection at: ${p}, reason: ${reason}\n`);
});
I suggest you put the code in a try catch block to find out whether its the code or some external cause which results in program termination.
and then check the log after the event...
try {
//your code
}catch(e) {
console.log(e.stack);
}

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");
});

Electron kill child_process.exec

I have an electron app that uses child_process.exec to run long running tasks.
I am struggling to manage when the user exits the app during those tasks.
If they exit my app or hit close the child processes continue to run until they finish however the electron app window has already closed and exited.
Is there a way to notify the user that there are process still running and when they have finished then close the app window?
All I have in my main.js is the standard code:
// Quit when all windows are closed.
app.on('window-all-closed', function() {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform != 'darwin') {
app.quit();
}
});
Should I be adding a check somewhere?
Thanks for your help
EDITED
I cannot seem to get the PID of the child_process until it has finished. This is my child_process code
var loader = child_process.exec(cmd, function(error, stdout, stderr) {
console.log(loader.pid)
if (error) {
console.log(error.message);
}
console.log('Loaded: ', value);
});
Should I be trying to get it in a different way?
So after everyones great comments I was able to update my code with a number of additions to get it to work, so am posting my updates for everyone else.
1) Change from child_process.exec to child_process.spawn
var loader = child_process.spawn('program', options, { detached: true })
2) Use the Electron ipcRenderer to communicate from my module to the main.js script. This allows me to send the PIDs to main.js
ipcRenderer.send('pid-message', loader.pid);
ipcMain.on('pid-message', function(event, arg) {
console.log('Main:', arg);
pids.push(arg);
});
3) Add those PIDs to array
4) In my main.js I added the following code to kill any PIDs that exist in the array before exiting the app.
// App close handler
app.on('before-quit', function() {
pids.forEach(function(pid) {
// A simple pid lookup
ps.kill( pid, function( err ) {
if (err) {
throw new Error( err );
}
else {
console.log( 'Process %s has been killed!', pid );
}
});
});
});
Thanks for everyones help.
ChildProcess emits an exit event when the process has finished - if you keep track of the current processes in an array, and have them remove themselves after the exit event fires, you should be able to just foreach over the remaining ones running ChildProcess.kill() when you exit your app.
This may not be 100% working code/not the best way of doing things, as I'm not in a position to test it right now, but it should be enough to set you down the right path.
var processes = [];
// Adding a process
var newProcess = child_process.exec("mycommand");
processes.push(newProcess);
newProcess.on("exit", function () {
processes.splice(processes.indexOf(newProcess), 1);
});
// App close handler
app.on('window-all-closed', function() {
if (process.platform != 'darwin') {
processes.forEach(function(proc) {
proc.kill();
});
app.quit();
}
});
EDIT: As shreik mentioned in a comment, you could also just store the PIDs in the array instead of the ChildProcess objects, then use process.kill(pid) to kill them. Might be a little more efficient!
Another solution. If you want to keep using exec()
In order to kill the child process running by exec() take a look to the module ps-tree. They exaplain what is happening.
in UNIX, a process may terminate by using the exit call, and it's
parent process may wait for that event by using the wait system call.
the wait system call returns the process identifier of a terminated
child, so that the parent tell which of the possibly many children has
terminated. If the parent terminates, however, all it's children have
assigned as their new parent the init process. Thus, the children
still have a parent to collect their status and execution statistics.
(from "operating system concepts")
SOLUTION: use ps-tree to get all processes that a child_process may have started, so that they
exec() actually works like this:
function exec (cmd, cb) {
spawn('sh', ['-c', cmd]);
...
}
So check the example and adapt it to your needs
var cp = require('child_process'),
psTree = require('ps-tree');
var child = cp.exec("node -e 'while (true);'", function () { /*...*/ });
psTree(child.pid, function (err, children) {
cp.spawn('kill', ['-9'].concat(children.map(function (p) { return p.PID })));
});

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);
});

Resources