mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-27 08:34:11 +00:00
Closes #45211 This ensures that all sub-processes that were launched by the ACP server are terminated. One scenario where this is easily reproducible: - Start a new Claude Code ACP session - Submit a prompt - While Claude-code is still responding, start a new session - The `claude-code` subprocess is leaked from the previous session (The Claude-code SDK runs the Claude-code binary in a sub process) This PR fixes this by using process groups on Unix. It does not fix the process leaks on Windows yet (will follow up with another PR) Release Notes: - Fixed an issue where subprocesses of ACP servers could be leaked after starting a new session
82 lines
2.2 KiB
Rust
82 lines
2.2 KiB
Rust
use anyhow::{Context as _, Result};
|
|
use std::process::Stdio;
|
|
|
|
/// A wrapper around `smol::process::Child` that ensures all subprocesses
|
|
/// are killed when the process is terminated by using process groups.
|
|
pub struct Child {
|
|
process: smol::process::Child,
|
|
}
|
|
|
|
impl std::ops::Deref for Child {
|
|
type Target = smol::process::Child;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.process
|
|
}
|
|
}
|
|
|
|
impl std::ops::DerefMut for Child {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.process
|
|
}
|
|
}
|
|
|
|
impl Child {
|
|
#[cfg(not(windows))]
|
|
pub fn spawn(
|
|
mut command: std::process::Command,
|
|
stdin: Stdio,
|
|
stdout: Stdio,
|
|
stderr: Stdio,
|
|
) -> Result<Self> {
|
|
crate::set_pre_exec_to_start_new_session(&mut command);
|
|
let mut command = smol::process::Command::from(command);
|
|
let process = command
|
|
.stdin(stdin)
|
|
.stdout(stdout)
|
|
.stderr(stderr)
|
|
.spawn()
|
|
.with_context(|| format!("failed to spawn command {command:?}"))?;
|
|
Ok(Self { process })
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub fn spawn(
|
|
command: std::process::Command,
|
|
stdin: Stdio,
|
|
stdout: Stdio,
|
|
stderr: Stdio,
|
|
) -> Result<Self> {
|
|
// TODO(windows): create a job object and add the child process handle to it,
|
|
// see https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects
|
|
let mut command = smol::process::Command::from(command);
|
|
let process = command
|
|
.stdin(stdin)
|
|
.stdout(stdout)
|
|
.stderr(stderr)
|
|
.spawn()
|
|
.with_context(|| format!("failed to spawn command {command:?}"))?;
|
|
|
|
Ok(Self { process })
|
|
}
|
|
|
|
pub fn into_inner(self) -> smol::process::Child {
|
|
self.process
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
pub fn kill(&mut self) -> Result<()> {
|
|
let pid = self.process.id();
|
|
unsafe {
|
|
libc::killpg(pid as i32, libc::SIGKILL);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub fn kill(&mut self) -> Result<()> {
|
|
// TODO(windows): terminate the job object in kill
|
|
self.process.kill()?;
|
|
Ok(())
|
|
}
|
|
}
|