Weird interaction between Stdio::piped and Child::try_wait

Hello,

code:

use std::process::{Command, Stdio};

fn main() {
    let mut cmd = Command::new("cat")
        .arg(std::env::args().nth(1).unwrap())
        .stdout(Stdio::piped()) // if this line is removed, the program works normally
        .spawn()
        .unwrap();
    while cmd.try_wait().unwrap().is_none() {
        //stuck here
    }
}

If I run this program on a "large" file (>~80KB) it hangs indefinitely.

If I remove the stdout piping, it works normally.

Is this an expected behavior?
What I feel is somehow waitpid slows down the running program if its stdout is piped? (running this on a "small" (~4kb) file works)

I expect the problem is that the buffer it uses for stdout is filled up and never consumed, so it hangs while waiting for the buffer to be read. In the example you can just use Command::output instead, but I assume your actual use case is more complex.

Try this for example

    let mut stdout = cmd.stdout.take().unwrap();
    let mut buf = [0; 512];
    while cmd.try_wait().unwrap().is_none() {
        stdout.read(&mut buf).unwrap();
    }

edit: There's an open issue regarding this, where you can also find a comment by me from earlier this year when I also bumped into this. :sweat_smile:

2 Likes

@Heliozoa Thanks a lot!

Here is what I ended up doing:

fn fn(self: process::Child, function:...) -> Result<process::Output> { 
        let mut stdout = self.stdout.take().expect("stdout is piped");
        let mut stderr = self.stderr.take().expect("stderr is piped");

        let (tx_out, rx) = mpsc::channel();
        let tx_err = tx_out.clone();
        enum OutType {
            Stdout(Vec<u8>),
            Stderr(Vec<u8>),
        }

        std::thread::spawn(move || {
            let mut out = Vec::new();
            stdout.read_to_end(&mut out).unwrap();
            tx_out.send(OutType::Stdout(out)).unwrap();
        });

        std::thread::spawn(move || {
            let mut err = Vec::new();
            stderr.read_to_end(&mut err).unwrap();
            tx_err.send(OutType::Stderr(err)).unwrap();
        });

        while self.try_wait()?.is_none() {
            if let Some(ref function) = function {
                function(&mut self)?;
            }
        }
        let mut stdout = None;
        let mut stderr = None;
        for _ in 0..2 {
            match rx.recv()? {
                OutType::Stdout(out) => stdout = Some(out),
                OutType::Stderr(err) => stderr = Some(err),
            }
        }

        Ok(
            Output {
                status: self.wait()?,
                stdout: stdout.unwrap(),
                stderr: stderr.unwrap(),
            }
        )
   }    
}