Skip to content

Commit

Permalink
revised stderr message handling
Browse files Browse the repository at this point in the history
  • Loading branch information
jtlapp committed Dec 27, 2016
1 parent 9262780 commit 19b47e5
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 147 deletions.
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ nodeCleanup(function (exitCode, signal) {
If you only want to install your own messages for *Ctrl-C* and uncaught exception (either or both), you can do this:

```js
nodeCleanup(null, {
nodeCleanup({
ctrl_C: "{^C}",
uncaughtException: "Uh oh. Look what happened:"
});
```

To get just the default `stderr` messages, without installing a cleanup handler:
To get the default `stderr` messages, without installing a cleanup handler:

```js
nodeCleanup();
Expand Down Expand Up @@ -104,19 +104,22 @@ nodeCleanup(function (exitCode, signal) {

### `nodeCleanup()`

`nodeCleanup()` has the following ([FlowType](https://flowtype.org/docs/getting-started.html#_)) signature:
`nodeCleanup()` has the following available ([FlowType](https://flowtype.org/docs/getting-started.html#_)) signatures:

```js
function nodeCleanup(cleanupHandler?: Function, messages?: object): void
function nodeCleanup(cleanupHandler: Function): void
function nodeCleanup(cleanupHandler: Function, stderrMessages: object): void
function nodeCleanup(stderrMessages: object): void
function nodeCleanup(): void
```

`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.
The 1st form installs a cleanup handler. The 2nd form also assigns messages to write to `stderr` on SIGINT or an uncaught exception. The 3rd and 4th forms only assign messages to write to `stderr`, without installing a cleanup handler. The 4th form assigns default `stderr` 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.
`cleanupHandler` is a cleanup handler callback and is described in its own section below. When no cleanup handlers are installed, termination events all result in the process terminating, including signal events.

`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.
`stderrMessages` is an object mapping any of the keys `ctrl_C` and `uncaughtException` to message strings that output to `stderr`. Set a message to the empty string `''` inhibit a previously-assigned 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()` may be called multiple times to install multiple cleanup handlers or override previous messages. Each handler gets called on each signal or termination condition. The most recently assigned messages apply.

### `nodeCleanup.uninstall()`

Expand Down Expand Up @@ -162,12 +165,11 @@ subtap
## Incompatibilities with v1.0.x
TBD
`node-cleanup` v2+ is not fully compatible with v1.x. You may need to change your usage to upgrade. These are the potential incompatibilities:
- default messages
- catches SIGHUP, SIGQUIT, and SIGTERM
- The cleanup handlers now also run on SIGHUP, SIGQUIT, and SIGTERM, which were not getting cleanup processing before.
- `stderr` messages are handled quite differently. Previously, there were defaults that you had to override, and only your first message assignments applied. Now, the defaults **only** install with the parameterless call `nodeCleanup()`. Otherwise there are no messages unless you provide them. Moreover, the most recent message assignments are the ones that get used.
## Credit
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 that @zixia and I found ourselves needing for our respective projects.
65 changes: 43 additions & 22 deletions node-cleanup.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,17 @@ The process terminates after cleanup, except possibly on signals. If any cleanup
Install a cleanup handler as follows:
var nodeCleanup = require('node-cleanup');
nodeCleanup(cleanupHandler, terminationMessages);
nodeCleanup(cleanupHandler, stderrMessages);
Or to only install stderr messages:
nodeCleanup(stderrMessages);
Or to install the default stderr messages:
nodeCleanup() may be called multiple times to install multiple cleanup handlers. However, only the termination messages established by the first call get used.
nodeCleanup();
nodeCleanup() may be called multiple times to install multiple cleanup handlers. However, only the most recently installed stderr messages get used. The messages available are ctrl_C and uncaughtException.
The following uninstalls all cleanup handlers and may be called multiple times in succession:
Expand All @@ -25,13 +33,15 @@ This module has its origin in code by @CanyonCasa at http://stackoverflow.com/

//// CONSTANTS ////////////////////////////////////////////////////////////////

var DEFAULT_SIGINT_MSG = '[ctrl-C]';
var DEFAULT_EXCEPTION_MSG = 'Uncaught exception...';
var DEFAULT_MESSAGES = {
ctrl_C: '[ctrl-C]',
uncaughtException: 'Uncaught exception...'
};

//// CONFIGURATION ////////////////////////////////////////////////////////////

var cleanupHandlers = null; // array of cleanup handlers to call
var exceptionMessage = null; // stderr message for uncaught exceptions
var messages = null; // messages to write to stderr

var sigintHandler; // POSIX signal handlers
var sighupHandler;
Expand All @@ -40,16 +50,16 @@ var sigtermHandler;

//// HANDLERS /////////////////////////////////////////////////////////////////

function signalHandler(signal, message)
function signalHandler(signal)
{
var exit = true;
cleanupHandlers.forEach(function (cleanup) {
if (cleanup(null, signal) === false)
exit = false;
});
if (exit) {
if (message !== '')
process.stderr.write(message + "\n");
if (signal === 'SIGINT' && messages && messages.ctrl_C !== '')
process.stderr.write(messages.ctrl_C + "\n");
uninstall(); // don't cleanup again
// necessary to communicate the signal to the parent process
process.kill(process.pid, signal);
Expand All @@ -58,8 +68,8 @@ function signalHandler(signal, message)

function exceptionHandler(e)
{
if (exceptionMessage !== '')
process.stderr.write(exceptionMessage + "\n");
if (messages && messages.uncaughtException !== '')
process.stderr.write(messages.uncaughtException + "\n");
process.stderr.write(e.stack + "\n");
process.exit(1); // will call exitHandler() for cleanup
}
Expand All @@ -73,22 +83,33 @@ function exitHandler(exitCode, signal)

//// MAIN /////////////////////////////////////////////////////////////////////

function install(cleanupHandler, messages)
function install(cleanupHandler, stderrMessages)
{
if (cleanupHandler) {
if (typeof cleanupHandler === 'object') {
stderrMessages = cleanupHandler;
cleanupHandler = null;
}
}
else if (!stderrMessages)
stderrMessages = DEFAULT_MESSAGES;

if (stderrMessages) {
if (messages === null)
messages = { ctrl_C: '', uncaughtException: '' };
if (typeof stderrMessages.ctrl_C === 'string')
messages.ctrl_C = stderrMessages.ctrl_C;
if (typeof stderrMessages.uncaughtException === 'string')
messages.uncaughtException = stderrMessages.uncaughtException;
}

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', '');
sigintHandler = signalHandler.bind(this, 'SIGINT');
sighupHandler = signalHandler.bind(this, 'SIGHUP');
sigquitHandler = signalHandler.bind(this, 'SIGQUIT');
sigtermHandler = signalHandler.bind(this, 'SIGTERM');

process.on('SIGINT', sigintHandler);
process.on('SIGHUP', sighupHandler);
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "node-cleanup",
"version": "2.0.0",
"version": "2.1.0",
"description": "installs cleanup handlers that always run on exiting node",
"main": "node-cleanup.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "tap tests/*.js"
},
"repository": {
"type": "git",
Expand Down
9 changes: 7 additions & 2 deletions tests/bin/stackable.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Child process that installs node-cleanup for testing zero, one, or multiple (two
{
handlers; number; // 0, 1, or 2 concurrent cleanup handlers
messages0: object|null; // messages argument for no-cleanup call, if any
messages1: object|null; // messages argument for 1st nodeCleanup() call
messages2: object|null; // messages argument for 2nd nodeCleanup() call
return1: boolean; // return value of 1st cleanup handler
Expand Down Expand Up @@ -47,8 +48,12 @@ function cleanup2(exitCode, signal) {

//// MAIN /////////////////////////////////////////////////////////////////////

if (config.handlers === 0)
nodeCleanup();
if (config.handlers === 0) {
if (config.messages0)
nodeCleanup(config.messages0);
else
nodeCleanup();
}
else {
nodeCleanup(cleanup1, config.messages1);
if (config.handlers > 1)
Expand Down
Loading

0 comments on commit 19b47e5

Please sign in to comment.