tokio icon indicating copy to clipboard operation
tokio copied to clipboard

Difference in behavior between Stdio::piped() and tokio::net::unix::pipe

Open stepancheg opened this issue 5 months ago • 3 comments
trafficstars

Is your feature request related to a problem? Please describe.

Repro code does this:

  • create a process sh -c 'sleep 1000000000' (exec sleep 1 reproduces it too)
  • pipes stdout
  • kills the process (but does not kill child sleep process)
  • waits for process
  • waits for pipe

When using Stdio::piped() everything works fine.

When using tokio::net::unix::pipe, last step hangs.

I don't have explanation why it hangs.

    #[tokio::test]
    async fn repro_hanging_pipe() {
        let mut command = Command::new("sh");
        command.args(["-c", "sleep 10000000000"]);
        // Also reproduces with `sleep 1` and even `exec sleep 1`.

        command.stdin(Stdio::null());
        command.stderr(Stdio::inherit());

        let (mut child, mut stdout_rx): (_, Pin<Box<dyn AsyncRead>>) = if false {
            // Works with regular stdout pipe.
            command.stdout(Stdio::piped());
            let mut child = command.spawn().unwrap();
            let stdout_rx = mem::take(&mut child.stdout).unwrap();
            (child, Box::pin(stdout_rx))
        } else {
            // Hangs with tokio::net::unix::pipe.
            let (stdout_tx, stdout_rx) = tokio::net::unix::pipe::pipe().unwrap();
            command.stdout(stdout_tx.into_blocking_fd().unwrap());
            let child = command.spawn().unwrap();
            (child, Box::pin(stdout_rx))
        };

        let mut stdout = Vec::new();
        let stdout_fut = stdout_rx.read_to_end(&mut stdout);

        eprintln!("Sending SIGKILL");
        child.start_kill().unwrap();

        // To be safe.
        mem::take(&mut child.stdout);
        mem::take(&mut child.stderr);
        mem::take(&mut child.stdin);

        eprintln!("Calling wait");
        child.wait().await.unwrap();
        eprintln!("Waited; waiting for stdout");

        stdout_fut.await.unwrap();

        eprintln!("all good; this code is unreachable with tokio::net::unix::pipe");
    }

Describe the solution you'd like

Around this line

https://github.com/tokio-rs/tokio/blob/ab8d7b82a1252b41dc072f641befb6d2afcb3373/tokio/src/net/unix/pipe.rs#L24-L25

explain the difference.

Describe alternatives you've considered

None.

Additional context

Reproduces on Mac and Linux.

stepancheg avatar May 31 '25 02:05 stepancheg