From 28cf8fddd4c19e98fd0a6fcf0a6e7ea545521412 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 14 Jan 2015 01:33:05 +0200 Subject: [PATCH] Fix attach stream closing issues Fixes: #9860 Fixes: detach and attach tty mode We never actually need to close container `stdin` after `stdout/stderr` finishes. We only need to close the `stdin` goroutine. In some cases this also means closing `stdin` but that is already controlled by the goroutine itself. Signed-off-by: Tonis Tiigi --- daemon/attach.go | 3 +- .../docker_cli_attach_unix_test.go | 123 ++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 integration-cli/docker_cli_attach_unix_test.go diff --git a/daemon/attach.go b/daemon/attach.go index dc7ffa3071..881b021e17 100644 --- a/daemon/attach.go +++ b/daemon/attach.go @@ -179,9 +179,8 @@ func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, t } defer func() { // Make sure stdin gets closed - if stdinOnce && cStdin != nil { + if stdin != nil { stdin.Close() - cStdin.Close() } streamPipe.Close() wg.Done() diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go new file mode 100644 index 0000000000..451147d8ac --- /dev/null +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -0,0 +1,123 @@ +package main + +import ( + "os/exec" + "strings" + "testing" + "time" + + "github.com/kr/pty" +) + +// #9860 +func TestAttachClosedOnContainerStop(t *testing.T) { + defer deleteAllContainers() + + cmd := exec.Command(dockerBinary, "run", "-dti", "busybox", "sleep", "2") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to start container: %v (%v)", out, err) + } + + id := stripTrailingCharacters(out) + if err := waitRun(id); err != nil { + t.Fatal(err) + } + + done := make(chan struct{}) + + go func() { + defer close(done) + + _, tty, err := pty.Open() + if err != nil { + t.Fatalf("could not open pty: %v", err) + } + attachCmd := exec.Command(dockerBinary, "attach", id) + attachCmd.Stdin = tty + attachCmd.Stdout = tty + attachCmd.Stderr = tty + + if err := attachCmd.Run(); err != nil { + t.Fatalf("attach returned error %s", err) + } + }() + + waitCmd := exec.Command(dockerBinary, "wait", id) + if out, _, err = runCommandWithOutput(waitCmd); err != nil { + t.Fatalf("error thrown while waiting for container: %s, %v", out, err) + } + select { + case <-done: + case <-time.After(attachWait): + t.Fatal("timed out without attach returning") + } + + logDone("attach - return after container finished") +} + +func TestAttachAfterDetach(t *testing.T) { + defer deleteAllContainers() + + name := "detachtest" + + cpty, tty, err := pty.Open() + if err != nil { + t.Fatalf("Could not open pty: %v", err) + } + cmd := exec.Command(dockerBinary, "run", "-ti", "--name", name, "busybox") + cmd.Stdin = tty + cmd.Stdout = tty + cmd.Stderr = tty + + detached := make(chan struct{}) + go func() { + if err := cmd.Run(); err != nil { + t.Fatalf("attach returned error %s", err) + } + close(detached) + }() + + time.Sleep(500 * time.Millisecond) + cpty.Write([]byte{16}) + time.Sleep(100 * time.Millisecond) + cpty.Write([]byte{17}) + + <-detached + + cpty, tty, err = pty.Open() + if err != nil { + t.Fatalf("Could not open pty: %v", err) + } + + cmd = exec.Command(dockerBinary, "attach", name) + cmd.Stdin = tty + cmd.Stdout = tty + cmd.Stderr = tty + + go func() { + if err := cmd.Run(); err != nil { + t.Fatalf("attach returned error %s", err) + } + cpty.Close() // unblocks the reader in case of a failure + }() + + time.Sleep(500 * time.Millisecond) + cpty.Write([]byte("\n")) + time.Sleep(500 * time.Millisecond) + bytes := make([]byte, 10) + + n, err := cpty.Read(bytes) + + if err != nil { + t.Fatalf("prompt read failed: %v", err) + } + + if !strings.Contains(string(bytes[:n]), "/ #") { + t.Fatalf("failed to get a new prompt. got %s", string(bytes[:n])) + } + + cpty.Write([]byte("exit\n")) + + logDone("attach - reconnect after detaching") +}