use std::io::Read;
use std::process::{Command, Stdio};
use std::sync::mpsc::{self, Receiver};
use std::thread::JoinHandle;
use std::time::Duration;
use wait_timeout::ChildExt;
#[allow(dead_code)]
pub const DOCKER_PROBE_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Debug)]
pub enum ProbeOutcome {
Completed { success: bool, stdout: String },
TimedOut,
SpawnFailed,
}
struct PipeReader {
handle: JoinHandle<()>,
rx: Receiver<Vec<u8>>,
}
impl PipeReader {
fn spawn<R: Read + Send + 'static>(pipe: Option<R>) -> Self {
let (tx, rx) = mpsc::channel();
let handle = std::thread::spawn(move || {
let mut buf = Vec::new();
if let Some(mut pipe) = pipe {
let _ = pipe.read_to_end(&mut buf);
}
let _ = tx.send(buf);
});
PipeReader { handle, rx }
}
fn collect(self, grace: Duration) -> Vec<u8> {
match self.rx.recv_timeout(grace) {
Ok(buf) => {
let _ = self.handle.join();
buf
}
Err(_) => Vec::new(),
}
}
}
#[allow(dead_code)]
pub fn bounded_probe(bin: &str, args: &[&str], timeout: Duration) -> ProbeOutcome {
const DRAIN_GRACE: Duration = Duration::from_secs(2);
let mut child = match Command::new(bin)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Ok(child) => child,
Err(_) => return ProbeOutcome::SpawnFailed,
};
let stdout_reader = PipeReader::spawn(child.stdout.take());
let stderr_reader = PipeReader::spawn(child.stderr.take());
match child.wait_timeout(timeout) {
Ok(Some(status)) => {
let stdout = stdout_reader.collect(DRAIN_GRACE);
let _stderr = stderr_reader.collect(DRAIN_GRACE);
ProbeOutcome::Completed {
success: status.success(),
stdout: String::from_utf8_lossy(&stdout).to_string(),
}
}
Ok(None) | Err(_) => {
let _ = child.kill();
let _ = child.wait();
let _stdout = stdout_reader.collect(DRAIN_GRACE);
let _stderr = stderr_reader.collect(DRAIN_GRACE);
ProbeOutcome::TimedOut
}
}
}
#[allow(dead_code)]
pub fn docker_probe(args: &[&str]) -> Option<String> {
match bounded_probe("docker", args, DOCKER_PROBE_TIMEOUT) {
ProbeOutcome::Completed {
success: true,
stdout,
} => Some(stdout),
_ => None,
}
}