Skip to main content

brush_core/
processes.rs

1//! Process management
2
3use futures::FutureExt;
4
5use crate::{error, sys};
6
7/// A waitable future that will yield the results of a child process's execution.
8pub(crate) type WaitableChildProcess = std::pin::Pin<
9    Box<dyn futures::Future<Output = Result<std::process::Output, std::io::Error>> + Send + Sync>,
10>;
11
12/// Tracks a child process being awaited.
13pub struct ChildProcess {
14    /// A waitable future that will yield the results of a child process's execution.
15    exec_future: WaitableChildProcess,
16    /// If available, the process ID of the child.
17    pid: Option<sys::process::ProcessId>,
18    /// If available, the process group ID of the child.
19    pgid: Option<sys::process::ProcessId>,
20}
21
22impl ChildProcess {
23    /// Wraps a child process and its future.
24    pub fn new(
25        child: sys::process::Child,
26        pid: Option<sys::process::ProcessId>,
27        pgid: Option<sys::process::ProcessId>,
28    ) -> Self {
29        Self {
30            exec_future: Box::pin(child.wait_with_output()),
31            pid,
32            pgid,
33        }
34    }
35
36    /// Returns the process's ID.
37    pub const fn pid(&self) -> Option<sys::process::ProcessId> {
38        self.pid
39    }
40
41    /// Returns the process's group ID.
42    pub const fn pgid(&self) -> Option<sys::process::ProcessId> {
43        self.pgid
44    }
45
46    /// Waits for the process to exit.
47    pub async fn wait(&mut self) -> Result<ProcessWaitResult, error::Error> {
48        #[allow(unused_mut, reason = "only mutated on some platforms")]
49        let mut sigtstp = sys::signal::tstp_signal_listener()?;
50        #[allow(unused_mut, reason = "only mutated on some platforms")]
51        let mut sigchld = sys::signal::chld_signal_listener()?;
52
53        #[allow(clippy::ignored_unit_patterns)]
54        loop {
55            tokio::select! {
56                output = &mut self.exec_future => {
57                    break Ok(ProcessWaitResult::Completed(output?))
58                },
59                _ = sigtstp.recv() => {
60                    break Ok(ProcessWaitResult::Stopped)
61                },
62                _ = sigchld.recv() => {
63                    if sys::signal::poll_for_stopped_children()? {
64                        break Ok(ProcessWaitResult::Stopped);
65                    }
66                },
67                _ = sys::signal::await_ctrl_c() => {
68                    // SIGINT got thrown. Handle it and continue looping. The child should
69                    // have received it as well, and either handled it or ended up getting
70                    // terminated (in which case we'll see the child exit).
71                },
72            }
73        }
74    }
75
76    pub(crate) fn poll(&mut self) -> Option<Result<std::process::Output, error::Error>> {
77        let checkable_future = &mut self.exec_future;
78        checkable_future
79            .now_or_never()
80            .map(|result| result.map_err(Into::into))
81    }
82}
83
84/// Represents the result of waiting for an executing process.
85pub enum ProcessWaitResult {
86    /// The process completed.
87    Completed(std::process::Output),
88    /// The process stopped and has not yet completed.
89    Stopped,
90}