Browse code

Merge pull request #20589 from coolljt0725/fix_restore_terminal

cli: move setRawTerminal and restoreTerminal to holdHijackedConnection

Alexander Morozov authored on 2016/03/25 14:42:24
Showing 5 changed files
... ...
@@ -71,12 +71,6 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
71 71
 		return err
72 72
 	}
73 73
 	defer resp.Close()
74
-	if in != nil && c.Config.Tty {
75
-		if err := cli.setRawTerminal(); err != nil {
76
-			return err
77
-		}
78
-		defer cli.restoreTerminal(in)
79
-	}
80 74
 
81 75
 	if c.Config.Tty && cli.isTerminalOut {
82 76
 		height, width := cli.getTtySize()
... ...
@@ -92,8 +86,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
92 92
 			logrus.Debugf("Error monitoring TTY size: %s", err)
93 93
 		}
94 94
 	}
95
-
96
-	if err := cli.holdHijackedConnection(c.Config.Tty, in, cli.out, cli.err, resp); err != nil {
95
+	if err := cli.holdHijackedConnection(context.Background(), c.Config.Tty, in, cli.out, cli.err, resp); err != nil {
97 96
 		return err
98 97
 	}
99 98
 
... ...
@@ -89,14 +89,8 @@ func (cli *DockerCli) CmdExec(args ...string) error {
89 89
 		return err
90 90
 	}
91 91
 	defer resp.Close()
92
-	if in != nil && execConfig.Tty {
93
-		if err := cli.setRawTerminal(); err != nil {
94
-			return err
95
-		}
96
-		defer cli.restoreTerminal(in)
97
-	}
98 92
 	errCh = promise.Go(func() error {
99
-		return cli.holdHijackedConnection(execConfig.Tty, in, out, stderr, resp)
93
+		return cli.holdHijackedConnection(context.Background(), execConfig.Tty, in, out, stderr, resp)
100 94
 	})
101 95
 
102 96
 	if execConfig.Tty && cli.isTerminalIn {
... ...
@@ -2,23 +2,48 @@ package client
2 2
 
3 3
 import (
4 4
 	"io"
5
+	"sync"
6
+
7
+	"golang.org/x/net/context"
5 8
 
6 9
 	"github.com/Sirupsen/logrus"
7 10
 	"github.com/docker/docker/pkg/stdcopy"
8 11
 	"github.com/docker/engine-api/types"
9 12
 )
10 13
 
11
-func (cli *DockerCli) holdHijackedConnection(tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
12
-	var err error
14
+func (cli *DockerCli) holdHijackedConnection(ctx context.Context, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
15
+	var (
16
+		err         error
17
+		restoreOnce sync.Once
18
+	)
19
+	if inputStream != nil && tty {
20
+		if err := cli.setRawTerminal(); err != nil {
21
+			return err
22
+		}
23
+		defer func() {
24
+			restoreOnce.Do(func() {
25
+				cli.restoreTerminal(inputStream)
26
+			})
27
+		}()
28
+	}
29
+
13 30
 	receiveStdout := make(chan error, 1)
14 31
 	if outputStream != nil || errorStream != nil {
15 32
 		go func() {
16 33
 			// When TTY is ON, use regular copy
17 34
 			if tty && outputStream != nil {
18 35
 				_, err = io.Copy(outputStream, resp.Reader)
36
+				// we should restore the terminal as soon as possible once connection end
37
+				// so any following print messages will be in normal type.
38
+				if inputStream != nil {
39
+					restoreOnce.Do(func() {
40
+						cli.restoreTerminal(inputStream)
41
+					})
42
+				}
19 43
 			} else {
20 44
 				_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
21 45
 			}
46
+
22 47
 			logrus.Debugf("[hijack] End of stdout")
23 48
 			receiveStdout <- err
24 49
 		}()
... ...
@@ -28,6 +53,13 @@ func (cli *DockerCli) holdHijackedConnection(tty bool, inputStream io.ReadCloser
28 28
 	go func() {
29 29
 		if inputStream != nil {
30 30
 			io.Copy(resp.Conn, inputStream)
31
+			// we should restore the terminal as soon as possible once connection end
32
+			// so any following print messages will be in normal type.
33
+			if tty {
34
+				restoreOnce.Do(func() {
35
+					cli.restoreTerminal(inputStream)
36
+				})
37
+			}
31 38
 			logrus.Debugf("[hijack] End of stdin")
32 39
 		}
33 40
 
... ...
@@ -45,11 +77,16 @@ func (cli *DockerCli) holdHijackedConnection(tty bool, inputStream io.ReadCloser
45 45
 		}
46 46
 	case <-stdinDone:
47 47
 		if outputStream != nil || errorStream != nil {
48
-			if err := <-receiveStdout; err != nil {
49
-				logrus.Debugf("Error receiveStdout: %s", err)
50
-				return err
48
+			select {
49
+			case err := <-receiveStdout:
50
+				if err != nil {
51
+					logrus.Debugf("Error receiveStdout: %s", err)
52
+					return err
53
+				}
54
+			case <-ctx.Done():
51 55
 			}
52 56
 		}
57
+	case <-ctx.Done():
53 58
 	}
54 59
 
55 60
 	return nil
... ...
@@ -158,6 +158,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
158 158
 	var (
159 159
 		waitDisplayID chan struct{}
160 160
 		errCh         chan error
161
+		cancelFun     context.CancelFunc
162
+		ctx           context.Context
161 163
 	)
162 164
 	if !config.AttachStdout && !config.AttachStderr {
163 165
 		// Make this asynchronous to allow the client to write to stdin before having to read the ID
... ...
@@ -170,8 +172,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
170 170
 	if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
171 171
 		return ErrConflictRestartPolicyAndAutoRemove
172 172
 	}
173
-
174
-	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
173
+	attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
174
+	if attach {
175 175
 		var (
176 176
 			out, stderr io.Writer
177 177
 			in          io.ReadCloser
... ...
@@ -207,14 +209,9 @@ func (cli *DockerCli) CmdRun(args ...string) error {
207 207
 		if err != nil {
208 208
 			return err
209 209
 		}
210
-		if in != nil && config.Tty {
211
-			if err := cli.setRawTerminal(); err != nil {
212
-				return err
213
-			}
214
-			defer cli.restoreTerminal(in)
215
-		}
210
+		ctx, cancelFun = context.WithCancel(context.Background())
216 211
 		errCh = promise.Go(func() error {
217
-			return cli.holdHijackedConnection(config.Tty, in, out, stderr, resp)
212
+			return cli.holdHijackedConnection(ctx, config.Tty, in, out, stderr, resp)
218 213
 		})
219 214
 	}
220 215
 
... ...
@@ -228,6 +225,14 @@ func (cli *DockerCli) CmdRun(args ...string) error {
228 228
 
229 229
 	//start the container
230 230
 	if err := cli.client.ContainerStart(context.Background(), createResponse.ID); err != nil {
231
+		// If we have holdHijackedConnection, we should notify
232
+		// holdHijackedConnection we are going to exit and wait
233
+		// to avoid the terminal are not restored.
234
+		if attach {
235
+			cancelFun()
236
+			<-errCh
237
+		}
238
+
231 239
 		cmd.ReportError(err.Error(), false)
232 240
 		return runStartContainerErr(err)
233 241
 	}
... ...
@@ -89,6 +89,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
89 89
 		}
90 90
 
91 91
 		var in io.ReadCloser
92
+
92 93
 		if options.Stdin {
93 94
 			in = cli.in
94 95
 		}
... ...
@@ -98,19 +99,15 @@ func (cli *DockerCli) CmdStart(args ...string) error {
98 98
 			return err
99 99
 		}
100 100
 		defer resp.Close()
101
-		if in != nil && c.Config.Tty {
102
-			if err := cli.setRawTerminal(); err != nil {
103
-				return err
104
-			}
105
-			defer cli.restoreTerminal(in)
106
-		}
107
-
101
+		ctx, cancelFun := context.WithCancel(context.Background())
108 102
 		cErr := promise.Go(func() error {
109
-			return cli.holdHijackedConnection(c.Config.Tty, in, cli.out, cli.err, resp)
103
+			return cli.holdHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp)
110 104
 		})
111 105
 
112 106
 		// 3. Start the container.
113 107
 		if err := cli.client.ContainerStart(context.Background(), containerID); err != nil {
108
+			cancelFun()
109
+			<-cErr
114 110
 			return err
115 111
 		}
116 112