Code change:
It looks like the use of overlapped I/O on stdio ports may cause some problems in Windows. When I try to shell out to using on the stderr port, the command runs successfully but hangs at the end. pointed me to this commit as the likely culprit: Here's the smallest test case I could figure out how to create: I wanted to try to come up with a minimal Node script to shell out to, as opposed to . Unfortunately, I'm not good enough at debugging npm and Node to figure out exactly what they're doing that causes the hang.
Ok I've tested this and it's a stable-to-stable regression that happened between 1.8 and 1.9. I believe the 1.9 release is indeed the first one with , the likely change which is a culprit. Tagging appropriately as such.
It looks like Rust is passing overlapped-mode pipe handles to the child process. The function returns two overlapped HANDLES. I'm not sure the overlapped mode can be changed after the fact -- maybe would work -- but I don't see it used anywhere, and I'd wonder about its use on the server handle. If the child inherits overlapped-mode handles, it would need to do async I/O to stdin/stdout/stderr. By convention, it does sync I/O using lpOverlapped==NULL, but that has undefined behavior if the handles are overlapped pipes. FWIW: the server and client ends of a named pipe aren't quite symmetric, but I'm guessing it doesn't matter whether the child process receives a server pipe or a client pipe? The interesting thing is that writing to a broken pipe returns , but reading from one returns . Apparently this is true for both ends. (I apparently wrote for this back in June'16.) I'd probably pass the client end to the child process, because it doesn't make sense that a program can call DisconnectNamedPipe on its stdio handles. (DisconnectNamedPipe doesn't work on client handles.) I made a Rust + C++ test case demonstrating how passing overlapped handles to the child breaks:
Fascinating! Thanks for the links That perfectly explains I think what's going on here. I think I can see the fix here, so I'll work on that pronto.
Yeah, I think the fix is easy -- use non-overlapped handles for the child but overlapped handles for the parent.

Related discussion:
text: inner: Handle, } <del> pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> { </del> <ins> pub struct Pipes { pub ours: AnonPipe, pub theirs: AnonPipe, } /// Although this looks similar to `anon_pipe` in the Unix module it's actually /// subtly different. Here we'll return two pipes in the `Pipes` return value, /// but one is intended for "us" where as the other is intended for "someone /// else". /// /// Currently the only use case for this function is pipes for stdio on /// processes in the standard library, so "ours" is the one that'll stay in our /// process whereas "theirs" will be inherited to a child. /// /// The ours/theirs pipes are *not* specifically readable or writable. Each /// one only supports a read or a write, but which is which depends on the /// boolean flag given. If `ours_readable` is true then `ours` is readable where /// `theirs` is writable. Conversely if `ours_readable` is false then `ours` is /// writable where `theirs` is readable. /// /// Also note that the `ours` pipe is always a handle opened up in overlapped /// mode. This means that technically speaking it should only ever be used /// with `OVERLAPPED` instances, but also works out ok if it's only ever used /// once at a time (which we do indeed guarantee). pub fn anon_pipe(ours_readable: bool) -> io::Result<Pipes> { </ins> // Note that we specifically do *not* use `CreatePipe` here because // unfortunately the anonymous pipes returned do not support overlapped <del> // operations. // // Instead, we create a "hopefully unique" name and create a named pipe // which has overlapped operations enabled. </del> <ins> // operations. Instead, we create a "hopefully unique" name and create a // named pipe which has overlapped operations enabled. </ins> // <del> // Once we do this, we connect do it as usual via `CreateFileW`, and then we // return those reader/writer halves. </del> <ins> // Once we do this, we connect do it as usual via `CreateFileW`, and then // we return those reader/writer halves. Note that the `ours` pipe return // value is always the named pipe, whereas `theirs` is just the normal file. // This should hopefully shield us from child processes which assume their // stdout is a named pipe, which would indeed be odd! </ins> unsafe { <del> let reader; </del> <ins> let ours; </ins> let mut name; let mut tries = 0; let mut reject_remote_clients_flag = c::PIPE_REJECT_REMOTE_CLIENTS;