From 993a1dd41ae5f96bdb24b09757e24c2ac24126d0 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sun, 28 Nov 2021 00:45:38 +0100 Subject: [PATCH] feat(runtime): add op_set_exit_code (#12911) Set the exit code to use if none is provided to Deno.exit(), or when Deno exits naturally. Needed for process.exitCode Node compat. Paves the way for #12888. --- cli/main.rs | 4 ++++ cli/tests/integration/run_tests.rs | 18 ++++++++++++++++++ cli/tests/testdata/empty.out | 0 cli/tests/testdata/set_exit_code_0.ts | 2 ++ cli/tests/testdata/set_exit_code_1.ts | 2 ++ cli/tests/testdata/set_exit_code_2.ts | 2 ++ runtime/js/30_os.js | 11 +++++++++-- runtime/lib.rs | 11 +++++++++++ runtime/ops/os.rs | 10 +++++++++- 9 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 cli/tests/testdata/empty.out create mode 100644 cli/tests/testdata/set_exit_code_0.ts create mode 100644 cli/tests/testdata/set_exit_code_1.ts create mode 100644 cli/tests/testdata/set_exit_code_2.ts diff --git a/cli/main.rs b/cli/main.rs index e2ef26cd3a..3d8a80cee2 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -93,6 +93,7 @@ use std::iter::once; use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; +use std::sync::atomic::Ordering::Relaxed; use std::sync::Arc; fn create_web_worker_callback(ps: ProcState) -> Arc { @@ -1475,4 +1476,7 @@ pub fn main() { logger::init(flags.log_level); unwrap_or_exit(run_basic(get_subcommand(flags))); + + let code = deno_runtime::EXIT_CODE.load(Relaxed); + std::process::exit(code); } diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index d6a5a4c252..47041e499b 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -859,6 +859,24 @@ itest!(exit_error42 { output: "exit_error42.ts.out", }); +itest!(set_exit_code_0 { + args: "run --no-check --unstable set_exit_code_0.ts", + output: "empty.out", + exit_code: 0, +}); + +itest!(set_exit_code_1 { + args: "run --no-check --unstable set_exit_code_1.ts", + output: "empty.out", + exit_code: 42, +}); + +itest!(set_exit_code_2 { + args: "run --no-check --unstable set_exit_code_2.ts", + output: "empty.out", + exit_code: 42, +}); + itest!(heapstats { args: "run --quiet --unstable --v8-flags=--expose-gc heapstats.js", output: "heapstats.js.out", diff --git a/cli/tests/testdata/empty.out b/cli/tests/testdata/empty.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cli/tests/testdata/set_exit_code_0.ts b/cli/tests/testdata/set_exit_code_0.ts new file mode 100644 index 0000000000..7eb14fa3c2 --- /dev/null +++ b/cli/tests/testdata/set_exit_code_0.ts @@ -0,0 +1,2 @@ +Deno.core.opSync("op_set_exit_code", 42); +Deno.exit(0); // Takes precedence. diff --git a/cli/tests/testdata/set_exit_code_1.ts b/cli/tests/testdata/set_exit_code_1.ts new file mode 100644 index 0000000000..96ec9889cc --- /dev/null +++ b/cli/tests/testdata/set_exit_code_1.ts @@ -0,0 +1,2 @@ +Deno.core.opSync("op_set_exit_code", 42); +Deno.exit(); diff --git a/cli/tests/testdata/set_exit_code_2.ts b/cli/tests/testdata/set_exit_code_2.ts new file mode 100644 index 0000000000..a1f2b5d3c8 --- /dev/null +++ b/cli/tests/testdata/set_exit_code_2.ts @@ -0,0 +1,2 @@ +Deno.core.opSync("op_set_exit_code", 42); +// Exits naturally. diff --git a/runtime/js/30_os.js b/runtime/js/30_os.js index 15df6f5549..f6bada6a52 100644 --- a/runtime/js/30_os.js +++ b/runtime/js/30_os.js @@ -31,7 +31,14 @@ exitHandler = fn; } - function exit(code = 0) { + function exit(code) { + // Set exit code first so unload event listeners can override it. + if (typeof code === "number") { + core.opSync("op_set_exit_code", code); + } else { + code = 0; + } + // Dispatches `unload` only when it's not dispatched yet. if (!window[SymbolFor("isUnloadDispatched")]) { // Invokes the `unload` hooks before exiting @@ -44,7 +51,7 @@ return; } - core.opSync("op_exit", code); + core.opSync("op_exit"); throw new Error("Code not reachable"); } diff --git a/runtime/lib.rs b/runtime/lib.rs index fb6159dd9e..ca250ce20c 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -1,5 +1,7 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use std::sync::atomic::AtomicI32; + pub use deno_broadcast_channel; pub use deno_console; pub use deno_crypto; @@ -29,3 +31,12 @@ pub mod worker; mod worker_bootstrap; pub use worker_bootstrap::BootstrapOptions; + +// The global may not be very elegant but: +// +// 1. op_exit() calls std::process::exit() so there is not much point storing +// the exit code in runtime state +// +// 2. storing it in runtime state makes retrieving it again in cli/main.rs +// unduly complicated +pub static EXIT_CODE: AtomicI32 = AtomicI32::new(0); diff --git a/runtime/ops/os.rs b/runtime/ops/os.rs index 0a6269ac5a..8e96de75dc 100644 --- a/runtime/ops/os.rs +++ b/runtime/ops/os.rs @@ -10,6 +10,7 @@ use deno_core::OpState; use serde::Serialize; use std::collections::HashMap; use std::env; +use std::sync::atomic::Ordering::Relaxed; pub fn init() -> Extension { Extension::builder() @@ -23,6 +24,7 @@ pub fn init() -> Extension { ("op_hostname", op_sync(op_hostname)), ("op_loadavg", op_sync(op_loadavg)), ("op_os_release", op_sync(op_os_release)), + ("op_set_exit_code", op_sync(op_set_exit_code)), ("op_system_memory_info", op_sync(op_system_memory_info)), ]) .build() @@ -95,7 +97,13 @@ fn op_delete_env( Ok(()) } -fn op_exit(_state: &mut OpState, code: i32, _: ()) -> Result<(), AnyError> { +fn op_set_exit_code(_: &mut OpState, code: i32, _: ()) -> Result<(), AnyError> { + crate::EXIT_CODE.store(code, Relaxed); + Ok(()) +} + +fn op_exit(_: &mut OpState, _: (), _: ()) -> Result<(), AnyError> { + let code = crate::EXIT_CODE.load(Relaxed); std::process::exit(code) }