Skip to content

Commit ef45fd4

Browse files
committed
process: add optional timeout to process.exit()
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).
1 parent 44a4032 commit ef45fd4

File tree

3 files changed

+124
-2
lines changed

3 files changed

+124
-2
lines changed

doc/api/process.md

+56-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,35 @@ process.on('exit', (code) => {
4444
console.log('About to exit with code:', code);
4545
});
4646
```
47+
## Event: 'exitingSoon'
48+
49+
Emitted when `process.exit()` is called using the optional `timeout` argument.
50+
51+
Calling `process.exit()` with the optional `timeout` creates an internal timer
52+
that will exit the Node.js process after the given period of time (in
53+
milliseconds). Each `exitingSoon` listener registered at the time the
54+
`process.exit()` was called will be invoked and passed a callback that must be
55+
called when the listener has completed it's work and is ready for the process
56+
to exit. The process will exit either when all of the listeners have called
57+
the callback indicating that they are ready or when the internal exit timer
58+
is triggered, whichever comes first.
59+
60+
```js
61+
// Some ongoing task that would normally keep the event loop active
62+
const timer1 = setInterval(() => {}, 1000);
63+
64+
// Register an exitingSoon handler to clean up before exit
65+
process.on('exitingSoon', (ready) => {
66+
setImmediate(() => {
67+
// Clean up resources
68+
clearInterval(timer1);
69+
// Notify that we're done
70+
ready();
71+
});
72+
});
73+
74+
process.exit(0, 10000);
75+
```
4776

4877
## Event: 'message'
4978

@@ -704,7 +733,7 @@ Example:
704733
```
705734

706735

707-
## process.exit([code])
736+
## process.exit([code][, timeout])
708737

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

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

749+
When the optional `timeout` value is specified, an internal timer will be
750+
created that will end the process after `timeout` milliseconds. Any listeners
751+
registered for the `'exitingSoon'` event will be invoked and will be passed
752+
a callback that should be called when the listener is ready for the exit to
753+
proceed. The process will exit either when all listeners have signaled that
754+
they are ready (by invoking the callback) or when the exit timer expires,
755+
whichever comes first.
756+
757+
The `timeout` argument is ignored if it is not a positive, finite number.
758+
759+
```js
760+
// Some ongoing task that would normally keep the event loop active
761+
const timer1 = setInterval(() => {}, 1000);
762+
763+
// Register an exitingSoon handler to clean up before exit
764+
process.on('exitingSoon', (ready) => {
765+
setImmediate(() => {
766+
// Clean up resources
767+
clearInterval(timer1);
768+
// Notify that we're done
769+
ready();
770+
});
771+
});
772+
773+
process.exit(0, 10000);
774+
```
720775

721776
## process.exitCode
722777

lib/internal/process.js

+39-1
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,48 @@ function setupConfig(_source) {
7070

7171
function setupKillAndExit() {
7272

73-
process.exit = function(code) {
73+
const kExitTimer = Symbol('kExitTimer');
74+
75+
function exitWithTimeout(code, timeout) {
76+
// The way this works is simple. When exitWithTimeout is called, the
77+
// number of listeners registered for the `exitingSoon` event on
78+
// process is grabbed. A callback is created and passed to each of
79+
// the listeners on emit. When each is done doing it's thing, it
80+
// invokes the callback, which counts down. Once the counter hits
81+
// zero, the real process.exit is called. In the meantime, an unref'd
82+
// timer is created to run proecss.exit() at a given timeout just in
83+
// case the cleanup code takes too long. Returns true if the method
84+
// schedules the exit, returns false if an exit has already been
85+
// scheduled.
86+
if (process[kExitTimer] !== undefined)
87+
return false; // Already called!
88+
var count = process.listenerCount('exitingSoon');
89+
function ready() {
90+
count--;
91+
if (count <= 0) {
92+
// If we're here, we beat the exit timeout, clear it and exit.
93+
clearTimeout(process[kExitTimer]);
94+
process.exit(code);
95+
}
96+
}
97+
process[kExitTimer] = setTimeout(() => {
98+
process.exit(code);
99+
}, timeout);
100+
process[kExitTimer].unref();
101+
process.emit('exitingSoon', ready);
102+
return true;
103+
}
104+
105+
process.exit = function(code, timeout) {
74106
if (code || code === 0)
75107
process.exitCode = code;
76108

109+
if (timeout) {
110+
timeout |= 0;
111+
if (timeout > 0)
112+
return exitWithTimeout(code, timeout);
113+
}
114+
77115
if (!process._exiting) {
78116
process._exiting = true;
79117
process.emit('exit', process.exitCode || 0);
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
// Schedule something that'll keep the loop active normally
7+
const timer1 = setInterval(() => {}, 1000);
8+
const timer2 = setInterval(() => {}, 1000);
9+
10+
// Register an exitingSoon handler to clean up before exit
11+
process.on('exitingSoon', common.mustCall((ready) => {
12+
// Simulate some async task
13+
assert.strictEqual(process.exitCode, 0);
14+
// Shouldn't be callable twice
15+
assert.strictEqual(process.exit(0, 10000), false);
16+
setImmediate(() => {
17+
// Clean up resources
18+
clearInterval(timer1);
19+
// Notify that we're done
20+
ready();
21+
});
22+
}));
23+
24+
process.on('exitingSoon', common.mustCall((ready) => {
25+
clearInterval(timer2);
26+
ready();
27+
}));
28+
29+
assert.strictEqual(process.exit(0, 10000), true);

0 commit comments

Comments
 (0)