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