diff --git a/doc/api/process.md b/doc/api/process.md index bdadaaea88c164..456d6c0aa5afd3 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -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' @@ -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`. @@ -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 diff --git a/lib/internal/process.js b/lib/internal/process.js index 17ca5bc326c08a..c953bb72789abe 100644 --- a/lib/internal/process.js +++ b/lib/internal/process.js @@ -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); diff --git a/test/parallel/test-process-exit-timeout.js b/test/parallel/test-process-exit-timeout.js new file mode 100644 index 00000000000000..0b4b543565373b --- /dev/null +++ b/test/parallel/test-process-exit-timeout.js @@ -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);