diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 3a16abff11..d63510b834 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "fmt" + "io" "io/ioutil" "net" "os" @@ -4467,3 +4468,32 @@ func (s *DockerSuite) TestRunStoppedLoggingDriverNoLeak(c *check.C) { // NGoroutines is not updated right away, so we need to wait before failing c.Assert(waitForGoroutines(nroutines), checker.IsNil) } + +// #28658 +func (s *DockerSuite) TestSlowStdinClosing(c *check.C) { + name := "testslowstdinclosing" + repeat := 3 // regression happened 50% of the time + for i := 0; i < repeat; i++ { + cmd := exec.Command(dockerBinary, "run", "--rm", "--name", name, "-i", "busybox", "cat") + cmd.Stdin = &delayedReader{} + done := make(chan error, 1) + go func() { + _, err := runCommand(cmd) + done <- err + }() + + select { + case <-time.After(15 * time.Second): + c.Fatal("running container timed out") // cleanup in teardown + case err := <-done: + c.Assert(err, checker.IsNil) + } + } +} + +type delayedReader struct{} + +func (s *delayedReader) Read([]byte) (int, error) { + time.Sleep(500 * time.Millisecond) + return 0, io.EOF +} diff --git a/libcontainerd/container_linux.go b/libcontainerd/container_linux.go index 3c587139ea..29c98de245 100644 --- a/libcontainerd/container_linux.go +++ b/libcontainerd/container_linux.go @@ -115,12 +115,16 @@ func (ctr *container) start(attachStdio StdioCallback) error { stdinOnce.Do(func() { // on error from attach we don't know if stdin was already closed err = stdin.Close() go func() { + select { + case <-ready: + case <-ctx.Done(): + } select { case <-ready: if err := ctr.sendCloseStdin(); err != nil { logrus.Warnf("failed to close stdin: %+v", err) } - case <-ctx.Done(): + default: } }() })