use std::process::Stdio;
use tokio::process::{ChildStderr, ChildStdin, ChildStdout, Command};
use anyhow::{Result, anyhow};
use tracing::warn;
use crate::protocol::common::SendReceivePair;
use crate::protocol::common::{ReceivingStream, SendingStream};
#[derive(Debug, derive_more::Constructor)]
pub(crate) struct ProcessWrapper {
process: tokio::process::Child,
}
impl Drop for ProcessWrapper {
fn drop(&mut self) {
if let Ok(Some(_)) = self.process.try_wait() {
return;
}
let _ = self
.process
.start_kill()
.map_err(|e| warn!("killing connection process: {e}"));
let _ = self
.process
.try_wait()
.map_err(|e| warn!("reaping connection process: {e}"));
}
}
impl SendingStream for ChildStdin {}
impl ReceivingStream for ChildStdout {}
impl ProcessWrapper {
pub(crate) fn spawn(mut cmd: Command) -> Result<Self> {
let process = cmd
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.map_err(|e| anyhow!("could not spawn child process: {e}"))?;
Ok(Self { process })
}
pub(crate) fn stderr(&mut self) -> Option<ChildStderr> {
self.process.stderr.take()
}
pub(crate) async fn close(&mut self) -> Result<()> {
let _ = self.process.wait().await?;
Ok(())
}
pub(crate) fn stream_pair(&mut self) -> Result<SendReceivePair<ChildStdin, ChildStdout>> {
let sp = SendReceivePair::from((
self.process
.stdin
.take()
.ok_or_else(|| anyhow!("could not access process stdin"))?,
self.process
.stdout
.take()
.ok_or_else(|| anyhow!("could not access process stdout"))?,
));
Ok(sp)
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod test {
#[cfg(unix)]
#[tokio::test]
async fn drop_coverage() {
let process = tokio::process::Command::new("sleep")
.arg("100")
.spawn()
.expect("could not spawn sleep command");
let wrapper = super::ProcessWrapper { process };
drop(wrapper);
}
}