Browse code

Add detach event

If we attach to a running container and stream is closed afterwards, we
can never be sure if the container is stopped or detached. Adding a new
type of `detach` event can explicitly notify client that container is
detached, so client will know that there's no need to wait for its exit
code and it can move forward to next step now.

Signed-off-by: Zhang Wei <zhangwei555@huawei.com>

Zhang Wei authored on 2016/05/22 23:04:39
Showing 4 changed files
... ...
@@ -48,6 +48,21 @@ var (
48 48
 	errInvalidNetwork  = fmt.Errorf("invalid network settings while building port map info")
49 49
 )
50 50
 
51
+// AttachError represents errors of attach
52
+type AttachError interface {
53
+	IsDetached() bool
54
+}
55
+
56
+type detachError struct{}
57
+
58
+func (e detachError) IsDetached() bool {
59
+	return true
60
+}
61
+
62
+func (e detachError) Error() string {
63
+	return "detached from container"
64
+}
65
+
51 66
 // CommonContainer holds the fields for a container which are
52 67
 // applicable across all platforms supported by the daemon.
53 68
 type CommonContainer struct {
... ...
@@ -393,7 +408,6 @@ func AttachStreams(ctx context.Context, streamConfig *runconfig.StreamConfig, op
393 393
 			_, err = copyEscapable(cStdin, stdin, keys)
394 394
 		} else {
395 395
 			_, err = io.Copy(cStdin, stdin)
396
-
397 396
 		}
398 397
 		if err == io.ErrClosedPipe {
399 398
 			err = nil
... ...
@@ -492,10 +506,8 @@ func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64
492 492
 					break
493 493
 				}
494 494
 				if i == len(keys)-1 {
495
-					if err := src.Close(); err != nil {
496
-						return 0, err
497
-					}
498
-					return 0, nil
495
+					src.Close()
496
+					return 0, detachError{}
499 497
 				}
500 498
 				nr, er = src.Read(buf)
501 499
 			}
... ...
@@ -73,9 +73,9 @@ func (daemon *Daemon) ContainerAttachRaw(prefixOrName string, stdin io.ReadClose
73 73
 	return daemon.containerAttach(container, stdin, stdout, stderr, false, stream, nil)
74 74
 }
75 75
 
76
-func (daemon *Daemon) containerAttach(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error {
76
+func (daemon *Daemon) containerAttach(c *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error {
77 77
 	if logs {
78
-		logDriver, err := daemon.getLogger(container)
78
+		logDriver, err := daemon.getLogger(c)
79 79
 		if err != nil {
80 80
 			return err
81 81
 		}
... ...
@@ -105,7 +105,7 @@ func (daemon *Daemon) containerAttach(container *container.Container, stdin io.R
105 105
 		}
106 106
 	}
107 107
 
108
-	daemon.LogContainerEvent(container, "attach")
108
+	daemon.LogContainerEvent(c, "attach")
109 109
 
110 110
 	//stream
111 111
 	if stream {
... ...
@@ -119,11 +119,20 @@ func (daemon *Daemon) containerAttach(container *container.Container, stdin io.R
119 119
 			}()
120 120
 			stdinPipe = r
121 121
 		}
122
-		<-container.Attach(stdinPipe, stdout, stderr, keys)
122
+		err := <-c.Attach(stdinPipe, stdout, stderr, keys)
123
+		if err != nil {
124
+			e, ok := err.(container.AttachError)
125
+			if ok && e.IsDetached() {
126
+				daemon.LogContainerEvent(c, "detach")
127
+			} else {
128
+				logrus.Errorf("attach failed with error: %v", err)
129
+			}
130
+		}
131
+
123 132
 		// If we are in stdinonce mode, wait for the process to end
124 133
 		// otherwise, simply return
125
-		if container.Config.StdinOnce && !container.Config.Tty {
126
-			container.WaitStop(-1 * time.Second)
134
+		if c.Config.StdinOnce && !c.Config.Tty {
135
+			c.WaitStop(-1 * time.Second)
127 136
 		}
128 137
 	}
129 138
 	return nil
... ...
@@ -222,7 +222,11 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
222 222
 		return fmt.Errorf("context cancelled")
223 223
 	case err := <-attachErr:
224 224
 		if err != nil {
225
-			return fmt.Errorf("attach failed with error: %v", err)
225
+			e, ok := err.(container.AttachError)
226
+			if !ok || !e.IsDetached() {
227
+				return fmt.Errorf("attach failed with error: %v", err)
228
+			}
229
+			d.LogContainerEvent(c, "detach")
226 230
 		}
227 231
 	}
228 232
 	return nil
... ...
@@ -135,6 +135,11 @@ func (s *DockerSuite) TestRunAttachDetach(c *check.C) {
135 135
 
136 136
 	running := inspectField(c, name, "State.Running")
137 137
 	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
138
+
139
+	out, _ = dockerCmd(c, "events", "--since=0", "--until", daemonUnixTime(c), "-f", "container="+name)
140
+	// attach and detach event should be monitored
141
+	c.Assert(out, checker.Contains, "attach")
142
+	c.Assert(out, checker.Contains, "detach")
138 143
 }
139 144
 
140 145
 // TestRunDetach checks attaching and detaching with the escape sequence specified via flags.