tokio
tokio copied to clipboard
Difference in behavior between Stdio::piped() and tokio::net::unix::pipe
trafficstars
Is your feature request related to a problem? Please describe.
Repro code does this:
- create a process
sh -c 'sleep 1000000000'(exec sleep 1reproduces it too) - pipes stdout
- kills the process (but does not kill child
sleepprocess) - 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.