Socket
Socket
Sign inDemoInstall

node-cleanup

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-cleanup - npm Package Compare versions

Comparing version 1.0.1 to 2.0.0

tests/bin/grandchild.js

163

node-cleanup.js

@@ -1,60 +0,127 @@

/**
* nodeCleanup() installs a function that performs cleanup activities just
* before the node process exits. The cleanup function runs when the process
* exits normally, when the user presses ctrl-C, and when an exception is
* uncaught. The caller may specify the termination messages to use.
*
* Call this function multiple times to install multiple cleanup handlers.
* Only the messages provided with the first call are used.
*
* See https://github.com/jtlapp/node-cleanup for more information. Code
* largely borrowed from http://stackoverflow.com/a/21947851/650894.
*/
/******************************************************************************
nodeCleanup() installs functions -- cleanup handlers -- that perform cleanup activities just before the node process exits, except on SIGKILL, which can't be intercepted. nodeCleanup() can also install messages to be written to stderr on either SIGINT or an uncaught exception.
Each cleanup handler has the following (FlowType) signature:
cleanupHandler(exitCode: number|null, signal: string|null): boolean?
If the process is terminating for a reason other than a signal, exitCode is an integer that provides the reason for termination, and signal is null. If the process received a POSIX signal, signal is the signal's string name, and exitCode is null. These are also the arguments passed to a process' `exit` event handler, mirrored in node-cleanup for consistency.
The process terminates after cleanup, except possibly on signals. If any cleanup handler returns a boolean false for a signal, the process will not exit; otherwise the process exits. SIGKILL cannot be intercepted.
Install a cleanup handler as follows:
var nodeCleanup = require('node-cleanup');
nodeCleanup(cleanupHandler, terminationMessages);
nodeCleanup() may be called multiple times to install multiple cleanup handlers. However, only the termination messages established by the first call get used.
The following uninstalls all cleanup handlers and may be called multiple times in succession:
nodeCleanup.uninstall();
This module has its origin in code by @CanyonCasa at http://stackoverflow.com/a/21947851/650894, but the module was significantly rewritten to resolve issues raised by @Banjocat at http://stackoverflow.com/questions/14031763/doing-a-cleanup-action-just-before-node-js-exits#comment68567869_21947851. It has also been extended for greater configurability.
******************************************************************************/
var installed = false;
//// CONSTANTS ////////////////////////////////////////////////////////////////
module.exports = function nodeCleanup(cleanupHandler, messages) {
var DEFAULT_SIGINT_MSG = '[ctrl-C]';
var DEFAULT_EXCEPTION_MSG = 'Uncaught exception...';
//// CONFIGURATION ////////////////////////////////////////////////////////////
// attach user callback to the process event emitter.
// if no callback, it will still exit gracefully on Ctrl-C
cleanupHandler = cleanupHandler || noOp;
process.on('cleanup', cleanupHandler);
// only install the termination handlers once
if (!installed) {
install(messages);
installed = true;
}
};
var cleanupHandlers = null; // array of cleanup handlers to call
var exceptionMessage = null; // stderr message for uncaught exceptions
function install(messages) {
var sigintHandler; // POSIX signal handlers
var sighupHandler;
var sigquitHandler;
var sigtermHandler;
messages = messages || {};
if (typeof messages.ctrl_C !== 'string')
messages.ctrl_C = '[ctrl-C]';
if (typeof messages.uncaughtException !== 'string')
messages.uncaughtException = 'Uncaught exception...';
//// HANDLERS /////////////////////////////////////////////////////////////////
// do app-specific cleaning before exiting
process.on('exit', function () {
process.emit('cleanup');
function signalHandler(signal, message)
{
var exit = true;
cleanupHandlers.forEach(function (cleanup) {
if (cleanup(null, signal) === false)
exit = false;
});
if (exit) {
if (message !== '')
process.stderr.write(message + "\n");
uninstall(); // don't cleanup again
// necessary to communicate the signal to the parent process
process.kill(process.pid, signal);
}
}
// catch ctrl+c event and exit normally
process.on('SIGINT', function () {
if (messages.ctrl_C !== '')
process.stderr.write(messages.ctrl_C + "\n");
process.exit(2);
});
function exceptionHandler(e)
{
if (exceptionMessage !== '')
process.stderr.write(exceptionMessage + "\n");
process.stderr.write(e.stack + "\n");
process.exit(1); // will call exitHandler() for cleanup
}
//catch uncaught exceptions, trace, then exit normally
process.on('uncaughtException', function(e) {
if (messages.uncaughtException !== '') {
process.stderr.write(messages.uncaughtException + "\n");
process.stderr.write(e.stack + "\n");
}
process.exit(99);
function exitHandler(exitCode, signal)
{
cleanupHandlers.forEach(function (cleanup) {
cleanup(exitCode, signal);
});
}
function noOp() {}; // for just the benefit of graceful SIGINTs
//// MAIN /////////////////////////////////////////////////////////////////////
function install(cleanupHandler, messages)
{
if (cleanupHandlers === null) {
cleanupHandlers = []; // establish before installing handlers
messages = messages || {};
if (typeof messages.ctrl_C !== 'string')
messages.ctrl_C = DEFAULT_SIGINT_MSG;
if (typeof messages.uncaughtException !== 'string')
messages.uncaughtException = DEFAULT_EXCEPTION_MSG;
exceptionMessage = messages.uncaughtException;
sigintHandler = signalHandler.bind(this, 'SIGINT', messages.ctrl_C);
sighupHandler = signalHandler.bind(this, 'SIGHUP', '');
sigquitHandler = signalHandler.bind(this, 'SIGQUIT', '');
sigtermHandler = signalHandler.bind(this, 'SIGTERM', '');
process.on('SIGINT', sigintHandler);
process.on('SIGHUP', sighupHandler);
process.on('SIGQUIT', sigquitHandler);
process.on('SIGTERM', sigtermHandler);
process.on('uncaughtException', exceptionHandler);
process.on('exit', exitHandler);
cleanupHandlers.push(cleanupHandler || noCleanup);
}
else if (cleanupHandler)
cleanupHandlers.push(cleanupHandler);
}
function uninstall()
{
if (cleanupHandlers !== null) {
process.removeListener('SIGINT', sigintHandler);
process.removeListener('SIGHUP', sighupHandler);
process.removeListener('SIGQUIT', sigquitHandler);
process.removeListener('SIGTERM', sigtermHandler);
process.removeListener('uncaughtException', exceptionHandler);
process.removeListener('exit', exitHandler);
cleanupHandlers = null; // null only after uninstalling
}
}
function noCleanup()
{
return true; // signals will always terminate process
}
//// EXPORTS //////////////////////////////////////////////////////////////////
module.exports = install;
install.uninstall = uninstall;
{
"name": "node-cleanup",
"version": "1.0.1",
"description": "installs a cleanup handler that always runs on exiting node",
"version": "2.0.0",
"description": "installs cleanup handlers that always run on exiting node",
"main": "node-cleanup.js",

@@ -35,3 +35,7 @@ "scripts": {

},
"homepage": "https://github.com/jtlapp/node-cleanup#readme"
"homepage": "https://github.com/jtlapp/node-cleanup#readme",
"devDependencies": {
"memory-streams": "^0.1.0",
"tap": "^8.0.1"
}
}
# node-cleanup
installs a cleanup handler that always runs on exiting node
installs custom cleanup handlers that run on exiting node

@@ -11,12 +11,29 @@ ## Installation

## Overview
`nodeCleanup()` installs functions that perform cleanup activities just before the node process exits. Let's call these functions "cleanup handlers." The cleanup handlers run under the following conditions:
- When the process exits normally (exit code 0).
- When the process exits due to an error, such as an uncaught exception (exit code 1).
- When the process receives one of the following POSIX signals: SIGINT (e.g. *Ctrl-C*), SIGHUP, SIGQUIT, or SIGTERM.
This solution has the following features:
- Allows cleanup handlers to behave as a function of exit code and signal.
- Allows multiple independent subsystems to install cleanup handlers.
- Allows for asynchronous cleanup on receiving a signal by postponing process termination.
- Allows for deferring to child processes the decision about whether a signal terminates the present process. For example, Emacs intercepts *Ctrl-C*, which should prevent its parent process from terminating.
- Allows for writing custom messages to `stderr` on SIGINT (e.g. *Ctrl-C*) and uncaught exceptions, regardless of the number of cleanup handlers installed.
- Allows for uninstalling all cleanup handlers, such as to change termination behavior after having intercepted and cleaned up for a signal.
The module also has an extensive test suite to help ensure reliability.
## Usage
nodeCleanup() installs a function that performs cleanup activities just before the node process exits. The cleanup function runs when the process exits normally, when the user presses *ctrl-C*, and when an exception is uncaught. The caller may specify the termination messages to use.
Here is the typical way to use `nodeCleanup()`:
You may call nodeCleanup() multiple times to install multiple cleanup handlers, but only the messages provided with the first call get used.
```js
var nodeCleanup = require('node-cleanup');
nodeCleanup(function () {
nodeCleanup(function (exitCode, signal) {
// release resources here before node exits

@@ -26,12 +43,61 @@ });

nodeCleanup() also ensures that *ctrl-C* is handled gracefully in contexts that already have exit handlers installed, such as Node Express. To receive just this benefit, the caller need not provide a cleanup handler.
If you only want to install your own messages for *Ctrl-C* and uncaught exception (either or both), you can do this:
By default, `nodeCleanup()` writes "[ctrl-C]" to `stderr` when interrupted and "Uncaught exception..." to `stderr` when an uncaught exception occurs. You may override either or both of these values in a second parameter:
```js
nodeCleanup(null, {
ctrl_C: "{^C}",
uncaughtException: "Uh oh. Look what happened:"
});
```
To get just the default `stderr` messages, without installing a cleanup handler:
```js
nodeCleanup();
```
You may also combine these to install a cleanup handler and `stderr` messages:
```js
nodeCleanup(function (exitCode, signal) {
// release resources here before node exits
}, {
ctrl_C: "{^C}",
uncaughtException: "Uh oh. Look what happened:"
});
```
You may perform asynchronous cleanup upon receiving a signal, as follows:
```js
nodeCleanup(function (exitCode, signal) {
if (signal) {
unsavedData.save(function done() {
// calling process.exit() won't inform parent process of signal
process.kill(process.pid, signal);
});
nodeCleanup.uninstall(); // don't call cleanup handler again
return false;
}
});
```
When you hit *Ctrl-C*, you send a SIGINT signal to each process in the current process group. A process group is set of processes that are all supposed to end together as a group instead of persisting independently. However, some programs, such as Emacs, intercept and repurpose SIGINT so that it does not end the process. In such cases, SIGINT should not end any processes of the group. Here is how you can delegate the decision to terminate to a child process:
```js
var nodeCleanup = require('node-cleanup');
var fork = require('child_process').fork;
nodeCleanup(function () {
var child = fork('path-to-child-script.js');
child.on('exit', function (exitCode, signal) {
child = null; // enable the cleanup handler
if (signal === 'SIGINT')
process.kill(process.pid, 'SIGINT');
});
nodeCleanup(function (exitCode, signal) {
if (child !== null && signal === 'SIGINT')
return false; // don't exit yet
// release resources here before node exits
}, { ctrl_C: '^C' });
});
```

@@ -41,16 +107,63 @@

`function nodeCleanup(cleanupHandler, messages)`
### `nodeCleanup()`
Install a cleanup handler that reliably runs when node exits. Both parameters are optional. Calling `nodeCleanup()` without a `cleanupHandler` still provides the benefit of ensuring that other installed exit handlers run on *ctrl-C*.
`nodeCleanup()` has the following ([FlowType](https://flowtype.org/docs/getting-started.html#_)) signature:
Call this function multiple times to install multiple cleanup handlers. Only the messages provided with the first call are used.
```js
function nodeCleanup(cleanupHandler?: Function, messages?: object): void
```
| Param | Description |
| --- | --- |
| cleanupHandler | A function that performs the final cleanup of resources before the node process exits. The function may write to `stderr` and `stdout`. It takes no parameters and can't abort the exit. The handler is optional, defaulting to a function that does nothing. |
| messages | An optional object mapping any of the keys `ctrl_C` and `uncaughtException` to the message strings that output to stderr. Set a message to the empty string `''` to prevent output to `stderr` for its case. Default messages are provided omitted messages. |
`nodeCleanup()` installs a cleanup handler. It may also assign messages to write to `stderr` on SIGINT or an uncaught exception. Both parameters are optional. If not `cleanupHandler` is provided, the `stderr` messages are still written. If no `messages` are provided, default `stderr` messages are written. Calling `nodeCleanup()` with no parameters just installs these default messages.
`cleanupHandler` is a cleanup handler callback and is described in its own section below. When null or undefined, termination events all result in the process terminating, including signals.
`messages` is an object mapping any of the keys `ctrl_C` and `uncaughtException` to message strings that output to `stderr`. Default messages are provided for omitted messages. Set a message to the empty string `''` inhibit the message.
`nodeCleanup()` may be called multiple times to install multiple cleanup handlers. Each of these handlers runs for each signal or termination condition. The first call to `nodeCleanup()` establishes the `stderr` messages; messages passed to subsequent calls are ignored.
### `nodeCleanup.uninstall()`
`nodeCleanup.uninstall()` uninstalls all installed cleanup handlers and voids the `stderr` message assignments. It may be called multiple times without harm.
This function is primarily useful when a signal occurs and the cleanup handler performs cleanup but disables immediate process termination. In this case, when it is finally time to terminate the process, the cleanup handlers shouldn't run again, so the process uninstalls the handlers before terminating itself.
### Cleanup Handlers
Each cleanup handler has the following ([FlowType](https://flowtype.org/docs/getting-started.html#_)) signature:
```js
function cleanupHandler(exitCode: number|null, signal: string|null): boolean?
```
If the process is terminating for a reason other than a POSIX signal, `exitCode` is the exit code, and `signal` is null. Otherwise, if the process received a signal, `signal` is the signal's string name, and `exitCode` is null. These are the arguments passed to a [child process `exit` event](https://nodejs.org/api/child_process.html#child_process_event_exit) handler, mirrored here in `node-cleanup` for consistency.
Node.js defines [these standard exit codes](https://nodejs.org/api/process.html#process_exit_codes), but it does not appear to use code values >128 for signals. According to the node.js docs, [these are the possible signals](http://man7.org/linux/man-pages/man7/signal.7.html).
The return value of a cleanup handler is only significant for signals. If any cleanup handler returns a boolean `false`, the process does not exit. If they all return `true` (or for backwards compatibility, no return value), the process exits, reporting the signal to the parent process as the reason for the exit. The process always exits after calling the cleanup handlers for non-signals.
When a cleanup handler returns `false` to prevent the process from exiting, the cleanup handler normally takes steps to ensure proper termination later. For example, the process may wait for asynchronous cleanup to complete, or it may wait for a child process to signal termination. Normally in these cases the process would use `nodeCleanup.uninstall()` to uninstall the cleanup handlers prior to the second termination to prevent them from running again.
A cleanup handler should never call `process.exit()`. If a handler prevents a signal from terminating the process but later wishes to terminate the process for reason of this signal, the process should call `process.kill(process.pid, signal)`. In particular, the process should **not** call `process.exit(128 + signalNumber)`, because while this does communicate the exit code to the parent process, it does not communicate the exit signal by the means that the [node.js `child_process` expects](https://nodejs.org/api/child_process.html#child_process_event_exit).
## Testing
This module includes an extensive test suite. You can run it from the module directory with either the [`tap`](http://www.node-tap.org/basics/) or [`subtap`](https://github.com/jtlapp/subtap) test runner, as follows:
```
sudo npm install -g tap
tap tests/*.js
```
or
```
sudo npm install -g subtap
subtap
```
(As of this writing, the test suite has only been run on a Mac. Behavior may vary from OS to OS, so I'm looking for feedback from other operating systems.)
## Credit
This code was borrowed and modified from [CanyonCasa](http://stackoverflow.com/users/3319552/canyoncasa)'s answer to a stackoverflow question. I found the code necessary for all my node projects. See [the stackoverflow answer](http://stackoverflow.com/a/21947851/650894) for more examples of use.
This module began by borrowing and modifying code from CanyonCasa's [answer to a stackoverflow question](http://stackoverflow.com/a/21947851/650894). I had found the code necessary for all my node projects. @Banjocat piped in with a [comment](http://stackoverflow.com/questions/14031763/doing-a-cleanup-action-just-before-node-js-exits/21947851#comment68567869_21947851) about how the solution didn't properly handle SIGINT. (See [this detailed explanation](https://www.cons.org/cracauer/sigint.html) of the SIGINT problem). I have completely rewritten the module to properly deal with SIGINT and other signals (I hope!). The rewrite also provides some additional flexibility I found myself needing in my projects.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc