Browse code

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 <tonistiigi@gmail.com>

Tonis Tiigi authored on 2015/01/14 08:33:05
Showing 2 changed files
... ...
@@ -179,9 +179,8 @@ func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
179 179
 		}
180 180
 		defer func() {
181 181
 			// Make sure stdin gets closed
182
-			if stdinOnce && cStdin != nil {
182
+			if stdin != nil {
183 183
 				stdin.Close()
184
-				cStdin.Close()
185 184
 			}
186 185
 			streamPipe.Close()
187 186
 			wg.Done()
188 187
new file mode 100644
... ...
@@ -0,0 +1,123 @@
0
+package main
1
+
2
+import (
3
+	"os/exec"
4
+	"strings"
5
+	"testing"
6
+	"time"
7
+
8
+	"github.com/kr/pty"
9
+)
10
+
11
+// #9860
12
+func TestAttachClosedOnContainerStop(t *testing.T) {
13
+	defer deleteAllContainers()
14
+
15
+	cmd := exec.Command(dockerBinary, "run", "-dti", "busybox", "sleep", "2")
16
+	out, _, err := runCommandWithOutput(cmd)
17
+	if err != nil {
18
+		t.Fatalf("failed to start container: %v (%v)", out, err)
19
+	}
20
+
21
+	id := stripTrailingCharacters(out)
22
+	if err := waitRun(id); err != nil {
23
+		t.Fatal(err)
24
+	}
25
+
26
+	done := make(chan struct{})
27
+
28
+	go func() {
29
+		defer close(done)
30
+
31
+		_, tty, err := pty.Open()
32
+		if err != nil {
33
+			t.Fatalf("could not open pty: %v", err)
34
+		}
35
+		attachCmd := exec.Command(dockerBinary, "attach", id)
36
+		attachCmd.Stdin = tty
37
+		attachCmd.Stdout = tty
38
+		attachCmd.Stderr = tty
39
+
40
+		if err := attachCmd.Run(); err != nil {
41
+			t.Fatalf("attach returned error %s", err)
42
+		}
43
+	}()
44
+
45
+	waitCmd := exec.Command(dockerBinary, "wait", id)
46
+	if out, _, err = runCommandWithOutput(waitCmd); err != nil {
47
+		t.Fatalf("error thrown while waiting for container: %s, %v", out, err)
48
+	}
49
+	select {
50
+	case <-done:
51
+	case <-time.After(attachWait):
52
+		t.Fatal("timed out without attach returning")
53
+	}
54
+
55
+	logDone("attach - return after container finished")
56
+}
57
+
58
+func TestAttachAfterDetach(t *testing.T) {
59
+	defer deleteAllContainers()
60
+
61
+	name := "detachtest"
62
+
63
+	cpty, tty, err := pty.Open()
64
+	if err != nil {
65
+		t.Fatalf("Could not open pty: %v", err)
66
+	}
67
+	cmd := exec.Command(dockerBinary, "run", "-ti", "--name", name, "busybox")
68
+	cmd.Stdin = tty
69
+	cmd.Stdout = tty
70
+	cmd.Stderr = tty
71
+
72
+	detached := make(chan struct{})
73
+	go func() {
74
+		if err := cmd.Run(); err != nil {
75
+			t.Fatalf("attach returned error %s", err)
76
+		}
77
+		close(detached)
78
+	}()
79
+
80
+	time.Sleep(500 * time.Millisecond)
81
+	cpty.Write([]byte{16})
82
+	time.Sleep(100 * time.Millisecond)
83
+	cpty.Write([]byte{17})
84
+
85
+	<-detached
86
+
87
+	cpty, tty, err = pty.Open()
88
+	if err != nil {
89
+		t.Fatalf("Could not open pty: %v", err)
90
+	}
91
+
92
+	cmd = exec.Command(dockerBinary, "attach", name)
93
+	cmd.Stdin = tty
94
+	cmd.Stdout = tty
95
+	cmd.Stderr = tty
96
+
97
+	go func() {
98
+		if err := cmd.Run(); err != nil {
99
+			t.Fatalf("attach returned error %s", err)
100
+		}
101
+		cpty.Close() // unblocks the reader in case of a failure
102
+	}()
103
+
104
+	time.Sleep(500 * time.Millisecond)
105
+	cpty.Write([]byte("\n"))
106
+	time.Sleep(500 * time.Millisecond)
107
+	bytes := make([]byte, 10)
108
+
109
+	n, err := cpty.Read(bytes)
110
+
111
+	if err != nil {
112
+		t.Fatalf("prompt read failed: %v", err)
113
+	}
114
+
115
+	if !strings.Contains(string(bytes[:n]), "/ #") {
116
+		t.Fatalf("failed to get a new prompt. got %s", string(bytes[:n]))
117
+	}
118
+
119
+	cpty.Write([]byte("exit\n"))
120
+
121
+	logDone("attach - reconnect after detaching")
122
+}