From c1f6b12be189b8931582b84cfd7650fec301ae54 Mon Sep 17 00:00:00 2001 From: Martin Olsson Date: Tue, 12 Nov 2024 22:00:30 +0100 Subject: [PATCH] feat: Kill all child processes when shell received CTRL-C --- Cargo.toml | 2 +- src/shell/commands/executable.rs | 45 ++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1212b0d..4422a9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ anyhow = "1.0.75" futures = { version = "0.3.29", optional = true } glob = { version = "0.3.1", optional = true } path-dedot = { version = "3.1.1", optional = true } -tokio = { version = "1", features = ["fs", "io-std", "io-util", "macros", "process", "rt-multi-thread", "sync", "time"], optional = true } +tokio = { version = "1", features = ["fs", "io-std", "io-util", "macros", "process", "rt-multi-thread", "sync", "time", "signal"], optional = true } tokio-util = { version = "0.7.10", optional = true } os_pipe = { version = "1.1.4", optional = true } serde = { version = "1", features = ["derive"], optional = true } diff --git a/src/shell/commands/executable.rs b/src/shell/commands/executable.rs index 7cf2b09..107ed2f 100644 --- a/src/shell/commands/executable.rs +++ b/src/shell/commands/executable.rs @@ -7,6 +7,8 @@ use crate::FutureExecuteResult; use crate::ShellCommand; use crate::ShellCommandContext; use futures::FutureExt; +use std::sync::Arc; +use tokio::sync::Mutex; /// Command that resolves the command name and /// executes it in a separate process. @@ -41,8 +43,8 @@ impl ShellCommand for ExecutableCommand { .stderr(stderr.clone().into_stdio()) .spawn(); - let mut child = match child { - Ok(child) => child, + let child = match child { + Ok(child) => Arc::new(Mutex::new(child)), Err(err) => { let _ = stderr.write_line(&format!( "Error launching '{}': {}", @@ -55,22 +57,33 @@ impl ShellCommand for ExecutableCommand { // avoid deadlock since this is holding onto the pipes drop(sub_command); - tokio::select! { - result = child.wait() => match result { - Ok(status) => ExecuteResult::Continue( - status.code().unwrap_or(1), - Vec::new(), - Vec::new(), - ), - Err(err) => { - let _ = stderr.write_line(&format!("{}", err)); - ExecuteResult::Continue(1, Vec::new(), Vec::new()) - } - }, - _ = context.state.token().cancelled() => { + let child_clone = Arc::clone(&child); + + tokio::spawn(async move { + tokio::signal::ctrl_c().await.unwrap(); + let mut child = child_clone.lock().await; + if let Ok(None) = child.try_wait() { let _ = child.kill().await; - ExecuteResult::for_cancellation() } + }); + + let mut child_locked = child.lock().await; + tokio::select! { + result = child_locked.wait() => match result { + Ok(status) => ExecuteResult::Continue( + status.code().unwrap_or(1), + Vec::new(), + Vec::new(), + ), + Err(err) => { + let _ = stderr.write_line(&format!("{}", err)); + ExecuteResult::Continue(1, Vec::new(), Vec::new()) + } + }, + _ = context.state.token().cancelled() => { + let _ = child_locked.kill().await; + ExecuteResult::for_cancellation() + } } } .boxed_local()