Spawn process with timeout and capture output in tokio

I want to run a program and kill it when it times out, while capturing its stdout and stderr. Here's the code I wrote using Tokio:

use tokio::process::Command;
use tokio::time;

let mut child = Command::new("...").spawn().unwrap();
let output = match time::timeout(timeout, child.wait() /* takes &mut self */).await {
    Ok(_) => child
        .wait_with_output() // takes self
        .await
        .unwrap(),
    Err(_) => {
        error!("...");
        child
            .kill()
            .await
            .unwrap();
        return Err("..");
    }
};

But I found the stdout/stderr is always empty. It seems like the pipes were closed before data was collected. Could tokio consider to provide something to solve this issue, or what's the right way to handle these?

Solved: I forget to set stdout/stderr to piped.

1 Like

You can spawn a background task for reading stdout. See e.g. this example from the docs:

#[tokio::main]
async fn main() {
    let (_tx, rx) = channel::<()>();

    let mut child = Command::new("echo")
        .arg("Hello World!")
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

    let mut stdout = child.stdout.take().expect("stdout is not captured");

    let read_stdout = tokio::spawn(async move {
        let mut buff = Vec::new();
        let _ = stdout.read_to_end(&mut buff).await;

        buff
    });

    tokio::select! {
        _ = child.wait() => {}
        _ = rx => { child.kill().await.expect("kill failed") },
    }

    let buff = read_stdout.await.unwrap();

    assert_eq!(buff, b"Hello World!\n");
}
2 Likes