process: fix process.exitCode handling for fatalException

* set process.exitCode before calling 'exit' handlers so that there will
  not be a situation where process.exitCode !== code in 'exit' callback
  during uncaughtException handling
* don't ignore process.exitCode set in 'exit' callback when failed with
  uncaughtException and there is no uncaughtException listener

PR-URL: https://github.com/nodejs/node/pull/21739
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
This commit is contained in:
Denys Otrishko 2018-07-10 18:44:16 +03:00 committed by Anna Henningsen
parent 998f9ffd42
commit 35326f27fd
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
7 changed files with 109 additions and 8 deletions

View File

@ -151,9 +151,13 @@ added: v0.1.18
The `'uncaughtException'` event is emitted when an uncaught JavaScript
exception bubbles all the way back to the event loop. By default, Node.js
handles such exceptions by printing the stack trace to `stderr` and exiting.
handles such exceptions by printing the stack trace to `stderr` and exiting
with code 1, overriding any previously set [`process.exitCode`][].
Adding a handler for the `'uncaughtException'` event overrides this default
behavior.
behavior. You may also change the [`process.exitCode`][] in
`'uncaughtException'` handler which will result in process exiting with
provided exit code, otherwise in the presence of such handler the process will
exit with 0.
The listener function is called with the `Error` object passed as the only
argument.

View File

@ -475,6 +475,7 @@
try {
if (!process._exiting) {
process._exiting = true;
process.exitCode = 1;
process.emit('exit', 1);
}
} catch {

View File

@ -454,9 +454,6 @@ function setupChild(evalScript) {
debug(`[${threadId}] fatal exception caught = ${caught}`);
if (!caught) {
// set correct code (uncaughtException) for [kOnExit](code) handler
process.exitCode = 1;
let serialized;
try {
serialized = serializeError(error);

View File

@ -1439,7 +1439,16 @@ void FatalException(Isolate* isolate,
exit(7);
} else if (caught->IsFalse()) {
ReportException(env, error, message);
exit(1);
// fatal_exception_function call before may have set a new exit code ->
// read it again, otherwise use default for uncaughtException 1
Local<String> exit_code = env->exit_code_string();
Local<Value> code;
if (!process_object->Get(env->context(), exit_code).ToLocal(&code) ||
!code->IsInt32()) {
exit(1);
}
exit(code.As<v8::Int32>()->Value());
}
}
}

View File

@ -34,6 +34,14 @@ switch (process.argv[2]) {
return child4();
case 'child5':
return child5();
case 'child6':
return child6();
case 'child7':
return child7();
case 'child8':
return child8();
case 'child9':
return child9();
case undefined:
return parent();
default:
@ -43,6 +51,7 @@ switch (process.argv[2]) {
function child1() {
process.exitCode = 42;
process.on('exit', function(code) {
assert.strictEqual(process.exitCode, 42);
assert.strictEqual(code, 42);
});
}
@ -50,6 +59,7 @@ function child1() {
function child2() {
process.exitCode = 99;
process.on('exit', function(code) {
assert.strictEqual(process.exitCode, 42);
assert.strictEqual(code, 42);
});
process.exit(42);
@ -58,6 +68,7 @@ function child2() {
function child3() {
process.exitCode = 99;
process.on('exit', function(code) {
assert.strictEqual(process.exitCode, 0);
assert.strictEqual(code, 0);
});
process.exit(0);
@ -66,7 +77,7 @@ function child3() {
function child4() {
process.exitCode = 99;
process.on('exit', function(code) {
if (code !== 1) {
if (code !== 1 || process.exitCode !== 1) {
console.log('wrong code! expected 1 for uncaughtException');
process.exit(99);
}
@ -77,11 +88,50 @@ function child4() {
function child5() {
process.exitCode = 95;
process.on('exit', function(code) {
assert.strictEqual(process.exitCode, 95);
assert.strictEqual(code, 95);
process.exitCode = 99;
});
}
function child6() {
process.on('exit', function(code) {
assert.strictEqual(process.exitCode, 0);
assert.strictEqual(code, 0);
});
process.on('uncaughtException', () => {});
throw new Error('ok');
}
function child7() {
process.on('exit', function(code) {
assert.strictEqual(process.exitCode, 97);
assert.strictEqual(code, 97);
});
process.on('uncaughtException', () => {
process.exitCode = 97;
});
throw new Error('ok');
}
function child8() {
process.on('exit', function(code) {
assert.strictEqual(process.exitCode, 1);
assert.strictEqual(code, 1);
process.exitCode = 98;
});
throw new Error('ok');
}
function child9() {
process.on('exit', function(code) {
assert.strictEqual(process.exitCode, 1);
assert.strictEqual(code, 1);
process.exitCode = 0;
});
throw new Error('ok');
}
function parent() {
const { spawn } = require('child_process');
const node = process.execPath;
@ -102,4 +152,8 @@ function parent() {
test('child3', 0);
test('child4', 1);
test('child5', 99);
test('child6', 0);
test('child7', 97);
test('child8', 98);
test('child9', 0);
}

View File

@ -35,6 +35,10 @@ if (!process.env.HAS_STARTED_WORKER) {
return child6();
case 'child7':
return child7();
case 'child8':
return child8();
case 'child9':
return child9();
default:
throw new Error('invalid');
}
@ -105,6 +109,24 @@ function child7() {
throw new Error('ok');
}
function child8() {
process.on('exit', (code) => {
assert.strictEqual(process.exitCode, 1);
assert.strictEqual(code, 1);
process.exitCode = 98;
});
throw new Error('ok');
}
function child9() {
process.on('exit', function(code) {
assert.strictEqual(process.exitCode, 1);
assert.strictEqual(code, 1);
process.exitCode = 0;
});
throw new Error('ok');
}
function parent() {
const test = (arg, exit, error = null) => {
const w = new Worker(__filename);
@ -116,7 +138,9 @@ function parent() {
}));
if (error) {
w.on('error', common.mustCall((err) => {
assert(error.test(err));
console.log(err);
assert(error.test(err),
`wrong error for ${arg}\nexpected:${error} but got:${err}`);
}));
}
w.postMessage(arg);
@ -129,4 +153,6 @@ function parent() {
test('child5', 99);
test('child6', 0);
test('child7', 97);
test('child8', 98, /^Error: ok$/);
test('child9', 0, /^Error: ok$/);
}

View File

@ -10,6 +10,7 @@ if (!process.env.HAS_STARTED_WORKER) {
const w = new Worker(__filename);
w.on('message', common.mustNotCall());
w.on('error', common.mustCall((err) => {
console.log(err.message);
assert(/^Error: foo$/.test(err));
}));
w.on('exit', common.mustCall((code) => {
@ -17,5 +18,14 @@ if (!process.env.HAS_STARTED_WORKER) {
assert.strictEqual(code, 1);
}));
} else {
// cannot use common.mustCall as it cannot catch this
let called = false;
process.on('exit', (code) => {
if (!called) {
called = true;
} else {
assert.fail('Exit callback called twice in worker');
}
});
throw new Error('foo');
}