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>
| ... | ... |
@@ -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 |
+} |