vfa_tui/subprocess/
mod.rs1pub mod executor;
2pub mod signal;
3pub mod stream;
4
5use std::time::{Duration, Instant};
6
7use tokio::process::Child;
8use tokio::sync::mpsc;
9
10#[allow(unused_imports)]
11pub use executor::SubprocessExecutor;
12#[allow(unused_imports)]
13pub use stream::{OutputLine, OutputStream};
14
15pub struct SubprocessHandle {
17 child: Child,
18 stdout_rx: mpsc::Receiver<OutputLine>,
19 stderr_rx: mpsc::Receiver<OutputLine>,
20 start_time: Instant,
21 timeout: Duration,
22 finished: bool,
23 exit_code: Option<i32>,
24}
25
26impl SubprocessHandle {
27 pub(crate) fn new(
28 child: Child,
29 stdout_rx: mpsc::Receiver<OutputLine>,
30 stderr_rx: mpsc::Receiver<OutputLine>,
31 timeout: Duration,
32 ) -> Self {
33 Self {
34 child,
35 stdout_rx,
36 stderr_rx,
37 start_time: Instant::now(),
38 timeout,
39 finished: false,
40 exit_code: None,
41 }
42 }
43
44 pub async fn cancel(&mut self) -> anyhow::Result<()> {
46 if !self.finished {
47 signal::graceful_kill(&mut self.child).await?;
48 self.finished = true;
49 }
50 Ok(())
51 }
52
53 pub fn try_recv_stdout(&mut self) -> Option<OutputLine> {
55 self.stdout_rx.try_recv().ok()
56 }
57
58 pub fn try_recv_stderr(&mut self) -> Option<OutputLine> {
60 self.stderr_rx.try_recv().ok()
61 }
62
63 pub fn is_running(&self) -> bool {
65 !self.finished
66 }
67
68 pub fn exit_code(&self) -> Option<i32> {
70 self.exit_code
71 }
72
73 pub fn is_timed_out(&self) -> bool {
76 !self.finished && self.start_time.elapsed() > self.timeout
77 }
78
79 pub fn try_poll_exit(&mut self) -> bool {
83 if self.finished {
84 return true;
85 }
86 match self.child.try_wait() {
87 Ok(Some(status)) => {
88 self.finished = true;
89 self.exit_code = Some(status.code().unwrap_or(-1));
90 true
91 }
92 _ => false,
93 }
94 }
95
96 pub async fn check_timeout(&mut self) -> bool {
99 if self.finished {
100 return false;
101 }
102 if self.start_time.elapsed() > self.timeout {
103 self.cancel().await.ok();
104 self.finished = true;
105 true
106 } else {
107 false
108 }
109 }
110
111 pub async fn wait(&mut self) -> anyhow::Result<i32> {
113 if let Some(code) = self.exit_code {
114 return Ok(code);
115 }
116
117 let status = self.child.wait().await?;
118 self.finished = true;
119 let code = status.code().unwrap_or(-1);
120 self.exit_code = Some(code);
121 Ok(code)
122 }
123}