process.rs

 1use anyhow::{Context as _, Result};
 2use std::process::Stdio;
 3
 4/// A wrapper around `smol::process::Child` that ensures all subprocesses
 5/// are killed when the process is terminated by using process groups.
 6pub struct Child {
 7    process: smol::process::Child,
 8}
 9
10impl std::ops::Deref for Child {
11    type Target = smol::process::Child;
12
13    fn deref(&self) -> &Self::Target {
14        &self.process
15    }
16}
17
18impl std::ops::DerefMut for Child {
19    fn deref_mut(&mut self) -> &mut Self::Target {
20        &mut self.process
21    }
22}
23
24impl Child {
25    #[cfg(not(windows))]
26    pub fn spawn(
27        mut command: std::process::Command,
28        stdin: Stdio,
29        stdout: Stdio,
30        stderr: Stdio,
31    ) -> Result<Self> {
32        crate::set_pre_exec_to_start_new_session(&mut command);
33        let mut command = smol::process::Command::from(command);
34        let process = command
35            .stdin(stdin)
36            .stdout(stdout)
37            .stderr(stderr)
38            .spawn()
39            .with_context(|| format!("failed to spawn command {command:?}"))?;
40        Ok(Self { process })
41    }
42
43    #[cfg(windows)]
44    pub fn spawn(
45        command: std::process::Command,
46        stdin: Stdio,
47        stdout: Stdio,
48        stderr: Stdio,
49    ) -> Result<Self> {
50        // TODO(windows): create a job object and add the child process handle to it,
51        // see https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects
52        let mut command = smol::process::Command::from(command);
53        let process = command
54            .stdin(stdin)
55            .stdout(stdout)
56            .stderr(stderr)
57            .spawn()
58            .with_context(|| format!("failed to spawn command {command:?}"))?;
59
60        Ok(Self { process })
61    }
62
63    pub fn into_inner(self) -> smol::process::Child {
64        self.process
65    }
66
67    #[cfg(not(windows))]
68    pub fn kill(&mut self) -> Result<()> {
69        let pid = self.process.id();
70        unsafe {
71            libc::killpg(pid as i32, libc::SIGKILL);
72        }
73        Ok(())
74    }
75
76    #[cfg(windows)]
77    pub fn kill(&mut self) -> Result<()> {
78        // TODO(windows): terminate the job object in kill
79        self.process.kill()?;
80        Ok(())
81    }
82}