Doing a cleanup action just before Node.js exits

node.js

node.js Problem Overview


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...

node.js Solutions


Solution 1 - node.js

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

Solution 2 - node.js

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 

Solution 3 - node.js

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

Solution 4 - node.js

"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.

Solution 5 - node.js

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

Solution 6 - node.js

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

Solution 7 - node.js

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.

Solution 8 - node.js

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

Solution 9 - node.js

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.

  1. 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 }));
})
  1. 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();
}
  1. 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.

Solution 10 - node.js

io.js has an exit and a beforeExit event, which do what you want.

Solution 11 - node.js

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

Solution 12 - node.js

Here's a nice hack for windows

process.on('exit', async () => {
    require('fs').writeFileSync('./tmp.js', 'crash', 'utf-8')
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionErel Segal-HaleviView Question on Stackoverflow
Solution 1 - node.jsEmil CondreaView Answer on Stackoverflow
Solution 2 - node.jsCanyonCasaView Answer on Stackoverflow
Solution 3 - node.jslight24bulbsView Answer on Stackoverflow
Solution 4 - node.jsuser1278519View Answer on Stackoverflow
Solution 5 - node.jsAbdullahView Answer on Stackoverflow
Solution 6 - node.jsgorodezkiyView Answer on Stackoverflow
Solution 7 - node.jsCory DanielsonView Answer on Stackoverflow
Solution 8 - node.jsBlackGloryView Answer on Stackoverflow
Solution 9 - node.jsLe HieuView Answer on Stackoverflow
Solution 10 - node.jsGolo RodenView Answer on Stackoverflow
Solution 11 - node.jsHanumanView Answer on Stackoverflow
Solution 12 - node.jsMendel JacksView Answer on Stackoverflow