Skip to content

Commit 961f6e8

Browse files
lundibunditargos
authored andcommitted
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: #21739 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
1 parent aa5994f commit 961f6e8

File tree

7 files changed

+109
-8
lines changed

7 files changed

+109
-8
lines changed

doc/api/process.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,13 @@ added: v0.1.18
151151

152152
The `'uncaughtException'` event is emitted when an uncaught JavaScript
153153
exception bubbles all the way back to the event loop. By default, Node.js
154-
handles such exceptions by printing the stack trace to `stderr` and exiting.
154+
handles such exceptions by printing the stack trace to `stderr` and exiting
155+
with code 1, overriding any previously set [`process.exitCode`][].
155156
Adding a handler for the `'uncaughtException'` event overrides this default
156-
behavior.
157+
behavior. You may also change the [`process.exitCode`][] in
158+
`'uncaughtException'` handler which will result in process exiting with
159+
provided exit code, otherwise in the presence of such handler the process will
160+
exit with 0.
157161

158162
The listener function is called with the `Error` object passed as the only
159163
argument.

lib/internal/bootstrap/node.js

+1
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@
449449
try {
450450
if (!process._exiting) {
451451
process._exiting = true;
452+
process.exitCode = 1;
452453
process.emit('exit', 1);
453454
}
454455
} catch {

lib/internal/worker.js

-3
Original file line numberDiff line numberDiff line change
@@ -454,9 +454,6 @@ function setupChild(evalScript) {
454454
debug(`[${threadId}] fatal exception caught = ${caught}`);
455455

456456
if (!caught) {
457-
// set correct code (uncaughtException) for [kOnExit](code) handler
458-
process.exitCode = 1;
459-
460457
let serialized;
461458
try {
462459
serialized = serializeError(error);

src/node.cc

+10-1
Original file line numberDiff line numberDiff line change
@@ -1952,7 +1952,16 @@ void FatalException(Isolate* isolate,
19521952
exit(7);
19531953
} else if (caught->IsFalse()) {
19541954
ReportException(env, error, message);
1955-
exit(1);
1955+
1956+
// fatal_exception_function call before may have set a new exit code ->
1957+
// read it again, otherwise use default for uncaughtException 1
1958+
Local<String> exit_code = env->exit_code_string();
1959+
Local<Value> code;
1960+
if (!process_object->Get(env->context(), exit_code).ToLocal(&code) ||
1961+
!code->IsInt32()) {
1962+
exit(1);
1963+
}
1964+
exit(code.As<v8::Int32>()->Value());
19561965
}
19571966
}
19581967
}

test/parallel/test-process-exit-code.js

+55-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ switch (process.argv[2]) {
3434
return child4();
3535
case 'child5':
3636
return child5();
37+
case 'child6':
38+
return child6();
39+
case 'child7':
40+
return child7();
41+
case 'child8':
42+
return child8();
43+
case 'child9':
44+
return child9();
3745
case undefined:
3846
return parent();
3947
default:
@@ -43,13 +51,15 @@ switch (process.argv[2]) {
4351
function child1() {
4452
process.exitCode = 42;
4553
process.on('exit', function(code) {
54+
assert.strictEqual(process.exitCode, 42);
4655
assert.strictEqual(code, 42);
4756
});
4857
}
4958

5059
function child2() {
5160
process.exitCode = 99;
5261
process.on('exit', function(code) {
62+
assert.strictEqual(process.exitCode, 42);
5363
assert.strictEqual(code, 42);
5464
});
5565
process.exit(42);
@@ -58,6 +68,7 @@ function child2() {
5868
function child3() {
5969
process.exitCode = 99;
6070
process.on('exit', function(code) {
71+
assert.strictEqual(process.exitCode, 0);
6172
assert.strictEqual(code, 0);
6273
});
6374
process.exit(0);
@@ -66,7 +77,7 @@ function child3() {
6677
function child4() {
6778
process.exitCode = 99;
6879
process.on('exit', function(code) {
69-
if (code !== 1) {
80+
if (code !== 1 || process.exitCode !== 1) {
7081
console.log('wrong code! expected 1 for uncaughtException');
7182
process.exit(99);
7283
}
@@ -77,11 +88,50 @@ function child4() {
7788
function child5() {
7889
process.exitCode = 95;
7990
process.on('exit', function(code) {
91+
assert.strictEqual(process.exitCode, 95);
8092
assert.strictEqual(code, 95);
8193
process.exitCode = 99;
8294
});
8395
}
8496

97+
function child6() {
98+
process.on('exit', function(code) {
99+
assert.strictEqual(process.exitCode, 0);
100+
assert.strictEqual(code, 0);
101+
});
102+
process.on('uncaughtException', () => {});
103+
throw new Error('ok');
104+
}
105+
106+
function child7() {
107+
process.on('exit', function(code) {
108+
assert.strictEqual(process.exitCode, 97);
109+
assert.strictEqual(code, 97);
110+
});
111+
process.on('uncaughtException', () => {
112+
process.exitCode = 97;
113+
});
114+
throw new Error('ok');
115+
}
116+
117+
function child8() {
118+
process.on('exit', function(code) {
119+
assert.strictEqual(process.exitCode, 1);
120+
assert.strictEqual(code, 1);
121+
process.exitCode = 98;
122+
});
123+
throw new Error('ok');
124+
}
125+
126+
function child9() {
127+
process.on('exit', function(code) {
128+
assert.strictEqual(process.exitCode, 1);
129+
assert.strictEqual(code, 1);
130+
process.exitCode = 0;
131+
});
132+
throw new Error('ok');
133+
}
134+
85135
function parent() {
86136
const { spawn } = require('child_process');
87137
const node = process.execPath;
@@ -102,4 +152,8 @@ function parent() {
102152
test('child3', 0);
103153
test('child4', 1);
104154
test('child5', 99);
155+
test('child6', 0);
156+
test('child7', 97);
157+
test('child8', 98);
158+
test('child9', 0);
105159
}

test/parallel/test-worker-exit-code.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ if (!process.env.HAS_STARTED_WORKER) {
3535
return child6();
3636
case 'child7':
3737
return child7();
38+
case 'child8':
39+
return child8();
40+
case 'child9':
41+
return child9();
3842
default:
3943
throw new Error('invalid');
4044
}
@@ -105,6 +109,24 @@ function child7() {
105109
throw new Error('ok');
106110
}
107111

112+
function child8() {
113+
process.on('exit', (code) => {
114+
assert.strictEqual(process.exitCode, 1);
115+
assert.strictEqual(code, 1);
116+
process.exitCode = 98;
117+
});
118+
throw new Error('ok');
119+
}
120+
121+
function child9() {
122+
process.on('exit', function(code) {
123+
assert.strictEqual(process.exitCode, 1);
124+
assert.strictEqual(code, 1);
125+
process.exitCode = 0;
126+
});
127+
throw new Error('ok');
128+
}
129+
108130
function parent() {
109131
const test = (arg, exit, error = null) => {
110132
const w = new Worker(__filename);
@@ -116,7 +138,9 @@ function parent() {
116138
}));
117139
if (error) {
118140
w.on('error', common.mustCall((err) => {
119-
assert(error.test(err));
141+
console.log(err);
142+
assert(error.test(err),
143+
`wrong error for ${arg}\nexpected:${error} but got:${err}`);
120144
}));
121145
}
122146
w.postMessage(arg);
@@ -129,4 +153,6 @@ function parent() {
129153
test('child5', 99);
130154
test('child6', 0);
131155
test('child7', 97);
156+
test('child8', 98, /^Error: ok$/);
157+
test('child9', 0, /^Error: ok$/);
132158
}

test/parallel/test-worker-uncaught-exception.js

+10
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,22 @@ if (!process.env.HAS_STARTED_WORKER) {
1010
const w = new Worker(__filename);
1111
w.on('message', common.mustNotCall());
1212
w.on('error', common.mustCall((err) => {
13+
console.log(err.message);
1314
assert(/^Error: foo$/.test(err));
1415
}));
1516
w.on('exit', common.mustCall((code) => {
1617
// uncaughtException is code 1
1718
assert.strictEqual(code, 1);
1819
}));
1920
} else {
21+
// cannot use common.mustCall as it cannot catch this
22+
let called = false;
23+
process.on('exit', (code) => {
24+
if (!called) {
25+
called = true;
26+
} else {
27+
assert.fail('Exit callback called twice in worker');
28+
}
29+
});
2030
throw new Error('foo');
2131
}

0 commit comments

Comments
 (0)