Give special handling to the first stage's stdin to prevent a deadlock #19
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The pipe's
stdin
needs to be wrapped with something like anio.NopCloser()
to prevent the pipe from closing it.We used to use
io.NopCloser()
for this purpose, but it has a subtle problem. If the first stage is aCommand
, then we wants to set theexec.Cmd
'sStdin
to anio.Reader
corresponding top.stdin
. IfCmd.Stdin
is an*os.File
, then the file descriptor can be passed to the subcommand directly; there is no need for this process to create a pipe and copy the data into the input side of the pipe. But ifp.stdin
is not an*os.File
, then this optimization is prevented. And even worse, it also has the side effect that the goroutine that copies fromCmd.Stdin
into the pipe doesn't terminate until that fd is closed by the writing side.That isn't always what we want. Consider, for example, the following snippet, where the subcommand's stdin is set to the stdin of the enclosing Go program, but wrapped with
io.NopCloser
:In this case, we don't want the Go program to wait for
os.Stdin
to close (becausels
isn't even trying to read from its stdin). But it does:exec.Cmd
doesn't recognize thatCmd.Stdin
is an*os.File
, so it sets up a pipe and copies the data itself, and this goroutine doesn't terminate untilcmd.Stdin
(i.e., the Go program's own stdin) is closed. But if, for example, the Go program is run from an interactive shell session, that might never happen, in which case the program will fail to terminate, even afterls
exits.So instead, in this special case, wrap
p.stdin
in our ownnopCloser
, which behaves likeio.NopCloser
, except thatpipe.CommandStage
knows how to unwrap it before passing it toexec.Cmd
./cc @elhmn