Skip to content

Commit 28cf8fd

Browse files
committed
Fix attach stream closing issues
Fixes: moby#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>
1 parent 5862422 commit 28cf8fd

File tree

2 files changed

+124
-2
lines changed

2 files changed

+124
-2
lines changed

daemon/attach.go

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

0 commit comments

Comments
 (0)