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: } return Err(err) } <del> reader = Handle::new(handle); </del> <ins> ours = Handle::new(handle); </ins> break } <del> // Connect to the named pipe we just created in write-only mode (also // overlapped for async I/O below). </del> <ins> // Connect to the named pipe we just created. This handle is going to be // returned in `theirs`, so if `ours` is readable we want this to be // writable, otherwise if `ours` is writable we want this to be // readable. // // Additionally we don't enable overlapped mode on this because most // client processes aren't enabled to work with that. </ins> let mut opts = OpenOptions::new(); <del> opts.write(true); opts.read(false); </del> <ins> opts.write(ours_readable); opts.read(!ours_readable); </ins> opts.share_mode(0); <del> opts.attributes(c::FILE_FLAG_OVERLAPPED); let writer = File::open(Path::new(&name), &opts)?; let writer = AnonPipe { inner: writer.into_handle() }; </del> <ins> let theirs = File::open(Path::new(&name), &opts)?; let theirs = AnonPipe { inner: theirs.into_handle() }; </ins> <del> Ok((AnonPipe { inner: reader }, AnonPipe { inner: writer.into_handle() })) </del> <ins> Ok(Pipes { ours: AnonPipe { inner: ours }, theirs: AnonPipe { inner: theirs.into_handle() }, }) </ins> } }