Skip to content

Commit

Permalink
src: fix process exit listeners not receiving unsettled tla codes
Browse files Browse the repository at this point in the history
fix listeners registered via `process.on('exit', ...` not receiving
error code 13 when an unsettled top-level-await is encountered in
the code
  • Loading branch information
dario-piotrowicz committed Feb 2, 2025
1 parent 2bd5694 commit eef4c1f
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 25 deletions.
15 changes: 1 addition & 14 deletions src/api/embed_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,7 @@ Maybe<ExitCode> SpinEventLoopInternal(Environment* env) {

env->PrintInfoForSnapshotIfDebug();
env->ForEachRealm([](Realm* realm) { realm->VerifyNoStrongBaseObjects(); });
Maybe<ExitCode> exit_code = EmitProcessExitInternal(env);
if (exit_code.FromMaybe(ExitCode::kGenericUserError) !=
ExitCode::kNoFailure) {
return exit_code;
}

auto unsettled_tla = env->CheckUnsettledTopLevelAwait();
if (unsettled_tla.IsNothing()) {
return Nothing<ExitCode>();
}
if (!unsettled_tla.FromJust()) {
return Just(ExitCode::kUnsettledTopLevelAwait);
}
return Just(ExitCode::kNoFailure);
return EmitProcessExitInternal(env);
}

struct CommonEnvironmentSetup::Impl {
Expand Down
19 changes: 15 additions & 4 deletions src/api/hooks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,25 @@ Maybe<ExitCode> EmitProcessExitInternal(Environment* env) {
return Nothing<ExitCode>();
}

Local<Integer> exit_code = Integer::New(
isolate, static_cast<int32_t>(env->exit_code(ExitCode::kNoFailure)));
ExitCode exit_code = env->exit_code(ExitCode::kNoFailure);

// the exit code wasn't already set, so let's check for unsettled tlas
if (exit_code == ExitCode::kNoFailure) {
auto unsettled_tla = env->CheckUnsettledTopLevelAwait();
if (!unsettled_tla.FromJust()) {
exit_code = ExitCode::kUnsettledTopLevelAwait;
}
}

if (ProcessEmit(env, "exit", exit_code).IsEmpty()) {
Local<Integer> exit_code_int = Integer::New(
isolate, static_cast<int32_t>(exit_code));

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

// Reload exit code, it may be changed by `emit('exit')`
return Just(env->exit_code(ExitCode::kNoFailure));
return Just(env->exit_code(exit_code));
}

Maybe<int> EmitProcessExit(Environment* env) {
Expand Down
30 changes: 23 additions & 7 deletions test/es-module/test-esm-tla-unfinished.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,24 +72,31 @@ describe('ESM: unsettled and rejected promises', { concurrency: !process.env.TES
});

it('should exit for an unsettled TLA promise with warning', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
const { code, stderr } = await spawnPromisified(execPath, [
fixtures.path('es-modules/tla/unresolved.mjs'),
]);

assert.match(stderr, /Warning: Detected unsettled top-level await at.+unresolved\.mjs:1/);
assert.match(stderr, /Warning: Detected unsettled top-level await at.+unresolved\.mjs:5/);
assert.match(stderr, /await new Promise/);
assert.strictEqual(stdout, '');
assert.strictEqual(code, 13);
});

it('the process exit event should provide the correct code for an unsettled TLA promise', async () => {
const { code, stdout } = await spawnPromisified(execPath, [
fixtures.path('es-modules/tla/unresolved.mjs'),
]);

assert.strictEqual(stdout, 'the exit listener received code: 13\n');
assert.strictEqual(code, 13);
});

it('should exit for an unsettled TLA promise without warning', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
const { code, stderr } = await spawnPromisified(execPath, [
'--no-warnings',
fixtures.path('es-modules/tla/unresolved.mjs'),
]);

assert.strictEqual(stderr, '');
assert.strictEqual(stdout, '');
assert.strictEqual(code, 13);
});

Expand All @@ -105,13 +112,22 @@ describe('ESM: unsettled and rejected promises', { concurrency: !process.env.TES
});

it('should exit for an unsettled TLA promise and respect explicit exit code via stdin', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
const { code, stderr } = await spawnPromisified(execPath, [
'--no-warnings',
fixtures.path('es-modules/tla/unresolved-withexitcode.mjs'),
]);

assert.strictEqual(stderr, '');
assert.strictEqual(stdout, '');
assert.strictEqual(code, 42);
});

it('should exit for an unsettled TLA promise and respect explicit exit code in process exit event', async () => {
const { code, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
fixtures.path('es-modules/tla/unresolved-withexitcode.mjs'),
]);

assert.strictEqual(stdout, 'the exit listener received code: 42\n');
assert.strictEqual(code, 42);
});

Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/es-modules/tla/unresolved-withexitcode.mjs
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
process.on('exit', (exitCode) => {
console.log(`the exit listener received code: ${exitCode}`);
});

process.exitCode = 42;

await new Promise(() => {});
4 changes: 4 additions & 0 deletions test/fixtures/es-modules/tla/unresolved.mjs
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
process.on('exit', (exitCode) => {
console.log(`the exit listener received code: ${exitCode}`);
})

await new Promise(() => {});

0 comments on commit eef4c1f

Please sign in to comment.