From c9a460391379af955eccee05b8ff1659fd5be4fe Mon Sep 17 00:00:00 2001 From: Gang Chen <13298548+MoonBall@users.noreply.github.com> Date: Fri, 14 Jan 2022 08:37:41 +0800 Subject: [PATCH] esm: make `process.exit()` default to exit code 0 Due to a bug in top-level await implementation, it used to default to exit code 13. PR-URL: https://github.com/nodejs/node/pull/41388 Fixes: https://github.com/nodejs/node/issues/40808 Reviewed-By: Antoine du Hamel Reviewed-By: Guy Bedford --- .../modules/esm/handle_process_exit.js | 12 ++++++++++++ lib/internal/modules/run_main.js | 14 +++++--------- lib/internal/process/per_thread.js | 6 ++++++ test/es-module/test-esm-tla-unfinished.mjs | 18 ++++++++++++++++++ test/fixtures/es-modules/tla/process-exit.mjs | 1 + .../unresolved-with-worker-process-exit.mjs | 8 ++++++++ test/parallel/test-bootstrap-modules.js | 1 + 7 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 lib/internal/modules/esm/handle_process_exit.js create mode 100644 test/fixtures/es-modules/tla/process-exit.mjs create mode 100644 test/fixtures/es-modules/tla/unresolved-with-worker-process-exit.mjs diff --git a/lib/internal/modules/esm/handle_process_exit.js b/lib/internal/modules/esm/handle_process_exit.js new file mode 100644 index 00000000000000..db830900bd3154 --- /dev/null +++ b/lib/internal/modules/esm/handle_process_exit.js @@ -0,0 +1,12 @@ +'use strict'; + +// Handle a Promise from running code that potentially does Top-Level Await. +// In that case, it makes sense to set the exit code to a specific non-zero +// value if the main code never finishes running. +function handleProcessExit() { + process.exitCode ??= 13; +} + +module.exports = { + handleProcessExit, +}; diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index 9a0263024144fb..924c4836bb2aa2 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -8,6 +8,9 @@ const CJSLoader = require('internal/modules/cjs/loader'); const { Module, toRealPath, readPackageScope } = CJSLoader; const { getOptionValue } = require('internal/options'); const path = require('path'); +const { + handleProcessExit, +} = require('internal/modules/esm/handle_process_exit'); function resolveMainPath(main) { // Note extension resolution for the main entry point can be deprecated in a @@ -53,18 +56,11 @@ function runMainESM(mainPath) { } async function handleMainPromise(promise) { - // Handle a Promise from running code that potentially does Top-Level Await. - // In that case, it makes sense to set the exit code to a specific non-zero - // value if the main code never finishes running. - function handler() { - if (process.exitCode === undefined) - process.exitCode = 13; - } - process.on('exit', handler); + process.on('exit', handleProcessExit); try { return await promise; } finally { - process.off('exit', handler); + process.off('exit', handleProcessExit); } } diff --git a/lib/internal/process/per_thread.js b/lib/internal/process/per_thread.js index a63217d9b3955a..709bcb7b137639 100644 --- a/lib/internal/process/per_thread.js +++ b/lib/internal/process/per_thread.js @@ -48,6 +48,10 @@ const { } = require('internal/validators'); const constants = internalBinding('constants').os.signals; +const { + handleProcessExit, +} = require('internal/modules/esm/handle_process_exit'); + const kInternal = Symbol('internal properties'); function assert(x, msg) { @@ -175,6 +179,8 @@ function wrapProcessMethods(binding) { memoryUsage.rss = rss; function exit(code) { + process.off('exit', handleProcessExit); + if (code || code === 0) process.exitCode = code; diff --git a/test/es-module/test-esm-tla-unfinished.mjs b/test/es-module/test-esm-tla-unfinished.mjs index 7d35c86285ac81..d7658c19e98e1c 100644 --- a/test/es-module/test-esm-tla-unfinished.mjs +++ b/test/es-module/test-esm-tla-unfinished.mjs @@ -80,3 +80,21 @@ import fixtures from '../common/fixtures.js'; assert.deepStrictEqual([status, stdout], [1, '']); assert.match(stderr, /Error: Xyz/); } + +{ + // Calling process.exit() in .mjs should return status 0 + const { status, stdout, stderr } = child_process.spawnSync( + process.execPath, + [fixtures.path('es-modules/tla/process-exit.mjs')], + { encoding: 'utf8' }); + assert.deepStrictEqual([status, stdout, stderr], [0, '', '']); +} + +{ + // Calling process.exit() in worker thread shouldn't influence main thread + const { status, stdout, stderr } = child_process.spawnSync( + process.execPath, + [fixtures.path('es-modules/tla/unresolved-with-worker-process-exit.mjs')], + { encoding: 'utf8' }); + assert.deepStrictEqual([status, stdout, stderr], [13, '', '']); +} diff --git a/test/fixtures/es-modules/tla/process-exit.mjs b/test/fixtures/es-modules/tla/process-exit.mjs new file mode 100644 index 00000000000000..78e86b018e7fbb --- /dev/null +++ b/test/fixtures/es-modules/tla/process-exit.mjs @@ -0,0 +1 @@ +process.exit(); diff --git a/test/fixtures/es-modules/tla/unresolved-with-worker-process-exit.mjs b/test/fixtures/es-modules/tla/unresolved-with-worker-process-exit.mjs new file mode 100644 index 00000000000000..8d65baa2b59cce --- /dev/null +++ b/test/fixtures/es-modules/tla/unresolved-with-worker-process-exit.mjs @@ -0,0 +1,8 @@ +import { Worker, isMainThread } from 'worker_threads'; + +if (isMainThread) { + new Worker(new URL(import.meta.url)); + await new Promise(() => {}); +} else { + process.exit(); +} diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 9a687eede28ca9..cd1a503837191e 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -79,6 +79,7 @@ const expectedModules = new Set([ 'NativeModule internal/modules/esm/resolve', 'NativeModule internal/modules/esm/initialize_import_meta', 'NativeModule internal/modules/esm/translators', + 'NativeModule internal/modules/esm/handle_process_exit', 'NativeModule internal/process/esm_loader', 'NativeModule internal/options', 'NativeModule internal/perf/event_loop_delay',