KillOnDrop: SIGTERM process group when tool task is aborted

tokio::spawn abort drops the future but leaves child processes
running as orphans. KillOnDrop sends SIGTERM to the process
group on drop, ensuring cleanup. Defused via mem::forget on
normal completion.

Co-Developed-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
ProofOfConcept 2026-04-03 23:47:36 -04:00
parent a78f310e4d
commit 310bbe9fce

View file

@ -14,6 +14,18 @@ use tokio::io::AsyncReadExt;
use super::{ToolDef, ProcessTracker, default_timeout}; use super::{ToolDef, ProcessTracker, default_timeout};
/// RAII guard that SIGTERMs the process group on drop.
/// Ensures child processes are cleaned up when a task is aborted.
struct KillOnDrop(u32); // pid
impl Drop for KillOnDrop {
fn drop(&mut self) {
if self.0 != 0 {
unsafe { libc::kill(-(self.0 as i32), libc::SIGTERM); }
}
}
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct Args { struct Args {
command: String, command: String,
@ -60,6 +72,7 @@ pub async fn run_bash(args: &serde_json::Value, tracker: &ProcessTracker) -> Res
.with_context(|| format!("Failed to spawn: {}", command))?; .with_context(|| format!("Failed to spawn: {}", command))?;
let pid = child.id().unwrap_or(0); let pid = child.id().unwrap_or(0);
let kill_guard = KillOnDrop(pid);
tracker.register(pid, command).await; tracker.register(pid, command).await;
// Take ownership of stdout/stderr handles before waiting, // Take ownership of stdout/stderr handles before waiting,
@ -132,6 +145,8 @@ pub async fn run_bash(args: &serde_json::Value, tracker: &ProcessTracker) -> Res
} }
}; };
// Process completed normally — defuse the kill guard
std::mem::forget(kill_guard);
tracker.unregister(pid).await; tracker.unregister(pid).await;
result result
} }