pub mod executor;
pub mod signal;
pub mod stream;
use std::time::{Duration, Instant};
use tokio::process::Child;
use tokio::sync::mpsc;
#[allow(unused_imports)]
pub use executor::SubprocessExecutor;
#[allow(unused_imports)]
pub use stream::{OutputLine, OutputStream};
pub struct SubprocessHandle {
child: Child,
stdout_rx: mpsc::Receiver<OutputLine>,
stderr_rx: mpsc::Receiver<OutputLine>,
start_time: Instant,
timeout: Duration,
finished: bool,
exit_code: Option<i32>,
}
impl SubprocessHandle {
pub(crate) fn new(
child: Child,
stdout_rx: mpsc::Receiver<OutputLine>,
stderr_rx: mpsc::Receiver<OutputLine>,
timeout: Duration,
) -> Self {
Self {
child,
stdout_rx,
stderr_rx,
start_time: Instant::now(),
timeout,
finished: false,
exit_code: None,
}
}
pub async fn cancel(&mut self) -> anyhow::Result<()> {
if !self.finished {
signal::graceful_kill(&mut self.child).await?;
self.finished = true;
}
Ok(())
}
pub fn try_recv_stdout(&mut self) -> Option<OutputLine> {
self.stdout_rx.try_recv().ok()
}
pub fn try_recv_stderr(&mut self) -> Option<OutputLine> {
self.stderr_rx.try_recv().ok()
}
pub fn is_running(&self) -> bool {
!self.finished
}
pub fn exit_code(&self) -> Option<i32> {
self.exit_code
}
pub fn is_timed_out(&self) -> bool {
!self.finished && self.start_time.elapsed() > self.timeout
}
pub fn try_poll_exit(&mut self) -> bool {
if self.finished {
return true;
}
match self.child.try_wait() {
Ok(Some(status)) => {
self.finished = true;
self.exit_code = Some(status.code().unwrap_or(-1));
true
}
_ => false,
}
}
pub async fn check_timeout(&mut self) -> bool {
if self.finished {
return false;
}
if self.start_time.elapsed() > self.timeout {
self.cancel().await.ok();
self.finished = true;
true
} else {
false
}
}
pub async fn wait(&mut self) -> anyhow::Result<i32> {
if let Some(code) = self.exit_code {
return Ok(code);
}
let status = self.child.wait().await?;
self.finished = true;
let code = status.code().unwrap_or(-1);
self.exit_code = Some(code);
Ok(code)
}
}