refactor(cli): defer process termination (#11440)
This commit is contained in:
1 parent
8bd923ec85
commit
259b3584ff
28 files changed
+272
-86
No files matched your search
@@ -7,4 +7,5 @@
|
||||
disallowed-methods = [
|
||||
{ path = "std::env::vars", reason = "panics on non-UTF-8 environment variables (jdx/mise#5370); use crate::env::vars_safe(), or std::env::vars_os() when values must not be dropped" },
|
||||
{ path = "std::env::args", reason = "panics on non-UTF-8 arguments (jdx/mise#10056); use crate::env::args_safe(), or std::env::args_os()" },
|
||||
{ path = "std::process::exit", reason = "skips destructors; propagate the status to the binary's outermost boundary and allow only that boundary" },
|
||||
]
|
||||
@@ -5,7 +5,7 @@ extern crate log;
|
||||
#[cfg(feature = "cli")]
|
||||
mod cli;
|
||||
|
||||
#[allow(clippy::needless_return)]
|
||||
#[allow(clippy::disallowed_methods, clippy::needless_return)]
|
||||
#[cfg(feature = "cli")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
assert_exit_code() {
|
||||
local expected="$1"
|
||||
shift
|
||||
|
||||
local output status=0
|
||||
output="$("$@" 2>&1)" || status=$?
|
||||
if [[ $status -ne $expected ]]; then
|
||||
fail "[$*] expected exit code $expected, got $status: $output"
|
||||
fi
|
||||
assert_not_contains_text "$output" "exit with status"
|
||||
}
|
||||
|
||||
# Clap help and errors should keep their conventional statuses without exposing
|
||||
# the internal exit request used to unwind the command.
|
||||
assert_exit_code 0 mise --help
|
||||
assert_exit_code 2 mise --definitely-not-a-real-option
|
||||
|
||||
# Wrapper commands must preserve the exact child status.
|
||||
assert_exit_code 42 mise exec -- bash -c "exit 42"
|
||||
|
||||
# Task failures also propagate the task's status after result reporting.
|
||||
cat <<'EOF' >mise.toml
|
||||
[tasks.fail]
|
||||
run = "exit 23"
|
||||
|
||||
[tasks.wait]
|
||||
run = """
|
||||
touch "$READY_FILE"
|
||||
trap '' INT TERM
|
||||
while true; do sleep 1; done
|
||||
"""
|
||||
EOF
|
||||
assert_exit_code 23 mise run fail
|
||||
|
||||
# Task execution handles the first Ctrl-C gracefully and force-exits on the
|
||||
# second. The forced path should still unwind command-scoped destructors.
|
||||
ready_file="$TMPDIR/exit-status-child-ready"
|
||||
output_file="$TMPDIR/exit-status-child-output"
|
||||
# Isolate the process tree so the failure fallback can reap a child that
|
||||
# deliberately ignores INT and TERM.
|
||||
READY_FILE="$ready_file" setsid mise run wait >"$output_file" 2>&1 &
|
||||
mise_pid=$!
|
||||
wait_for_file "$ready_file" "task child" 30 "$mise_pid"
|
||||
kill -INT "$mise_pid"
|
||||
sleep 0.2
|
||||
kill -INT "$mise_pid"
|
||||
|
||||
for _ in {1..50}; do
|
||||
if ! kill -0 "$mise_pid" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
if kill -0 "$mise_pid" 2>/dev/null; then
|
||||
kill -KILL -- "-$mise_pid" 2>/dev/null || true
|
||||
wait "$mise_pid" 2>/dev/null || true
|
||||
fail "mise run did not exit after repeated Ctrl-C"
|
||||
fi
|
||||
|
||||
status=0
|
||||
wait "$mise_pid" || status=$?
|
||||
output="$(cat "$output_file")"
|
||||
if [[ $status -ne 1 ]]; then
|
||||
fail "mise run expected exit code 1 after repeated Ctrl-C, got $status: $output"
|
||||
fi
|
||||
assert_not_contains_text "$output" "exit with status"
|
||||
@@ -1214,7 +1214,7 @@ impl BootstrapStatus {
|
||||
table.print()?;
|
||||
}
|
||||
if self.missing && report.any_missing {
|
||||
crate::exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1948,7 +1948,7 @@ impl BootstrapPluginsStatus {
|
||||
}
|
||||
table.print()?;
|
||||
if self.missing && any_missing {
|
||||
crate::exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -2087,7 +2087,7 @@ impl BootstrapReposStatus {
|
||||
table.print()?;
|
||||
}
|
||||
if self.missing && any_missing {
|
||||
crate::exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -2216,7 +2216,7 @@ impl BootstrapLaunchdStatus {
|
||||
table.print()?;
|
||||
}
|
||||
if self.missing && any_missing {
|
||||
crate::exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -2322,7 +2322,7 @@ impl BootstrapSystemdStatus {
|
||||
table.print()?;
|
||||
}
|
||||
if self.missing && any_missing {
|
||||
crate::exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -2419,7 +2419,7 @@ impl BootstrapMacosDefaultsStatus {
|
||||
table.print()?;
|
||||
}
|
||||
if self.missing && any_missing {
|
||||
crate::exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -2498,7 +2498,7 @@ impl BootstrapShellStatus {
|
||||
table.print()?;
|
||||
}
|
||||
if self.missing && any_missing {
|
||||
crate::exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -2596,7 +2596,7 @@ impl BootstrapUserStatus {
|
||||
table.print()?;
|
||||
}
|
||||
if self.missing && any_missing {
|
||||
crate::exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mod path;
|
||||
|
||||
use crate::{exit, plugins::PluginEnum};
|
||||
use crate::plugins::PluginEnum;
|
||||
use std::collections::HashSet;
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
@@ -252,7 +252,7 @@ impl Doctor {
|
||||
println!("{out}");
|
||||
|
||||
if !self.errors.is_empty() {
|
||||
exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -343,7 +343,7 @@ impl Doctor {
|
||||
let num = style::nred(format!("{}.", i + 1));
|
||||
miseprintln!("{num} {}\n", info::indent_by(check, " ").trim_start());
|
||||
}
|
||||
exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -153,7 +153,7 @@ impl DotfilesStatus {
|
||||
}
|
||||
}
|
||||
if self.missing && any_missing {
|
||||
crate::exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+1
-1
@@ -263,7 +263,7 @@ impl Edit {
|
||||
info!("cancelled");
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {
|
||||
std::process::exit(130);
|
||||
return Err(crate::request_exit(130));
|
||||
}
|
||||
Err(e) => return Err(eyre!(e)),
|
||||
}
|
||||
|
||||
+2
-4
@@ -482,7 +482,7 @@ where
|
||||
let body = last.to_string_lossy();
|
||||
if let Some(mut c) = crate::path::cmd_verbatim_command(prog, &flags, &body) {
|
||||
match c.status()?.code() {
|
||||
Some(code) => std::process::exit(code),
|
||||
Some(code) => return Err(crate::request_exit(code)),
|
||||
None => return Err(eyre!("command failed: terminated by signal")),
|
||||
}
|
||||
}
|
||||
@@ -492,9 +492,7 @@ where
|
||||
let cmd = cmd::cmd(program, args);
|
||||
let res = cmd.unchecked().run()?;
|
||||
match res.status.code() {
|
||||
Some(code) => {
|
||||
std::process::exit(code);
|
||||
}
|
||||
Some(code) => Err(crate::request_exit(code)),
|
||||
None => Err(eyre!("command failed: terminated by signal")),
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ use crate::hook_env::{PREV_SESSION, WatchFilePattern};
|
||||
use crate::shell::{ShellType, get_shell};
|
||||
use crate::toolset::{ResolveOptions, Toolset, ToolsetBuilder};
|
||||
use crate::ui::style;
|
||||
use crate::{env, exit, hook_env, hooks, watch_files};
|
||||
use crate::{env, hook_env, hooks, watch_files};
|
||||
use console::truncate_str;
|
||||
use eyre::Result;
|
||||
use indexmap::IndexSet;
|
||||
@@ -78,7 +78,7 @@ impl HookEnv {
|
||||
display_path(&config_path)
|
||||
);
|
||||
}
|
||||
exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
};
|
||||
// Shell activation must stay fast and non-networked; missing tools are
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::exit;
|
||||
use crate::request_exit;
|
||||
|
||||
use eyre::Result;
|
||||
|
||||
@@ -33,6 +33,6 @@ impl HookNotFound {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
exit(127);
|
||||
Err(request_exit(127))
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -318,7 +318,7 @@ impl Install {
|
||||
if self.dry_run_code {
|
||||
let has_work = versions.iter().any(|tv| tv.install_satisfied != Some(true));
|
||||
if has_work {
|
||||
exit::exit(1);
|
||||
return Err(exit::request(1));
|
||||
}
|
||||
}
|
||||
return install_error;
|
||||
@@ -540,7 +540,7 @@ impl Install {
|
||||
};
|
||||
if self.is_dry_run() {
|
||||
if self.dry_run_code && has_missing {
|
||||
exit::exit(1);
|
||||
return Err(exit::request(1));
|
||||
}
|
||||
return install_error;
|
||||
}
|
||||
|
||||
+59
-14
@@ -1,12 +1,11 @@
|
||||
use crate::config::{Config, Settings};
|
||||
use crate::exit::exit;
|
||||
use crate::task::TaskOutput;
|
||||
use crate::ui::{self, ctrlc};
|
||||
use crate::{Result, backend};
|
||||
use crate::{Result, backend, request_exit};
|
||||
use crate::{cli::args::ToolArg, path::PathExt};
|
||||
use crate::{hook_env as hook_env_module, logger, migrate, shims};
|
||||
use clap::{ArgAction, CommandFactory, Subcommand};
|
||||
use eyre::bail;
|
||||
use eyre::{Report, bail};
|
||||
use std::path::PathBuf;
|
||||
|
||||
mod activate;
|
||||
@@ -676,6 +675,10 @@ fn preprocess_args_for_naked_run(cmd: &clap::Command, args: &[String]) -> Vec<St
|
||||
|
||||
impl Cli {
|
||||
pub async fn run(args: &Vec<String>) -> Result<()> {
|
||||
run_with_exit_signal(Self::run_inner(args), ctrlc::exit_signal()).await
|
||||
}
|
||||
|
||||
async fn run_inner(args: &Vec<String>) -> Result<()> {
|
||||
crate::env::ARGS.write().unwrap().clone_from(args);
|
||||
// Load .miserc.toml early, before MISE_ENV and other early settings are accessed.
|
||||
// This allows setting MISE_ENV in a config file instead of only via env vars.
|
||||
@@ -693,7 +696,6 @@ impl Cli {
|
||||
measure!("logger", { logger::init() });
|
||||
check_working_directory();
|
||||
measure!("handle_shim", { shims::handle_shim().await })?;
|
||||
ctrlc::init();
|
||||
let print_version = version::print_version_if_requested(args)?;
|
||||
// Clap's tool argument parsers consult installed plugin/tool metadata while
|
||||
// resolving registry options. Initialize that filesystem-only state before
|
||||
@@ -713,12 +715,12 @@ impl Cli {
|
||||
// Reuse the already-built clap command rather than letting
|
||||
// `Cli::parse_from` construct the whole tree a second time.
|
||||
let cli = measure!("get_matches_from", {
|
||||
let matches = cmd.get_matches_from(processed_args.iter());
|
||||
match <Cli as clap::FromArgMatches>::from_arg_matches(&matches) {
|
||||
Ok(cli) => cli,
|
||||
Err(err) => err.format(&mut Cli::command()).exit(),
|
||||
}
|
||||
});
|
||||
let matches = cmd
|
||||
.try_get_matches_from(processed_args.iter())
|
||||
.map_err(clap_error)?;
|
||||
<Cli as clap::FromArgMatches>::from_arg_matches(&matches)
|
||||
.map_err(|err| clap_error(err.format(&mut Cli::command())))
|
||||
})?;
|
||||
// Validate --cd path BEFORE Settings processes it and changes the directory
|
||||
validate_cd_path(&cli.cd)?;
|
||||
measure!("add_cli_matches", { Settings::add_cli_matches(&cli) });
|
||||
@@ -738,7 +740,7 @@ impl Cli {
|
||||
trace!("MISE_BIN: {}", crate::env::MISE_BIN.display_user());
|
||||
if print_version {
|
||||
version::show_latest().await;
|
||||
exit(0);
|
||||
return Err(request_exit(0));
|
||||
}
|
||||
let cmd = cli.get_command().await?;
|
||||
measure!("run {cmd}", { cmd.run().await })
|
||||
@@ -752,7 +754,7 @@ impl Cli {
|
||||
// Handle special case: "help", "-h", or "--help" as task should print help
|
||||
if task == "help" || task == "-h" || task == "--help" {
|
||||
Cli::command().print_help()?;
|
||||
exit(0);
|
||||
return Err(request_exit(0));
|
||||
}
|
||||
|
||||
let config = Config::get().await?;
|
||||
@@ -818,15 +820,33 @@ impl Cli {
|
||||
.chain(self.task_args_last)
|
||||
.collect(),
|
||||
)?;
|
||||
exit(0);
|
||||
return Err(request_exit(0));
|
||||
}
|
||||
}
|
||||
Cli::command().print_help()?;
|
||||
exit(1)
|
||||
Err(request_exit(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_with_exit_signal<T>(
|
||||
command: impl std::future::Future<Output = Result<T>>,
|
||||
exit_signal: impl std::future::Future<Output = i32>,
|
||||
) -> Result<T> {
|
||||
tokio::select! {
|
||||
result = command => result,
|
||||
code = exit_signal => Err(request_exit(code)),
|
||||
}
|
||||
}
|
||||
|
||||
fn clap_error(err: clap::Error) -> Report {
|
||||
let code = err.exit_code();
|
||||
match err.print() {
|
||||
Ok(()) => request_exit(code),
|
||||
Err(err) => err.into(),
|
||||
}
|
||||
}
|
||||
|
||||
const LONG_ABOUT: &str = "mise prepares your development environment before each command runs. https://github.com/jdx/mise";
|
||||
|
||||
const LONG_TASK_ABOUT: &str = r#"Task to run.
|
||||
@@ -921,6 +941,31 @@ mod tests {
|
||||
}
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn exit_signal_drops_command_future_before_returning() {
|
||||
struct DropGuard(std::sync::Arc<std::sync::atomic::AtomicBool>);
|
||||
|
||||
impl Drop for DropGuard {
|
||||
fn drop(&mut self) {
|
||||
self.0.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
let dropped = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||
let guard = DropGuard(dropped.clone());
|
||||
let command = async move {
|
||||
let _guard = guard;
|
||||
std::future::pending::<Result<()>>().await
|
||||
};
|
||||
|
||||
let err = run_with_exit_signal(command, std::future::ready(42))
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(crate::exit::requested_exit_code(&err), Some(42));
|
||||
assert!(dropped.load(std::sync::atomic::Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subcommands_are_sorted() {
|
||||
let cmd = Cli::command();
|
||||
|
||||
+1
-1
@@ -210,7 +210,7 @@ impl Run {
|
||||
let status = run_result?;
|
||||
if let Some(code) = status.code() {
|
||||
if code != 0 {
|
||||
std::process::exit(code);
|
||||
return Err(crate::request_exit(code));
|
||||
}
|
||||
} else if !status.success() {
|
||||
bail!("{engine_bin} exited abnormally: {status:?}");
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ impl Prune {
|
||||
let has_work = !to_delete.is_empty();
|
||||
delete(&config, self.is_dry_run(), to_delete).await?;
|
||||
if self.dry_run_code && has_work {
|
||||
exit::exit(1);
|
||||
return Err(exit::request(1));
|
||||
}
|
||||
if self.is_dry_run() {
|
||||
return Ok(());
|
||||
|
||||
+1
-1
@@ -579,7 +579,7 @@ impl Run {
|
||||
this.continue_on_error,
|
||||
this.timings(),
|
||||
);
|
||||
results_display.display_results(num_tasks, timer);
|
||||
results_display.display_results(num_tasks, timer)?;
|
||||
time!("parallelize_tasks done");
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -108,7 +108,7 @@ impl SystemStatus {
|
||||
}
|
||||
}
|
||||
if self.missing && any_missing {
|
||||
crate::exit(1);
|
||||
return Err(crate::request_exit(1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ impl Uninstall {
|
||||
|
||||
if self.is_dry_run() {
|
||||
if self.dry_run_code && has_work {
|
||||
exit::exit(1);
|
||||
return Err(exit::request(1));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
+1
-1
@@ -280,7 +280,7 @@ impl Upgrade {
|
||||
}
|
||||
}
|
||||
if self.dry_run_code {
|
||||
exit::exit(1);
|
||||
return Err(exit::request(1));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
+1
-1
@@ -326,7 +326,7 @@ impl Use {
|
||||
);
|
||||
}
|
||||
if self.dry_run_code {
|
||||
exit::exit(1);
|
||||
return Err(exit::request(1));
|
||||
}
|
||||
}
|
||||
} else if !quiet {
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ use crate::cmd;
|
||||
use crate::config::Config;
|
||||
use crate::dirs;
|
||||
use crate::env;
|
||||
use crate::exit::exit;
|
||||
use crate::request_exit;
|
||||
use crate::task::Deps;
|
||||
use crate::task::task_source_checker::task_cwd;
|
||||
use crate::toolset::ToolsetBuilder;
|
||||
@@ -74,7 +74,7 @@ impl Watch {
|
||||
eprintln!("{}: {}", style("Error").red().bold(), err);
|
||||
eprintln!("{}: Install watchexec with:", style("Hint").bold());
|
||||
eprintln!(" mise use -g watchexec@latest");
|
||||
exit(1);
|
||||
return Err(request_exit(1));
|
||||
}
|
||||
}
|
||||
let args = once(self.task)
|
||||
|
||||
+41
-1
@@ -1,14 +1,54 @@
|
||||
use crate::cmd::CmdLineRunner;
|
||||
use eyre::Report;
|
||||
#[cfg(unix)]
|
||||
use nix::sys::signal::SIGTERM;
|
||||
use std::error::Error;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
pub fn exit(code: i32) -> ! {
|
||||
#[derive(Debug)]
|
||||
struct ExitRequest {
|
||||
code: i32,
|
||||
}
|
||||
|
||||
impl Display for ExitRequest {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "exit with status {}", self.code)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ExitRequest {}
|
||||
|
||||
/// Request a process status without terminating before callers can unwind.
|
||||
pub fn request(code: i32) -> Report {
|
||||
Report::new(ExitRequest { code })
|
||||
}
|
||||
|
||||
pub fn requested_exit_code(err: &Report) -> Option<i32> {
|
||||
err.downcast_ref::<ExitRequest>().map(|exit| exit.code)
|
||||
}
|
||||
|
||||
pub fn kill_all() {
|
||||
#[cfg(unix)]
|
||||
CmdLineRunner::kill_all(SIGTERM);
|
||||
|
||||
#[cfg(windows)]
|
||||
CmdLineRunner::kill_all();
|
||||
}
|
||||
|
||||
/// Terminate only after command scopes and their destructors have unwound.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
pub fn terminate(code: i32) -> ! {
|
||||
debug!("exiting with code: {code}");
|
||||
std::process::exit(code)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn finds_requested_exit_code_through_context() {
|
||||
let err = request(42).wrap_err("context");
|
||||
assert_eq!(requested_exit_code(&err), Some(42));
|
||||
}
|
||||
}
|
||||
+42
-8
@@ -6,6 +6,7 @@
|
||||
use std::{
|
||||
panic,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::cli::Cli;
|
||||
@@ -100,11 +101,11 @@ mod versions_host;
|
||||
mod watch_files;
|
||||
mod wildcard;
|
||||
|
||||
pub(crate) use crate::exit::exit;
|
||||
pub(crate) use crate::exit::request as request_exit;
|
||||
pub(crate) use crate::result::Result;
|
||||
use crate::ui::multi_progress_report::MultiProgressReport;
|
||||
|
||||
fn main() -> eyre::Result<()> {
|
||||
fn main() {
|
||||
let nprocs = std::thread::available_parallelism()
|
||||
.map(|n| n.get())
|
||||
.unwrap_or_default();
|
||||
@@ -117,11 +118,41 @@ fn main() -> eyre::Result<()> {
|
||||
let threads = crate::env::MISE_JOBS
|
||||
.unwrap_or_else(|| nprocs.min(16))
|
||||
.max(8);
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
let runtime = match tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.worker_threads(threads)
|
||||
.build()?
|
||||
.block_on(main_())
|
||||
.build()
|
||||
{
|
||||
Ok(runtime) => runtime,
|
||||
Err(err) => {
|
||||
eprintln!("Error: {err:?}");
|
||||
exit::terminate(1);
|
||||
}
|
||||
};
|
||||
let result = runtime.block_on(main_());
|
||||
let (code, requested_exit) = match result {
|
||||
Ok(()) => (0, false),
|
||||
Err(err) => match exit::requested_exit_code(&err) {
|
||||
Some(code) => {
|
||||
exit::kill_all();
|
||||
(code, true)
|
||||
}
|
||||
None => {
|
||||
eprintln!("Error: {err:?}");
|
||||
(1, false)
|
||||
}
|
||||
},
|
||||
};
|
||||
if requested_exit {
|
||||
// Blocking tasks cannot be cancelled and may ignore the signals sent
|
||||
// above. Bound the wait so an intentional exit cannot hang forever.
|
||||
runtime.shutdown_timeout(Duration::from_secs(1));
|
||||
} else {
|
||||
drop(runtime);
|
||||
}
|
||||
if code != 0 {
|
||||
exit::terminate(code);
|
||||
}
|
||||
}
|
||||
|
||||
async fn main_() -> eyre::Result<()> {
|
||||
@@ -152,6 +183,9 @@ async fn main_() -> eyre::Result<()> {
|
||||
}
|
||||
|
||||
fn handle_err(err: Report) -> eyre::Result<()> {
|
||||
if exit::requested_exit_code(&err).is_some() {
|
||||
return Err(err);
|
||||
}
|
||||
if let Some(err) = err.downcast_ref::<std::io::Error>()
|
||||
&& err.kind() == std::io::ErrorKind::BrokenPipe
|
||||
{
|
||||
@@ -159,20 +193,20 @@ fn handle_err(err: Report) -> eyre::Result<()> {
|
||||
}
|
||||
if is_interrupted_io_error(&err) {
|
||||
stop_multi_progress();
|
||||
exit(130);
|
||||
return Err(request_exit(130));
|
||||
}
|
||||
|
||||
// Check for miette diagnostic errors and render them specially
|
||||
if let Some(diagnostic) = err.downcast_ref::<config::config_file::diagnostic::MiseDiagnostic>()
|
||||
{
|
||||
safe_eprintln!("{}", diagnostic.render());
|
||||
exit(1);
|
||||
return Err(request_exit(1));
|
||||
}
|
||||
|
||||
show_github_rate_limit_err(&err);
|
||||
if *env::MISE_FRIENDLY_ERROR {
|
||||
display_friendly_err(&err);
|
||||
exit(1);
|
||||
return Err(request_exit(1));
|
||||
}
|
||||
let async_backtrace = async_backtrace::taskdump_tree(true);
|
||||
Err(err.section(async_backtrace.header("Async Tasks")))
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::toolset::install_state;
|
||||
use crate::ui::multi_progress_report::MultiProgressReport;
|
||||
use crate::ui::progress_report::SingleReport;
|
||||
use crate::ui::prompt;
|
||||
use crate::{backend, dirs, env, exit, file, lock_file, registry};
|
||||
use crate::{backend, dirs, env, file, lock_file, registry};
|
||||
use async_trait::async_trait;
|
||||
use clap::Command;
|
||||
use console::style;
|
||||
@@ -459,7 +459,7 @@ Plugins could support local directories in the future but for now a symlink is r
|
||||
);
|
||||
Settings::ensure_not_safe("executing asdf plugin scripts")?;
|
||||
let result = self.script_man.cmd(&script).unchecked().run()?;
|
||||
exit(result.status.code().unwrap_or(-1));
|
||||
Err(crate::request_exit(result.status.code().unwrap_or(-1)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
use crate::exit;
|
||||
use crate::request_exit;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
@@ -77,7 +77,7 @@ pub async fn handle_shim() -> Result<()> {
|
||||
};
|
||||
time!("shim exec");
|
||||
exec.run().await?;
|
||||
exit(0);
|
||||
Err(request_exit(0))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::exit;
|
||||
use crate::task::task_output_handler::OutputHandler;
|
||||
use crate::task::{FailedTasks, Task};
|
||||
use crate::ui::{style, time};
|
||||
use crate::{Result, request_exit};
|
||||
|
||||
/// Handles display of task execution results and failure summaries
|
||||
pub struct TaskResultsDisplay {
|
||||
@@ -27,11 +27,11 @@ impl TaskResultsDisplay {
|
||||
}
|
||||
|
||||
/// Display final results and handle failures
|
||||
pub fn display_results(&self, num_tasks: usize, timer: std::time::Instant) {
|
||||
pub fn display_results(&self, num_tasks: usize, timer: std::time::Instant) -> Result<()> {
|
||||
self.display_keep_order_output();
|
||||
self.display_timing_summary(num_tasks, timer);
|
||||
self.maybe_print_failure_summary();
|
||||
self.exit_if_failed();
|
||||
self.exit_if_failed()
|
||||
}
|
||||
|
||||
/// Flush any remaining keep-order output (safety net).
|
||||
@@ -78,8 +78,8 @@ impl TaskResultsDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
/// Exit if any tasks failed
|
||||
fn exit_if_failed(&self) {
|
||||
/// Request a failing exit status if any tasks failed
|
||||
fn exit_if_failed(&self) -> Result<()> {
|
||||
if let Some((task, status)) = self.failed_tasks.lock().unwrap().first() {
|
||||
let prefix = task.estyled_prefix();
|
||||
self.eprint(
|
||||
@@ -87,8 +87,9 @@ impl TaskResultsDisplay {
|
||||
&prefix,
|
||||
&format!("{} task failed", style::ered("ERROR")),
|
||||
);
|
||||
exit(status.unwrap_or(1));
|
||||
return Err(request_exit(status.unwrap_or(1)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Print error message for a task
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::config::{Config, Settings};
|
||||
use crate::env_diff::EnvMap;
|
||||
use crate::exit::exit;
|
||||
use crate::request_exit;
|
||||
use crate::shell::ShellType;
|
||||
use crate::task::Task;
|
||||
use crate::tera::{TeraEngine, contains_template_syntax, get_tera, render_str};
|
||||
@@ -704,7 +704,7 @@ impl TaskScriptParser {
|
||||
// just print exactly what usage returns so the error output isn't double-wrapped
|
||||
// this could be displaying help or a parse error
|
||||
eprintln!("{}", format!("{e}").trim_end());
|
||||
exit(1);
|
||||
return Err(request_exit(1));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+14
-17
@@ -1,4 +1,3 @@
|
||||
use crate::exit;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::cmd::CmdLineRunner;
|
||||
@@ -9,23 +8,21 @@ static SHOW_CURSOR: AtomicBool = AtomicBool::new(false);
|
||||
static CANCELLED: AtomicBool = AtomicBool::new(false);
|
||||
// static HANDLERS: OnceCell<Vec<Box<dyn Fn() + Send + Sync + 'static>>> = OnceCell::new();
|
||||
|
||||
pub fn init() {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
if SHOW_CURSOR.load(Ordering::Relaxed) {
|
||||
let _ = Term::stderr().show_cursor();
|
||||
}
|
||||
CmdLineRunner::kill_all(nix::sys::signal::SIGINT);
|
||||
if EXIT.load(Ordering::Relaxed) || CANCELLED.load(Ordering::Relaxed) {
|
||||
debug!("Ctrl-C pressed, exiting...");
|
||||
exit(1);
|
||||
}
|
||||
// First ctrl-c when EXIT is false: mark as cancelled so a second
|
||||
// ctrl-c will force-exit and in-process operations can check the flag.
|
||||
CANCELLED.store(true, Ordering::Relaxed);
|
||||
pub async fn exit_signal() -> i32 {
|
||||
loop {
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
if SHOW_CURSOR.load(Ordering::Relaxed) {
|
||||
let _ = Term::stderr().show_cursor();
|
||||
}
|
||||
});
|
||||
CmdLineRunner::kill_all(nix::sys::signal::SIGINT);
|
||||
if EXIT.load(Ordering::Relaxed) || CANCELLED.load(Ordering::Relaxed) {
|
||||
debug!("Ctrl-C pressed, exiting...");
|
||||
return 1;
|
||||
}
|
||||
// First ctrl-c when EXIT is false: mark as cancelled so a second
|
||||
// ctrl-c will end the command and in-process operations can check the flag.
|
||||
CANCELLED.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exit_on_ctrl_c(do_exit: bool) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
pub fn init() {}
|
||||
pub async fn exit_signal() -> i32 {
|
||||
std::future::pending().await
|
||||
}
|
||||
|
||||
// pub fn add_handler(_func: impl Fn() + Send + Sync + 'static) {}
|
||||
|
||||
|
||||
Reference in new issue
Block a user