Skip to content

Commit 7a48cfd

Browse files
src: fix process exit listeners not receiving unsettled tla codes
fix listeners registered via `process.on('exit', ...` not receiving error code 13 when an unsettled top-level-await is encountered in the code
1 parent 0b125b0 commit 7a48cfd

File tree

8 files changed

+73
-9
lines changed

8 files changed

+73
-9
lines changed

doc/api/process.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,8 +1893,16 @@ A number which will be the process exit code, when the process either
18931893
exits gracefully, or is exited via [`process.exit()`][] without specifying
18941894
a code.
18951895
1896-
Specifying a code to [`process.exit(code)`][`process.exit()`] will override any
1897-
previous setting of `process.exitCode`.
1896+
The value of `process.exitCode` can be updated by either setting the value
1897+
directly (e.g. `process.exitCode = 9`), or by specifying a code to
1898+
[`process.exit(code)`][`process.exit()`] (the latter takes
1899+
precedence and will override any previous setting of `process.exitCode`).
1900+
1901+
The value can also be set implicitly by Node.js when unrecoverable errors occur (e.g.
1902+
such as the encountering of an unsettled top-level await). However explicit
1903+
manipulations of the exit code always take precedence over implicit ones (meaning that
1904+
for example updating `process.exitCode` to `9` guarantees that the program's exit code
1905+
will be `9` regardless on other errors Node.js might encounter).
18981906
18991907
## `process.features.cached_builtins`
19001908

lib/internal/bootstrap/node.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,15 @@ process.domain = null;
108108
ObjectDefineProperty(process, 'exitCode', {
109109
__proto__: null,
110110
get() {
111-
return exitCode;
111+
if (exitCode !== undefined) {
112+
return exitCode;
113+
}
114+
115+
if (fields[kHasExitCode] !== 0) {
116+
return fields[kExitCode];
117+
}
118+
119+
return undefined;
112120
},
113121
set(code) {
114122
if (code !== null && code !== undefined) {

src/api/hooks.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,14 @@ Maybe<ExitCode> EmitProcessExitInternal(Environment* env) {
7575
// the exit code wasn't already set, so let's check for unsettled tlas
7676
if (exit_code == ExitCode::kNoFailure) {
7777
auto unsettled_tla = env->CheckUnsettledTopLevelAwait();
78-
79-
exit_code = !unsettled_tla.FromJust()
80-
? ExitCode::kUnsettledTopLevelAwait
81-
: ExitCode::kNoFailure;
78+
if (!unsettled_tla.FromJust()) {
79+
exit_code = ExitCode::kUnsettledTopLevelAwait;
80+
env->set_exit_code(exit_code);
81+
}
8282
}
8383

84-
Local<Integer> exit_code_int = Integer::New(
85-
isolate, static_cast<int32_t>(exit_code));
84+
Local<Integer> exit_code_int =
85+
Integer::New(isolate, static_cast<int32_t>(exit_code));
8686

8787
if (ProcessEmit(env, "exit", exit_code_int).IsEmpty()) {
8888
return Nothing<ExitCode>();

src/env-inl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,11 @@ inline ExitCode Environment::exit_code(const ExitCode default_code) const {
341341
: static_cast<ExitCode>(exit_info_[kExitCode]);
342342
}
343343

344+
inline void Environment::set_exit_code(const ExitCode code) {
345+
exit_info_[kExitCode] = static_cast<int>(code);
346+
exit_info_[kHasExitCode] = 1;
347+
}
348+
344349
inline AliasedInt32Array& Environment::exit_info() {
345350
return exit_info_;
346351
}

src/env.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,8 @@ class Environment final : public MemoryRetainer {
739739
bool exiting() const;
740740
inline ExitCode exit_code(const ExitCode default_code) const;
741741

742+
inline void set_exit_code(const ExitCode code);
743+
742744
// This stores whether the --abort-on-uncaught-exception flag was passed
743745
// to Node.
744746
inline bool abort_on_uncaught_exception() const;

test/es-module/test-esm-tla-unfinished.mjs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,31 @@ describe('ESM: unsettled and rejected promises', { concurrency: !process.env.TES
174174
assert.strictEqual(stdout, '');
175175
assert.strictEqual(code, 13);
176176
});
177+
178+
describe('with exit listener', () => {
179+
it('the process exit event should provide the correct code', async () => {
180+
const { code, stdout } = await spawnPromisified(execPath, [
181+
fixtures.path('es-modules/tla/unresolved-with-listener.mjs'),
182+
]);
183+
184+
assert.strictEqual(stdout,
185+
'the exit listener received code: 13\n' +
186+
'process.exitCode inside the exist listener: 13\n'
187+
);
188+
assert.strictEqual(code, 13);
189+
});
190+
191+
it('should exit for an unsettled TLA promise and respect explicit exit code in process exit event', async () => {
192+
const { code, stdout } = await spawnPromisified(execPath, [
193+
'--no-warnings',
194+
fixtures.path('es-modules/tla/unresolved-withexitcode-and-listener.mjs'),
195+
]);
196+
197+
assert.strictEqual(stdout,
198+
'the exit listener received code: 42\n' +
199+
'process.exitCode inside the exist listener: 42\n'
200+
);
201+
assert.strictEqual(code, 42);
202+
});
203+
});
177204
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
process.on('exit', (exitCode) => {
2+
console.log(`the exit listener received code: ${exitCode}`);
3+
console.log(`process.exitCode inside the exist listener: ${process.exitCode}`);
4+
})
5+
6+
await new Promise(() => {});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
process.on('exit', (exitCode) => {
2+
console.log(`the exit listener received code: ${exitCode}`);
3+
console.log(`process.exitCode inside the exist listener: ${process.exitCode}`);
4+
});
5+
6+
process.exitCode = 42;
7+
8+
await new Promise(() => {});

0 commit comments

Comments
 (0)