diff --git a/Cargo.lock b/Cargo.lock index adb0d4f65d..d0a465ca11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1789,8 +1789,7 @@ dependencies = [ [[package]] name = "deno_task_shell" version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb3527b0dc64fb6c1cb19023ef92a949b54c735c37af9bfb9fe4095922278da" +source = "git+https://github.com/wolfv/deno_task_shell?branch=process-signaler#b6c834af92c4a71570fcccde7834e4af22c721cf" dependencies = [ "anyhow", "deno_path_util", @@ -1798,7 +1797,6 @@ dependencies = [ "glob", "monch", "nix 0.29.0", - "os_pipe", "path-dedot", "sys_traits", "thiserror 2.0.17", @@ -2145,7 +2143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3125,7 +3123,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -3440,7 +3438,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4397,16 +4395,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "os_pipe" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - [[package]] name = "os_str_bytes" version = "6.6.1" @@ -6107,7 +6095,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.1", + "socket2 0.5.10", "thiserror 2.0.17", "tokio", "tracing", @@ -6144,9 +6132,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.5.10", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -7179,7 +7167,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7192,7 +7180,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7259,7 +7247,7 @@ dependencies = [ "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs 0.26.11", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -8242,7 +8230,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix 1.1.2", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -10359,7 +10347,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2ffa2f3db3..12d43662a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ crossbeam-channel = "0.5.14" csv = "1.3.1" ctrlc = "3.4.5" dashmap = "6.1.0" -deno_task_shell = "0.26.0" +deno_task_shell = { git = "https://github.com/wolfv/deno_task_shell", branch = "process-signaler" } derive_more = "2.0.1" dialoguer = "0.11.0" digest = "0.10" diff --git a/crates/pixi_cli/src/run.rs b/crates/pixi_cli/src/run.rs index 189ef2266b..7653524e13 100644 --- a/crates/pixi_cli/src/run.rs +++ b/crates/pixi_cli/src/run.rs @@ -5,9 +5,6 @@ use std::{ string::String, }; -#[cfg(unix)] -use std::io::IsTerminal; - use clap::Parser; use deno_task_shell::KillSignal; use dialoguer::theme::ColorfulTheme; @@ -403,6 +400,7 @@ async fn execute_task( return Ok(()); }; let cwd = task.working_directory()?; + let execute_future = deno_task_shell::execute( script, command_env.clone(), @@ -520,33 +518,31 @@ async fn listen_ctrl_c_windows() { while let Ok(()) = tokio::signal::ctrl_c().await {} } -/// Listens to all incoming signals and forwards all of them, except -/// some cases. +/// Listens to all incoming signals and forwards them to the child process. /// -/// Note that we don't handle `SIGINT` correctly, if the subprocess changes -/// its PGID, then the system won't forward CTRL+C automatically. -/// However, we should do that to ensure consistent behaviour. +/// Signal forwarding follows UV's approach: +/// - SIGINT in interactive mode: Include PGID so deno_task_shell can skip +/// forwarding if the child is in the same PGID (terminal already sent it). +/// - All other signals: Always forward unconditionally. /// -/// To resolve this we should patch `deno_task_shell` to return PID -/// from which we could get PGID and do things right. -/// -/// Resulting approach should mimic -/// https://github.com/astral-sh/uv/blob/9d17dfa3537312b928f94479f632891f918c4760/crates/uv/src/child.rs#L156C21-L168C77. +/// Trade-off: `kill -INT ` won't work in interactive mode when the child +/// is in the same PGID. Use CTRL+C instead, or `kill -TERM` which always forwards. #[cfg(unix)] async fn listen_and_forward_all_signals(kill_signal: KillSignal) { - use futures::FutureExt; + use std::io::IsTerminal; + use futures::FutureExt; use pixi_core::signals::SIGNALS; + let is_interactive = std::io::stdin().is_terminal(); + let our_pgid = unsafe { libc::getpgid(0) }; + // listen and forward every signal we support let mut futures = Vec::with_capacity(SIGNALS.len()); - let is_interactive = std::io::stdin().is_terminal(); for signo in SIGNALS.iter().copied() { - if signo == libc::SIGKILL - || signo == libc::SIGSTOP - || (signo == libc::SIGINT && is_interactive) - { - continue; // skip, can't listen to these + // SIGKILL and SIGSTOP cannot be caught or blocked + if signo == libc::SIGKILL || signo == libc::SIGSTOP { + continue; } let kill_signal = kill_signal.clone(); @@ -557,7 +553,15 @@ async fn listen_and_forward_all_signals(kill_signal: KillSignal) { }; let signal_kind = signo.into(); while let Some(()) = stream.recv().await { - kill_signal.send(signal_kind); + // Only SIGINT gets PGID-aware handling in interactive mode. + // The terminal driver sends SIGINT to the process group on Ctrl+C, + // so we skip forwarding if the child is in the same PGID. + // All other signals are forwarded unconditionally. + if is_interactive && signo == libc::SIGINT { + kill_signal.send_from_pgid(signal_kind, our_pgid); + } else { + kill_signal.send(signal_kind); + } } } .boxed_local(),