asynchronousruststdiorust-tokio

How to read subprocess output asynchronously


I want to implement a futures::Stream for reading and parsing the standard output of a child subprocess.

What I'm doing at the moment:

The problem is that AllowStdIo doesn't make ChildStdout magically asynchronous and the self.io.read_line call still blocks.

I guess I need to pass something different instead of Stdio::pipe() to have it asynchronous, but what? Or is there a different solution for that?

This question is different from What is the best approach to encapsulate blocking I/O in future-rs? because I want to get asynchronous I/O for the specific case of a subprocess and not solve the problem of encapsulation of synchronous I/O.

Update: I'm using tokio = "0.1.3" to leverage its runtime feature and using tokio-process is not an option at the moment (https://github.com/alexcrichton/tokio-process/issues/27)


Solution

  • Here is my version using tokio::process

    let mut child = match Command::new(&args.run[0])
            .args(parameters)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .kill_on_drop(true)
            .spawn() 
        {
            Ok(c) => c,
            Err(e) => panic!("Unable to start process `{}`. {}", args.run[0], e),
        };
    
        let stdout = child.stdout.take().expect("child did not have a handle to stdout");
        let stderr = child.stderr.take().expect("child did not have a handle to stderr");
    
        let mut stdout_reader = BufReader::new(stdout).lines();
        let mut stderr_reader = BufReader::new(stderr).lines();
    
        loop {
            tokio::select! {
                result = stdout_reader.next_line() => {
                    match result {
                        Ok(Some(line)) => println!("Stdout: {}", line),
                        Err(_) => break,
                        _ => (),
                    }
                }
                result = stderr_reader.next_line() => {
                    match result {
                        Ok(Some(line)) => println!("Stderr: {}", line),
                        Err(_) => break,
                        _ => (),
                    }
                }
                result = child.wait() => {
                    match result {
                        Ok(exit_code) => println!("Child process exited with {}", exit_code),
                        _ => (),
                    }
                    break // child process exited
                }
            };
    
        }