diff --git a/api/client/hijack.go b/api/client/hijack.go index 14e3bc58f7..0cc57a2e74 100644 --- a/api/client/hijack.go +++ b/api/client/hijack.go @@ -16,7 +16,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/term" ) @@ -186,8 +185,6 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea started <- rwc } - var receiveStdout chan error - var oldState *term.State if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" { @@ -198,19 +195,15 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea defer term.RestoreTerminal(cli.inFd, oldState) } + receiveStdout := make(chan error, 1) if stdout != nil || stderr != nil { - receiveStdout = promise.Go(func() (err error) { + go func() { defer func() { if in != nil { if setRawTerminal && cli.isTerminalIn { term.RestoreTerminal(cli.inFd, oldState) } - // For some reason this Close call blocks on darwin.. - // As the client exists right after, simply discard the close - // until we find a better solution. - if runtime.GOOS != "darwin" { - in.Close() - } + in.Close() } }() @@ -221,11 +214,12 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea _, err = stdcopy.StdCopy(stdout, stderr, br) } logrus.Debugf("[hijack] End of stdout") - return err - }) + receiveStdout <- err + }() } - sendStdin := promise.Go(func() error { + stdinDone := make(chan struct{}) + go func() { if in != nil { io.Copy(rwc, in) logrus.Debugf("[hijack] End of stdin") @@ -238,22 +232,24 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea logrus.Debugf("Couldn't send EOF: %s", err) } } - // Discard errors due to pipe interruption - return nil - }) + close(stdinDone) + }() - if stdout != nil || stderr != nil { - if err := <-receiveStdout; err != nil { + select { + case err := <-receiveStdout: + if err != nil { logrus.Debugf("Error receiveStdout: %s", err) - return err + } + if cli.isTerminalIn { + return nil + } + case <-stdinDone: + if stdout != nil || stderr != nil { + if err := <-receiveStdout; err != nil { + logrus.Debugf("Error receiveStdout: %s", err) + } } } - if !cli.isTerminalIn { - if err := <-sendStdin; err != nil { - logrus.Debugf("Error sendStdin: %s", err) - return err - } - } return nil } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index f6485c2dec..f6fb99b46c 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3384,3 +3384,24 @@ func (s *DockerSuite) TestTwoContainersInNetHost(c *check.C) { dockerCmd(c, "stop", "first") dockerCmd(c, "stop", "second") } + +// #11957 - stdin with no tty does not exit if stdin is not closed even though container exited +func (s *DockerSuite) TestRunStdinBlockedAfterContainerExit(c *check.C) { + cmd := exec.Command(dockerBinary, "run", "-i", "--name=test", "busybox", "true") + in, err := cmd.StdinPipe() + c.Assert(err, check.IsNil) + defer in.Close() + c.Assert(cmd.Start(), check.IsNil) + + waitChan := make(chan error) + go func() { + waitChan <- cmd.Wait() + }() + + select { + case err := <-waitChan: + c.Assert(err, check.IsNil) + case <-time.After(3 * time.Second): + c.Fatal("timeout waiting for command to exit") + } +}