use std::process::Child;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
pub struct ProcessOutput {
pub stdout: String,
pub timed_out: bool,
pub exit_code: Option<i32>,
}
pub fn run_with_timeout(
mut child: Child,
timeout_secs: u64,
max_output_bytes: u64,
) -> Result<ProcessOutput, String> {
let stdout = child
.stdout
.take()
.ok_or("failed to capture subprocess stdout")?;
if let Some(stderr) = child.stderr.take() {
std::thread::spawn(move || {
use std::io::Read;
let _ = std::io::copy(&mut stderr.take(1024 * 1024), &mut std::io::sink());
});
}
let (cancel_tx, cancel_rx) = mpsc::channel::<()>();
let child = Arc::new(Mutex::new(child));
let child_watcher = Arc::clone(&child);
let timed_out = Arc::new(AtomicBool::new(false));
let timed_out_watcher = Arc::clone(&timed_out);
let watcher = thread::spawn(move || {
if cancel_rx
.recv_timeout(Duration::from_secs(timeout_secs))
.is_err()
&& let Ok(mut c) = child_watcher.lock()
&& matches!(c.try_wait(), Ok(None))
{
let _ = c.kill();
let _ = c.wait(); timed_out_watcher.store(true, Ordering::Relaxed);
}
});
let mut output = String::new();
{
use std::io::Read;
let _ = stdout.take(max_output_bytes).read_to_string(&mut output);
}
let _ = cancel_tx.send(());
let _ = watcher.join();
let exit_code = child
.lock()
.ok()
.and_then(|mut c| c.wait().ok().and_then(|s| s.code()));
Ok(ProcessOutput {
stdout: output,
timed_out: timed_out.load(Ordering::Relaxed),
exit_code,
})
}