Skip to content

Commit

Permalink
process: add optional timeout to process.exit()
Browse files Browse the repository at this point in the history
When set, an internal timer will be set that will exit the
process at the given timeout. In the meantime, the registered
listeners for process.on('exitingSoon') will be invoked and
passed a callback to be called when the handler is ready for
the process to exit. The process will exit either when the
internal timer fires or all the callbacks are called, whichever
comes first.

This is an attempt to deal more intelligently with resource
cleanup and async op completion on exit

(see nodejs#6456).
  • Loading branch information
jasnell committed Apr 29, 2016
1 parent 44a4032 commit ef45fd4
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 2 deletions.
57 changes: 56 additions & 1 deletion doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,35 @@ process.on('exit', (code) => {
console.log('About to exit with code:', code);
});
```
## Event: 'exitingSoon'

Emitted when `process.exit()` is called using the optional `timeout` argument.

Calling `process.exit()` with the optional `timeout` creates an internal timer
that will exit the Node.js process after the given period of time (in
milliseconds). Each `exitingSoon` listener registered at the time the
`process.exit()` was called will be invoked and passed a callback that must be
called when the listener has completed it's work and is ready for the process
to exit. The process will exit either when all of the listeners have called
the callback indicating that they are ready or when the internal exit timer
is triggered, whichever comes first.

```js
// Some ongoing task that would normally keep the event loop active
const timer1 = setInterval(() => {}, 1000);

// Register an exitingSoon handler to clean up before exit
process.on('exitingSoon', (ready) => {
setImmediate(() => {
// Clean up resources
clearInterval(timer1);
// Notify that we're done
ready();
});
});

process.exit(0, 10000);
```

## Event: 'message'

Expand Down Expand Up @@ -704,7 +733,7 @@ Example:
```


## process.exit([code])
## process.exit([code][, timeout])

Ends the process with the specified `code`. If omitted, exit uses the
'success' code `0`.
Expand All @@ -717,6 +746,32 @@ process.exit(1);

The shell that executed Node.js should see the exit code as 1.

When the optional `timeout` value is specified, an internal timer will be
created that will end the process after `timeout` milliseconds. Any listeners
registered for the `'exitingSoon'` event will be invoked and will be passed
a callback that should be called when the listener is ready for the exit to
proceed. The process will exit either when all listeners have signaled that
they are ready (by invoking the callback) or when the exit timer expires,
whichever comes first.

The `timeout` argument is ignored if it is not a positive, finite number.

```js
// Some ongoing task that would normally keep the event loop active
const timer1 = setInterval(() => {}, 1000);

// Register an exitingSoon handler to clean up before exit
process.on('exitingSoon', (ready) => {
setImmediate(() => {
// Clean up resources
clearInterval(timer1);
// Notify that we're done
ready();
});
});

process.exit(0, 10000);
```

## process.exitCode

Expand Down
40 changes: 39 additions & 1 deletion lib/internal/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,48 @@ function setupConfig(_source) {

function setupKillAndExit() {

process.exit = function(code) {
const kExitTimer = Symbol('kExitTimer');

function exitWithTimeout(code, timeout) {
// The way this works is simple. When exitWithTimeout is called, the
// number of listeners registered for the `exitingSoon` event on
// process is grabbed. A callback is created and passed to each of
// the listeners on emit. When each is done doing it's thing, it
// invokes the callback, which counts down. Once the counter hits
// zero, the real process.exit is called. In the meantime, an unref'd
// timer is created to run proecss.exit() at a given timeout just in
// case the cleanup code takes too long. Returns true if the method
// schedules the exit, returns false if an exit has already been
// scheduled.
if (process[kExitTimer] !== undefined)
return false; // Already called!
var count = process.listenerCount('exitingSoon');
function ready() {
count--;
if (count <= 0) {
// If we're here, we beat the exit timeout, clear it and exit.
clearTimeout(process[kExitTimer]);
process.exit(code);
}
}
process[kExitTimer] = setTimeout(() => {
process.exit(code);
}, timeout);
process[kExitTimer].unref();
process.emit('exitingSoon', ready);
return true;
}

process.exit = function(code, timeout) {
if (code || code === 0)
process.exitCode = code;

if (timeout) {
timeout |= 0;
if (timeout > 0)
return exitWithTimeout(code, timeout);
}

if (!process._exiting) {
process._exiting = true;
process.emit('exit', process.exitCode || 0);
Expand Down
29 changes: 29 additions & 0 deletions test/parallel/test-process-exit-timeout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

const common = require('../common');
const assert = require('assert');

// Schedule something that'll keep the loop active normally
const timer1 = setInterval(() => {}, 1000);
const timer2 = setInterval(() => {}, 1000);

// Register an exitingSoon handler to clean up before exit
process.on('exitingSoon', common.mustCall((ready) => {
// Simulate some async task
assert.strictEqual(process.exitCode, 0);
// Shouldn't be callable twice
assert.strictEqual(process.exit(0, 10000), false);
setImmediate(() => {
// Clean up resources
clearInterval(timer1);
// Notify that we're done
ready();
});
}));

process.on('exitingSoon', common.mustCall((ready) => {
clearInterval(timer2);
ready();
}));

assert.strictEqual(process.exit(0, 10000), true);

0 comments on commit ef45fd4

Please sign in to comment.