Browse code

Merge pull request #32237 from jlhawn/update_container_wait

Update Container Wait

Sebastiaan van Stijn authored on 2017/05/17 09:39:52
Showing 31 changed files
... ...
@@ -2,7 +2,6 @@ package container
2 2
 
3 3
 import (
4 4
 	"io"
5
-	"time"
6 5
 
7 6
 	"golang.org/x/net/context"
8 7
 
... ...
@@ -10,6 +9,7 @@ import (
10 10
 	"github.com/docker/docker/api/types/backend"
11 11
 	"github.com/docker/docker/api/types/container"
12 12
 	"github.com/docker/docker/api/types/filters"
13
+	containerpkg "github.com/docker/docker/container"
13 14
 	"github.com/docker/docker/pkg/archive"
14 15
 )
15 16
 
... ...
@@ -44,7 +44,7 @@ type stateBackend interface {
44 44
 	ContainerStop(name string, seconds *int) error
45 45
 	ContainerUnpause(name string) error
46 46
 	ContainerUpdate(name string, hostConfig *container.HostConfig) (container.ContainerUpdateOKBody, error)
47
-	ContainerWait(name string, timeout time.Duration) (int, error)
47
+	ContainerWait(ctx context.Context, name string, condition containerpkg.WaitCondition) (<-chan containerpkg.StateStatus, error)
48 48
 }
49 49
 
50 50
 // monitorBackend includes functions to implement to provide containers monitoring functionality.
... ...
@@ -59,7 +59,7 @@ func (r *containerRouter) initRoutes() {
59 59
 		router.NewPostRoute("/containers/{name:.*}/restart", r.postContainersRestart),
60 60
 		router.NewPostRoute("/containers/{name:.*}/start", r.postContainersStart),
61 61
 		router.NewPostRoute("/containers/{name:.*}/stop", r.postContainersStop),
62
-		router.NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait),
62
+		router.NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait, router.WithCancel),
63 63
 		router.NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize),
64 64
 		router.NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach),
65 65
 		router.NewPostRoute("/containers/{name:.*}/copy", r.postContainersCopy), // Deprecated since 1.8, Errors out since 1.12
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"net/http"
8 8
 	"strconv"
9 9
 	"syscall"
10
-	"time"
11 10
 
12 11
 	"github.com/Sirupsen/logrus"
13 12
 	"github.com/docker/docker/api"
... ...
@@ -17,6 +16,7 @@ import (
17 17
 	"github.com/docker/docker/api/types/container"
18 18
 	"github.com/docker/docker/api/types/filters"
19 19
 	"github.com/docker/docker/api/types/versions"
20
+	containerpkg "github.com/docker/docker/container"
20 21
 	"github.com/docker/docker/pkg/ioutils"
21 22
 	"github.com/docker/docker/pkg/signal"
22 23
 	"golang.org/x/net/context"
... ...
@@ -284,13 +284,48 @@ func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.Resp
284 284
 }
285 285
 
286 286
 func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
287
-	status, err := s.backend.ContainerWait(vars["name"], -1*time.Second)
287
+	// Behavior changed in version 1.30 to handle wait condition and to
288
+	// return headers immediately.
289
+	version := httputils.VersionFromContext(ctx)
290
+	legacyBehavior := versions.LessThan(version, "1.30")
291
+
292
+	// The wait condition defaults to "not-running".
293
+	waitCondition := containerpkg.WaitConditionNotRunning
294
+	if !legacyBehavior {
295
+		if err := httputils.ParseForm(r); err != nil {
296
+			return err
297
+		}
298
+		switch container.WaitCondition(r.Form.Get("condition")) {
299
+		case container.WaitConditionNextExit:
300
+			waitCondition = containerpkg.WaitConditionNextExit
301
+		case container.WaitConditionRemoved:
302
+			waitCondition = containerpkg.WaitConditionRemoved
303
+		}
304
+	}
305
+
306
+	// Note: the context should get canceled if the client closes the
307
+	// connection since this handler has been wrapped by the
308
+	// router.WithCancel() wrapper.
309
+	waitC, err := s.backend.ContainerWait(ctx, vars["name"], waitCondition)
288 310
 	if err != nil {
289 311
 		return err
290 312
 	}
291 313
 
292
-	return httputils.WriteJSON(w, http.StatusOK, &container.ContainerWaitOKBody{
293
-		StatusCode: int64(status),
314
+	w.Header().Set("Content-Type", "application/json")
315
+
316
+	if !legacyBehavior {
317
+		// Write response header immediately.
318
+		w.WriteHeader(http.StatusOK)
319
+		if flusher, ok := w.(http.Flusher); ok {
320
+			flusher.Flush()
321
+		}
322
+	}
323
+
324
+	// Block on the result of the wait operation.
325
+	status := <-waitC
326
+
327
+	return json.NewEncoder(w).Encode(&container.ContainerWaitOKBody{
328
+		StatusCode: int64(status.ExitCode()),
294 329
 	})
295 330
 }
296 331
 
... ...
@@ -4270,6 +4270,11 @@ paths:
4270 4270
           required: true
4271 4271
           description: "ID or name of the container"
4272 4272
           type: "string"
4273
+        - name: "condition"
4274
+          in: "query"
4275
+          description: "Wait until a container state reaches the given condition, either 'not-running' (default), 'next-exit', or 'removed'."
4276
+          type: "string"
4277
+          default: "not-running"
4273 4278
       tags: ["Container"]
4274 4279
   /containers/{id}:
4275 4280
     delete:
4276 4281
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+package container
1
+
2
+// WaitCondition is a type used to specify a container state for which
3
+// to wait.
4
+type WaitCondition string
5
+
6
+// Possible WaitCondition Values.
7
+//
8
+// WaitConditionNotRunning (default) is used to wait for any of the non-running
9
+// states: "created", "exited", "dead", "removing", or "removed".
10
+//
11
+// WaitConditionNextExit is used to wait for the next time the state changes
12
+// to a non-running state. If the state is currently "created" or "exited",
13
+// this would cause Wait() to block until either the container runs and exits
14
+// or is removed.
15
+//
16
+// WaitConditionRemoved is used to wait for the container to be removed.
17
+const (
18
+	WaitConditionNotRunning WaitCondition = "not-running"
19
+	WaitConditionNextExit   WaitCondition = "next-exit"
20
+	WaitConditionRemoved    WaitCondition = "removed"
21
+)
... ...
@@ -6,12 +6,13 @@ package builder
6 6
 
7 7
 import (
8 8
 	"io"
9
-	"time"
9
+
10
+	"golang.org/x/net/context"
10 11
 
11 12
 	"github.com/docker/docker/api/types"
12 13
 	"github.com/docker/docker/api/types/backend"
13 14
 	"github.com/docker/docker/api/types/container"
14
-	"golang.org/x/net/context"
15
+	containerpkg "github.com/docker/docker/container"
15 16
 )
16 17
 
17 18
 const (
... ...
@@ -49,7 +50,7 @@ type Backend interface {
49 49
 	// ContainerStart starts a new container
50 50
 	ContainerStart(containerID string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error
51 51
 	// ContainerWait stops processing until the given container is stopped.
52
-	ContainerWait(containerID string, timeout time.Duration) (int, error)
52
+	ContainerWait(ctx context.Context, name string, condition containerpkg.WaitCondition) (<-chan containerpkg.StateStatus, error)
53 53
 	// ContainerCreateWorkdir creates the workdir
54 54
 	ContainerCreateWorkdir(containerID string) error
55 55
 
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	"github.com/docker/docker/api/types/container"
24 24
 	"github.com/docker/docker/builder"
25 25
 	"github.com/docker/docker/builder/remotecontext"
26
+	containerpkg "github.com/docker/docker/container"
26 27
 	"github.com/docker/docker/pkg/httputils"
27 28
 	"github.com/docker/docker/pkg/ioutils"
28 29
 	"github.com/docker/docker/pkg/jsonmessage"
... ...
@@ -596,16 +597,25 @@ func (b *Builder) run(cID string, cmd []string) (err error) {
596 596
 		return err
597 597
 	}
598 598
 
599
-	if ret, _ := b.docker.ContainerWait(cID, -1); ret != 0 {
599
+	waitC, err := b.docker.ContainerWait(b.clientCtx, cID, containerpkg.WaitConditionNotRunning)
600
+	if err != nil {
601
+		// Unable to begin waiting for container.
602
+		close(finished)
603
+		if cancelErr := <-cancelErrCh; cancelErr != nil {
604
+			logrus.Debugf("Build cancelled (%v) and unable to begin ContainerWait: %d", cancelErr, err)
605
+		}
606
+		return err
607
+	}
608
+
609
+	if status := <-waitC; status.ExitCode() != 0 {
600 610
 		close(finished)
601 611
 		if cancelErr := <-cancelErrCh; cancelErr != nil {
602
-			logrus.Debugf("Build cancelled (%v) and got a non-zero code from ContainerWait: %d",
603
-				cancelErr, ret)
612
+			logrus.Debugf("Build cancelled (%v) and got a non-zero code from ContainerWait: %d", cancelErr, status.ExitCode())
604 613
 		}
605 614
 		// TODO: change error type, because jsonmessage.JSONError assumes HTTP
606 615
 		return &jsonmessage.JSONError{
607
-			Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", strings.Join(cmd, " "), ret),
608
-			Code:    ret,
616
+			Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", strings.Join(cmd, " "), status.ExitCode()),
617
+			Code:    status.ExitCode(),
609 618
 		}
610 619
 	}
611 620
 	close(finished)
... ...
@@ -2,13 +2,13 @@ package dockerfile
2 2
 
3 3
 import (
4 4
 	"io"
5
-	"time"
6 5
 
7 6
 	"github.com/docker/distribution/reference"
8 7
 	"github.com/docker/docker/api/types"
9 8
 	"github.com/docker/docker/api/types/backend"
10 9
 	"github.com/docker/docker/api/types/container"
11 10
 	"github.com/docker/docker/builder"
11
+	containerpkg "github.com/docker/docker/container"
12 12
 	"github.com/docker/docker/image"
13 13
 	"golang.org/x/net/context"
14 14
 )
... ...
@@ -54,8 +54,8 @@ func (m *MockBackend) ContainerStart(containerID string, hostConfig *container.H
54 54
 	return nil
55 55
 }
56 56
 
57
-func (m *MockBackend) ContainerWait(containerID string, timeout time.Duration) (int, error) {
58
-	return 0, nil
57
+func (m *MockBackend) ContainerWait(ctx context.Context, containerID string, condition containerpkg.WaitCondition) (<-chan containerpkg.StateStatus, error) {
58
+	return nil, nil
59 59
 }
60 60
 
61 61
 func (m *MockBackend) ContainerCreateWorkdir(containerID string) error {
... ...
@@ -2,25 +2,83 @@ package client
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
+	"net/url"
5 6
 
6 7
 	"golang.org/x/net/context"
7 8
 
8 9
 	"github.com/docker/docker/api/types/container"
10
+	"github.com/docker/docker/api/types/versions"
9 11
 )
10 12
 
11
-// ContainerWait pauses execution until a container exits.
12
-// It returns the API status code as response of its readiness.
13
-func (cli *Client) ContainerWait(ctx context.Context, containerID string) (int64, error) {
14
-	resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil)
15
-	if err != nil {
16
-		return -1, err
13
+// ContainerWait waits until the specified continer is in a certain state
14
+// indicated by the given condition, either "not-running" (default),
15
+// "next-exit", or "removed".
16
+//
17
+// If this client's API version is beforer 1.30, condition is ignored and
18
+// ContainerWait will return immediately with the two channels, as the server
19
+// will wait as if the condition were "not-running".
20
+//
21
+// If this client's API version is at least 1.30, ContainerWait blocks until
22
+// the request has been acknowledged by the server (with a response header),
23
+// then returns two channels on which the caller can wait for the exit status
24
+// of the container or an error if there was a problem either beginning the
25
+// wait request or in getting the response. This allows the caller to
26
+// sychronize ContainerWait with other calls, such as specifying a
27
+// "next-exit" condition before issuing a ContainerStart request.
28
+func (cli *Client) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) {
29
+	if versions.LessThan(cli.ClientVersion(), "1.30") {
30
+		return cli.legacyContainerWait(ctx, containerID)
17 31
 	}
18
-	defer ensureReaderClosed(resp)
19 32
 
20
-	var res container.ContainerWaitOKBody
21
-	if err := json.NewDecoder(resp.body).Decode(&res); err != nil {
22
-		return -1, err
33
+	resultC := make(chan container.ContainerWaitOKBody)
34
+	errC := make(chan error)
35
+
36
+	query := url.Values{}
37
+	query.Set("condition", string(condition))
38
+
39
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", query, nil, nil)
40
+	if err != nil {
41
+		defer ensureReaderClosed(resp)
42
+		errC <- err
43
+		return resultC, errC
23 44
 	}
24 45
 
25
-	return res.StatusCode, nil
46
+	go func() {
47
+		defer ensureReaderClosed(resp)
48
+		var res container.ContainerWaitOKBody
49
+		if err := json.NewDecoder(resp.body).Decode(&res); err != nil {
50
+			errC <- err
51
+			return
52
+		}
53
+
54
+		resultC <- res
55
+	}()
56
+
57
+	return resultC, errC
58
+}
59
+
60
+// legacyContainerWait returns immediately and doesn't have an option to wait
61
+// until the container is removed.
62
+func (cli *Client) legacyContainerWait(ctx context.Context, containerID string) (<-chan container.ContainerWaitOKBody, <-chan error) {
63
+	resultC := make(chan container.ContainerWaitOKBody)
64
+	errC := make(chan error)
65
+
66
+	go func() {
67
+		resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil)
68
+		if err != nil {
69
+			errC <- err
70
+			return
71
+		}
72
+		defer ensureReaderClosed(resp)
73
+
74
+		var res container.ContainerWaitOKBody
75
+		if err := json.NewDecoder(resp.body).Decode(&res); err != nil {
76
+			errC <- err
77
+			return
78
+		}
79
+
80
+		resultC <- res
81
+	}()
82
+
83
+	return resultC, errC
26 84
 }
... ...
@@ -20,12 +20,14 @@ func TestContainerWaitError(t *testing.T) {
20 20
 	client := &Client{
21 21
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
22 22
 	}
23
-	code, err := client.ContainerWait(context.Background(), "nothing")
24
-	if err == nil || err.Error() != "Error response from daemon: Server error" {
25
-		t.Fatalf("expected a Server Error, got %v", err)
26
-	}
27
-	if code != -1 {
28
-		t.Fatalf("expected a status code equal to '-1', got %d", code)
23
+	resultC, errC := client.ContainerWait(context.Background(), "nothing", "")
24
+	select {
25
+	case result := <-resultC:
26
+		t.Fatalf("expected to not get a wait result, got %d", result.StatusCode)
27
+	case err := <-errC:
28
+		if err.Error() != "Error response from daemon: Server error" {
29
+			t.Fatalf("expected a Server Error, got %v", err)
30
+		}
29 31
 	}
30 32
 }
31 33
 
... ...
@@ -49,12 +51,14 @@ func TestContainerWait(t *testing.T) {
49 49
 		}),
50 50
 	}
51 51
 
52
-	code, err := client.ContainerWait(context.Background(), "container_id")
53
-	if err != nil {
52
+	resultC, errC := client.ContainerWait(context.Background(), "container_id", "")
53
+	select {
54
+	case err := <-errC:
54 55
 		t.Fatal(err)
55
-	}
56
-	if code != 15 {
57
-		t.Fatalf("expected a status code equal to '15', got %d", code)
56
+	case result := <-resultC:
57
+		if result.StatusCode != 15 {
58
+			t.Fatalf("expected a status code equal to '15', got %d", result.StatusCode)
59
+		}
58 60
 	}
59 61
 }
60 62
 
... ...
@@ -63,8 +67,8 @@ func ExampleClient_ContainerWait_withTimeout() {
63 63
 	defer cancel()
64 64
 
65 65
 	client, _ := NewEnvClient()
66
-	_, err := client.ContainerWait(ctx, "container_id")
67
-	if err != nil {
66
+	_, errC := client.ContainerWait(ctx, "container_id", "")
67
+	if err := <-errC; err != nil {
68 68
 		log.Fatal(err)
69 69
 	}
70 70
 }
... ...
@@ -64,7 +64,7 @@ type ContainerAPIClient interface {
64 64
 	ContainerTop(ctx context.Context, container string, arguments []string) (container.ContainerTopOKBody, error)
65 65
 	ContainerUnpause(ctx context.Context, container string) error
66 66
 	ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.ContainerUpdateOKBody, error)
67
-	ContainerWait(ctx context.Context, container string) (int64, error)
67
+	ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error)
68 68
 	CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
69 69
 	CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error
70 70
 	ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error)
... ...
@@ -1,6 +1,7 @@
1 1
 package container
2 2
 
3 3
 import (
4
+	"errors"
4 5
 	"fmt"
5 6
 	"sync"
6 7
 	"time"
... ...
@@ -29,40 +30,37 @@ type State struct {
29 29
 	ErrorMsg          string `json:"Error"` // contains last known error when starting the container
30 30
 	StartedAt         time.Time
31 31
 	FinishedAt        time.Time
32
-	waitChan          chan struct{}
33 32
 	Health            *Health
33
+
34
+	waitStop   chan struct{}
35
+	waitRemove chan struct{}
34 36
 }
35 37
 
36
-// StateStatus is used to return an error type implementing both
37
-// exec.ExitCode and error.
38
+// StateStatus is used to return container wait results.
39
+// Implements exec.ExitCode interface.
38 40
 // This type is needed as State include a sync.Mutex field which make
39 41
 // copying it unsafe.
40 42
 type StateStatus struct {
41 43
 	exitCode int
42
-	error    string
43
-}
44
-
45
-func newStateStatus(ec int, err string) *StateStatus {
46
-	return &StateStatus{
47
-		exitCode: ec,
48
-		error:    err,
49
-	}
44
+	err      error
50 45
 }
51 46
 
52 47
 // ExitCode returns current exitcode for the state.
53
-func (ss *StateStatus) ExitCode() int {
54
-	return ss.exitCode
48
+func (s StateStatus) ExitCode() int {
49
+	return s.exitCode
55 50
 }
56 51
 
57
-// Error returns current error for the state.
58
-func (ss *StateStatus) Error() string {
59
-	return ss.error
52
+// Err returns current error for the state. Returns nil if the container had
53
+// exited on its own.
54
+func (s StateStatus) Err() error {
55
+	return s.err
60 56
 }
61 57
 
62 58
 // NewState creates a default state object with a fresh channel for state changes.
63 59
 func NewState() *State {
64 60
 	return &State{
65
-		waitChan: make(chan struct{}),
61
+		waitStop:   make(chan struct{}),
62
+		waitRemove: make(chan struct{}),
66 63
 	}
67 64
 }
68 65
 
... ...
@@ -160,64 +158,89 @@ func IsValidStateString(s string) bool {
160 160
 	return true
161 161
 }
162 162
 
163
-func wait(waitChan <-chan struct{}, timeout time.Duration) error {
164
-	if timeout < 0 {
165
-		<-waitChan
166
-		return nil
167
-	}
168
-	select {
169
-	case <-time.After(timeout):
170
-		return fmt.Errorf("Timed out: %v", timeout)
171
-	case <-waitChan:
172
-		return nil
173
-	}
174
-}
163
+// WaitCondition is an enum type for different states to wait for.
164
+type WaitCondition int
165
+
166
+// Possible WaitCondition Values.
167
+//
168
+// WaitConditionNotRunning (default) is used to wait for any of the non-running
169
+// states: "created", "exited", "dead", "removing", or "removed".
170
+//
171
+// WaitConditionNextExit is used to wait for the next time the state changes
172
+// to a non-running state. If the state is currently "created" or "exited",
173
+// this would cause Wait() to block until either the container runs and exits
174
+// or is removed.
175
+//
176
+// WaitConditionRemoved is used to wait for the container to be removed.
177
+const (
178
+	WaitConditionNotRunning WaitCondition = iota
179
+	WaitConditionNextExit
180
+	WaitConditionRemoved
181
+)
175 182
 
176
-// WaitStop waits until state is stopped. If state already stopped it returns
177
-// immediately. If you want wait forever you must supply negative timeout.
178
-// Returns exit code, that was passed to SetStopped
179
-func (s *State) WaitStop(timeout time.Duration) (int, error) {
180
-	ctx := context.Background()
181
-	if timeout >= 0 {
182
-		var cancel func()
183
-		ctx, cancel = context.WithTimeout(ctx, timeout)
184
-		defer cancel()
185
-	}
186
-	if err := s.WaitWithContext(ctx); err != nil {
187
-		if status, ok := err.(*StateStatus); ok {
188
-			return status.ExitCode(), nil
183
+// Wait waits until the continer is in a certain state indicated by the given
184
+// condition. A context must be used for cancelling the request, controlling
185
+// timeouts, and avoiding goroutine leaks. Wait must be called without holding
186
+// the state lock. Returns a channel from which the caller will receive the
187
+// result. If the container exited on its own, the result's Err() method will
188
+// be nil and its ExitCode() method will return the conatiners exit code,
189
+// otherwise, the results Err() method will return an error indicating why the
190
+// wait operation failed.
191
+func (s *State) Wait(ctx context.Context, condition WaitCondition) <-chan StateStatus {
192
+	s.Lock()
193
+	defer s.Unlock()
194
+
195
+	if condition == WaitConditionNotRunning && !s.Running {
196
+		// Buffer so we can put it in the channel now.
197
+		resultC := make(chan StateStatus, 1)
198
+
199
+		// Send the current status.
200
+		resultC <- StateStatus{
201
+			exitCode: s.ExitCode(),
202
+			err:      s.Err(),
189 203
 		}
190
-		return -1, err
204
+
205
+		return resultC
191 206
 	}
192
-	return 0, nil
193
-}
194 207
 
195
-// WaitWithContext waits for the container to stop. Optional context can be
196
-// passed for canceling the request.
197
-func (s *State) WaitWithContext(ctx context.Context) error {
198
-	s.Lock()
199
-	if !s.Running {
200
-		state := newStateStatus(s.ExitCode(), s.Error())
201
-		defer s.Unlock()
202
-		if state.ExitCode() == 0 {
203
-			return nil
204
-		}
205
-		return state
208
+	// If we are waiting only for removal, the waitStop channel should
209
+	// remain nil and block forever.
210
+	var waitStop chan struct{}
211
+	if condition < WaitConditionRemoved {
212
+		waitStop = s.waitStop
206 213
 	}
207
-	waitChan := s.waitChan
208
-	s.Unlock()
209
-	select {
210
-	case <-waitChan:
214
+
215
+	// Always wait for removal, just in case the container gets removed
216
+	// while it is still in a "created" state, in which case it is never
217
+	// actually stopped.
218
+	waitRemove := s.waitRemove
219
+
220
+	resultC := make(chan StateStatus)
221
+
222
+	go func() {
223
+		select {
224
+		case <-ctx.Done():
225
+			// Context timeout or cancellation.
226
+			resultC <- StateStatus{
227
+				exitCode: -1,
228
+				err:      ctx.Err(),
229
+			}
230
+			return
231
+		case <-waitStop:
232
+		case <-waitRemove:
233
+		}
234
+
211 235
 		s.Lock()
212
-		state := newStateStatus(s.ExitCode(), s.Error())
213
-		s.Unlock()
214
-		if state.ExitCode() == 0 {
215
-			return nil
236
+		result := StateStatus{
237
+			exitCode: s.ExitCode(),
238
+			err:      s.Err(),
216 239
 		}
217
-		return state
218
-	case <-ctx.Done():
219
-		return ctx.Err()
220
-	}
240
+		s.Unlock()
241
+
242
+		resultC <- result
243
+	}()
244
+
245
+	return resultC
221 246
 }
222 247
 
223 248
 // IsRunning returns whether the running flag is set. Used by Container to check whether a container is running.
... ...
@@ -268,8 +291,8 @@ func (s *State) SetStopped(exitStatus *ExitStatus) {
268 268
 	s.Pid = 0
269 269
 	s.FinishedAt = time.Now().UTC()
270 270
 	s.setFromExitStatus(exitStatus)
271
-	close(s.waitChan) // fire waiters for stop
272
-	s.waitChan = make(chan struct{})
271
+	close(s.waitStop) // Fire waiters for stop
272
+	s.waitStop = make(chan struct{})
273 273
 }
274 274
 
275 275
 // SetRestarting sets the container state to "restarting" without locking.
... ...
@@ -282,8 +305,8 @@ func (s *State) SetRestarting(exitStatus *ExitStatus) {
282 282
 	s.Pid = 0
283 283
 	s.FinishedAt = time.Now().UTC()
284 284
 	s.setFromExitStatus(exitStatus)
285
-	close(s.waitChan) // fire waiters for stop
286
-	s.waitChan = make(chan struct{})
285
+	close(s.waitStop) // Fire waiters for stop
286
+	s.waitStop = make(chan struct{})
287 287
 }
288 288
 
289 289
 // SetError sets the container's error state. This is useful when we want to
... ...
@@ -335,7 +358,19 @@ func (s *State) SetDead() {
335 335
 	s.Unlock()
336 336
 }
337 337
 
338
-// Error returns current error for the state.
339
-func (s *State) Error() string {
340
-	return s.ErrorMsg
338
+// SetRemoved assumes this container is already in the "dead" state and
339
+// closes the internal waitRemove channel to unblock callers waiting for a
340
+// container to be removed.
341
+func (s *State) SetRemoved() {
342
+	s.Lock()
343
+	close(s.waitRemove) // Unblock those waiting on remove.
344
+	s.Unlock()
345
+}
346
+
347
+// Err returns an error if there is one.
348
+func (s *State) Err() error {
349
+	if s.ErrorMsg != "" {
350
+		return errors.New(s.ErrorMsg)
351
+	}
352
+	return nil
341 353
 }
... ...
@@ -1,7 +1,7 @@
1 1
 package container
2 2
 
3 3
 import (
4
-	"sync/atomic"
4
+	"context"
5 5
 	"testing"
6 6
 	"time"
7 7
 
... ...
@@ -30,31 +30,63 @@ func TestIsValidHealthString(t *testing.T) {
30 30
 
31 31
 func TestStateRunStop(t *testing.T) {
32 32
 	s := NewState()
33
-	for i := 1; i < 3; i++ { // full lifecycle two times
33
+
34
+	// Begin another wait with WaitConditionRemoved. It should complete
35
+	// within 200 milliseconds.
36
+	ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
37
+	defer cancel()
38
+	removalWait := s.Wait(ctx, WaitConditionRemoved)
39
+
40
+	// Full lifecycle two times.
41
+	for i := 1; i <= 2; i++ {
42
+		// A wait with WaitConditionNotRunning should return
43
+		// immediately since the state is now either "created" (on the
44
+		// first iteration) or "exited" (on the second iteration). It
45
+		// shouldn't take more than 50 milliseconds.
46
+		ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
47
+		defer cancel()
48
+		// Expectx exit code to be i-1 since it should be the exit
49
+		// code from the previous loop or 0 for the created state.
50
+		if status := <-s.Wait(ctx, WaitConditionNotRunning); status.ExitCode() != i-1 {
51
+			t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i-1, status.Err())
52
+		}
53
+
54
+		// A wait with WaitConditionNextExit should block until the
55
+		// container has started and exited. It shouldn't take more
56
+		// than 100 milliseconds.
57
+		ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
58
+		defer cancel()
59
+		initialWait := s.Wait(ctx, WaitConditionNextExit)
60
+
61
+		// Set the state to "Running".
34 62
 		s.Lock()
35
-		s.SetRunning(i+100, false)
63
+		s.SetRunning(i, true)
36 64
 		s.Unlock()
37 65
 
66
+		// Assert desired state.
38 67
 		if !s.IsRunning() {
39 68
 			t.Fatal("State not running")
40 69
 		}
41
-		if s.Pid != i+100 {
42
-			t.Fatalf("Pid %v, expected %v", s.Pid, i+100)
70
+		if s.Pid != i {
71
+			t.Fatalf("Pid %v, expected %v", s.Pid, i)
43 72
 		}
44 73
 		if s.ExitCode() != 0 {
45 74
 			t.Fatalf("ExitCode %v, expected 0", s.ExitCode())
46 75
 		}
47 76
 
48
-		stopped := make(chan struct{})
49
-		var exit int64
50
-		go func() {
51
-			exitCode, _ := s.WaitStop(-1 * time.Second)
52
-			atomic.StoreInt64(&exit, int64(exitCode))
53
-			close(stopped)
54
-		}()
77
+		// Now that it's running, a wait with WaitConditionNotRunning
78
+		// should block until we stop the container. It shouldn't take
79
+		// more than 100 milliseconds.
80
+		ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
81
+		defer cancel()
82
+		exitWait := s.Wait(ctx, WaitConditionNotRunning)
83
+
84
+		// Set the state to "Exited".
55 85
 		s.Lock()
56 86
 		s.SetStopped(&ExitStatus{ExitCode: i})
57 87
 		s.Unlock()
88
+
89
+		// Assert desired state.
58 90
 		if s.IsRunning() {
59 91
 			t.Fatal("State is running")
60 92
 		}
... ...
@@ -64,50 +96,73 @@ func TestStateRunStop(t *testing.T) {
64 64
 		if s.Pid != 0 {
65 65
 			t.Fatalf("Pid %v, expected 0", s.Pid)
66 66
 		}
67
-		select {
68
-		case <-time.After(100 * time.Millisecond):
69
-			t.Fatal("Stop callback doesn't fire in 100 milliseconds")
70
-		case <-stopped:
71
-			t.Log("Stop callback fired")
72
-		}
73
-		exitCode := int(atomic.LoadInt64(&exit))
74
-		if exitCode != i {
75
-			t.Fatalf("ExitCode %v, expected %v", exitCode, i)
67
+
68
+		// Receive the initialWait result.
69
+		if status := <-initialWait; status.ExitCode() != i {
70
+			t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i, status.Err())
76 71
 		}
77
-		if exitCode, err := s.WaitStop(-1 * time.Second); err != nil || exitCode != i {
78
-			t.Fatalf("WaitStop returned exitCode: %v, err: %v, expected exitCode: %v, err: %v", exitCode, err, i, nil)
72
+
73
+		// Receive the exitWait result.
74
+		if status := <-exitWait; status.ExitCode() != i {
75
+			t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i, status.Err())
79 76
 		}
80 77
 	}
78
+
79
+	// Set the state to dead and removed.
80
+	s.SetDead()
81
+	s.SetRemoved()
82
+
83
+	// Wait for removed status or timeout.
84
+	if status := <-removalWait; status.ExitCode() != 2 {
85
+		// Should have the final exit code from the loop.
86
+		t.Fatalf("Removal wait exitCode %v, expected %v, err %q", status.ExitCode(), 2, status.Err())
87
+	}
81 88
 }
82 89
 
83 90
 func TestStateTimeoutWait(t *testing.T) {
84 91
 	s := NewState()
85
-	stopped := make(chan struct{})
86
-	go func() {
87
-		s.WaitStop(100 * time.Millisecond)
88
-		close(stopped)
89
-	}()
92
+
93
+	s.Lock()
94
+	s.SetRunning(0, true)
95
+	s.Unlock()
96
+
97
+	// Start a wait with a timeout.
98
+	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
99
+	defer cancel()
100
+	waitC := s.Wait(ctx, WaitConditionNotRunning)
101
+
102
+	// It should timeout *before* this 200ms timer does.
90 103
 	select {
91 104
 	case <-time.After(200 * time.Millisecond):
92 105
 		t.Fatal("Stop callback doesn't fire in 200 milliseconds")
93
-	case <-stopped:
106
+	case status := <-waitC:
94 107
 		t.Log("Stop callback fired")
108
+		// Should be a timeout error.
109
+		if status.Err() == nil {
110
+			t.Fatal("expected timeout error, got nil")
111
+		}
112
+		if status.ExitCode() != -1 {
113
+			t.Fatalf("expected exit code %v, got %v", -1, status.ExitCode())
114
+		}
95 115
 	}
96 116
 
97 117
 	s.Lock()
98
-	s.SetStopped(&ExitStatus{ExitCode: 1})
118
+	s.SetStopped(&ExitStatus{ExitCode: 0})
99 119
 	s.Unlock()
100 120
 
101
-	stopped = make(chan struct{})
102
-	go func() {
103
-		s.WaitStop(100 * time.Millisecond)
104
-		close(stopped)
105
-	}()
121
+	// Start another wait with a timeout. This one should return
122
+	// immediately.
123
+	ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
124
+	defer cancel()
125
+	waitC = s.Wait(ctx, WaitConditionNotRunning)
126
+
106 127
 	select {
107 128
 	case <-time.After(200 * time.Millisecond):
108 129
 		t.Fatal("Stop callback doesn't fire in 200 milliseconds")
109
-	case <-stopped:
130
+	case status := <-waitC:
110 131
 		t.Log("Stop callback fired")
132
+		if status.ExitCode() != 0 {
133
+			t.Fatalf("expected exit code %v, got %v, err %q", 0, status.ExitCode(), status.Err())
134
+		}
111 135
 	}
112
-
113 136
 }
... ...
@@ -8,17 +8,11 @@ import (
8 8
 
9 9
 	"github.com/Sirupsen/logrus"
10 10
 	"github.com/docker/docker/pkg/promise"
11
+	"github.com/docker/docker/pkg/term"
11 12
 )
12 13
 
13 14
 var defaultEscapeSequence = []byte{16, 17} // ctrl-p, ctrl-q
14 15
 
15
-// DetachError is special error which returned in case of container detach.
16
-type DetachError struct{}
17
-
18
-func (DetachError) Error() string {
19
-	return "detached from container"
20
-}
21
-
22 16
 // AttachConfig is the config struct used to attach a client to a stream's stdio
23 17
 type AttachConfig struct {
24 18
 	// Tells the attach copier that the stream's stdin is a TTY and to look for
... ...
@@ -173,62 +167,11 @@ func (c *Config) CopyStreams(ctx context.Context, cfg *AttachConfig) chan error
173 173
 	})
174 174
 }
175 175
 
176
-// ttyProxy is used only for attaches with a TTY. It is used to proxy
177
-// stdin keypresses from the underlying reader and look for the passed in
178
-// escape key sequence to signal a detach.
179
-type ttyProxy struct {
180
-	escapeKeys   []byte
181
-	escapeKeyPos int
182
-	r            io.Reader
183
-}
184
-
185
-func (r *ttyProxy) Read(buf []byte) (int, error) {
186
-	nr, err := r.r.Read(buf)
187
-
188
-	preserve := func() {
189
-		// this preserves the original key presses in the passed in buffer
190
-		nr += r.escapeKeyPos
191
-		preserve := make([]byte, 0, r.escapeKeyPos+len(buf))
192
-		preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...)
193
-		preserve = append(preserve, buf...)
194
-		r.escapeKeyPos = 0
195
-		copy(buf[0:nr], preserve)
196
-	}
197
-
198
-	if nr != 1 || err != nil {
199
-		if r.escapeKeyPos > 0 {
200
-			preserve()
201
-		}
202
-		return nr, err
203
-	}
204
-
205
-	if buf[0] != r.escapeKeys[r.escapeKeyPos] {
206
-		if r.escapeKeyPos > 0 {
207
-			preserve()
208
-		}
209
-		return nr, nil
210
-	}
211
-
212
-	if r.escapeKeyPos == len(r.escapeKeys)-1 {
213
-		return 0, DetachError{}
214
-	}
215
-
216
-	// Looks like we've got an escape key, but we need to match again on the next
217
-	// read.
218
-	// Store the current escape key we found so we can look for the next one on
219
-	// the next read.
220
-	// Since this is an escape key, make sure we don't let the caller read it
221
-	// If later on we find that this is not the escape sequence, we'll add the
222
-	// keys back
223
-	r.escapeKeyPos++
224
-	return nr - r.escapeKeyPos, nil
225
-}
226
-
227 176
 func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
228 177
 	if len(keys) == 0 {
229 178
 		keys = defaultEscapeSequence
230 179
 	}
231
-	pr := &ttyProxy{escapeKeys: keys, r: src}
180
+	pr := term.NewEscapeProxy(src, keys)
232 181
 	defer src.Close()
233 182
 
234 183
 	return io.Copy(dst, pr)
... ...
@@ -1,9 +1,9 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"fmt"
5 6
 	"io"
6
-	"time"
7 7
 
8 8
 	"github.com/Sirupsen/logrus"
9 9
 	"github.com/docker/docker/api/errors"
... ...
@@ -160,21 +160,18 @@ func (daemon *Daemon) containerAttach(c *container.Container, cfg *stream.Attach
160 160
 		cfg.Stdin = nil
161 161
 	}
162 162
 
163
-	waitChan := make(chan struct{})
164 163
 	if c.Config.StdinOnce && !c.Config.Tty {
164
+		// Wait for the container to stop before returning.
165
+		waitChan := c.Wait(context.Background(), container.WaitConditionNotRunning)
165 166
 		defer func() {
166
-			<-waitChan
167
-		}()
168
-		go func() {
169
-			c.WaitStop(-1 * time.Second)
170
-			close(waitChan)
167
+			_ = <-waitChan // Ignore returned exit code.
171 168
 		}()
172 169
 	}
173 170
 
174 171
 	ctx := c.InitAttachContext()
175 172
 	err := <-c.StreamConfig.CopyStreams(ctx, cfg)
176 173
 	if err != nil {
177
-		if _, ok := err.(stream.DetachError); ok {
174
+		if _, ok := err.(term.EscapeError); ok {
178 175
 			daemon.LogContainerEvent(c, "detach")
179 176
 		} else {
180 177
 			logrus.Errorf("attach failed with error: %v", err)
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"github.com/docker/docker/api/types/filters"
14 14
 	"github.com/docker/docker/api/types/network"
15 15
 	swarmtypes "github.com/docker/docker/api/types/swarm"
16
+	containerpkg "github.com/docker/docker/container"
16 17
 	clustertypes "github.com/docker/docker/daemon/cluster/provider"
17 18
 	"github.com/docker/docker/plugin"
18 19
 	"github.com/docker/libnetwork"
... ...
@@ -39,7 +40,7 @@ type Backend interface {
39 39
 	DeactivateContainerServiceBinding(containerName string) error
40 40
 	UpdateContainerServiceConfig(containerName string, serviceConfig *clustertypes.ServiceConfig) error
41 41
 	ContainerInspectCurrent(name string, size bool) (*types.ContainerJSON, error)
42
-	ContainerWaitWithContext(ctx context.Context, name string) error
42
+	ContainerWait(ctx context.Context, name string, condition containerpkg.WaitCondition) (<-chan containerpkg.StateStatus, error)
43 43
 	ContainerRm(name string, config *types.ContainerRmConfig) error
44 44
 	ContainerKill(name string, sig uint64) error
45 45
 	SetContainerDependencyStore(name string, store exec.DependencyGetter) error
... ...
@@ -17,6 +17,7 @@ import (
17 17
 	"github.com/docker/docker/api/types/backend"
18 18
 	containertypes "github.com/docker/docker/api/types/container"
19 19
 	"github.com/docker/docker/api/types/events"
20
+	containerpkg "github.com/docker/docker/container"
20 21
 	"github.com/docker/docker/daemon/cluster/convert"
21 22
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
22 23
 	"github.com/docker/libnetwork"
... ...
@@ -337,8 +338,8 @@ func (c *containerAdapter) events(ctx context.Context) <-chan events.Message {
337 337
 	return eventsq
338 338
 }
339 339
 
340
-func (c *containerAdapter) wait(ctx context.Context) error {
341
-	return c.backend.ContainerWaitWithContext(ctx, c.container.nameOrID())
340
+func (c *containerAdapter) wait(ctx context.Context) (<-chan containerpkg.StateStatus, error) {
341
+	return c.backend.ContainerWait(ctx, c.container.nameOrID(), containerpkg.WaitConditionNotRunning)
342 342
 }
343 343
 
344 344
 func (c *containerAdapter) shutdown(ctx context.Context) error {
... ...
@@ -279,25 +279,27 @@ func (r *controller) Wait(pctx context.Context) error {
279 279
 		}
280 280
 	}()
281 281
 
282
-	err := r.adapter.wait(ctx)
283
-	if ctx.Err() != nil {
284
-		return ctx.Err()
282
+	waitC, err := r.adapter.wait(ctx)
283
+	if err != nil {
284
+		return err
285 285
 	}
286 286
 
287
-	if err != nil {
288
-		ee := &exitError{}
289
-		if ec, ok := err.(exec.ExitCoder); ok {
290
-			ee.code = ec.ExitCode()
287
+	if status := <-waitC; status.ExitCode() != 0 {
288
+		exitErr := &exitError{
289
+			code: status.ExitCode(),
291 290
 		}
291
+
292
+		// Set the cause if it is knowable.
292 293
 		select {
293 294
 		case e := <-healthErr:
294
-			ee.cause = e
295
+			exitErr.cause = e
295 296
 		default:
296
-			if err.Error() != "" {
297
-				ee.cause = err
297
+			if status.Err() != nil {
298
+				exitErr.cause = status.Err()
298 299
 			}
299 300
 		}
300
-		return ee
301
+
302
+		return exitErr
301 303
 	}
302 304
 
303 305
 	return nil
... ...
@@ -3,6 +3,7 @@
3 3
 package daemon
4 4
 
5 5
 import (
6
+	"context"
6 7
 	"fmt"
7 8
 	"io/ioutil"
8 9
 	"os"
... ...
@@ -290,11 +291,16 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
290 290
 	return nil
291 291
 }
292 292
 
293
-func killProcessDirectly(container *container.Container) error {
294
-	if _, err := container.WaitStop(10 * time.Second); err != nil {
293
+func killProcessDirectly(cntr *container.Container) error {
294
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
295
+	defer cancel()
296
+
297
+	// Block until the container to stops or timeout.
298
+	status := <-cntr.Wait(ctx, container.WaitConditionNotRunning)
299
+	if status.Err() != nil {
295 300
 		// Ensure that we don't kill ourselves
296
-		if pid := container.GetPID(); pid != 0 {
297
-			logrus.Infof("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", stringid.TruncateID(container.ID))
301
+		if pid := cntr.GetPID(); pid != 0 {
302
+			logrus.Infof("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", stringid.TruncateID(cntr.ID))
298 303
 			if err := syscall.Kill(pid, 9); err != nil {
299 304
 				if err != syscall.ESRCH {
300 305
 					return err
... ...
@@ -6,6 +6,7 @@
6 6
 package daemon
7 7
 
8 8
 import (
9
+	"context"
9 10
 	"fmt"
10 11
 	"io/ioutil"
11 12
 	"net"
... ...
@@ -773,7 +774,12 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
773 773
 		if err := daemon.containerUnpause(c); err != nil {
774 774
 			return fmt.Errorf("Failed to unpause container %s with error: %v", c.ID, err)
775 775
 		}
776
-		if _, err := c.WaitStop(time.Duration(stopTimeout) * time.Second); err != nil {
776
+
777
+		ctx, cancel := context.WithTimeout(context.Background(), time.Duration(stopTimeout)*time.Second)
778
+		defer cancel()
779
+
780
+		// Wait with timeout for container to exit.
781
+		if status := <-c.Wait(ctx, container.WaitConditionNotRunning); status.Err() != nil {
777 782
 			logrus.Debugf("container %s failed to exit in %d second of SIGTERM, sending SIGKILL to force", c.ID, stopTimeout)
778 783
 			sig, ok := signal.SignalMap["KILL"]
779 784
 			if !ok {
... ...
@@ -782,8 +788,10 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
782 782
 			if err := daemon.kill(c, int(sig)); err != nil {
783 783
 				logrus.Errorf("Failed to SIGKILL container %s", c.ID)
784 784
 			}
785
-			c.WaitStop(-1 * time.Second)
786
-			return err
785
+			// Wait for exit again without a timeout.
786
+			// Explicitly ignore the result.
787
+			_ = <-c.Wait(context.Background(), container.WaitConditionNotRunning)
788
+			return status.Err()
787 789
 		}
788 790
 	}
789 791
 	// If container failed to exit in stopTimeout seconds of SIGTERM, then using the force
... ...
@@ -791,7 +799,9 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
791 791
 		return fmt.Errorf("Failed to stop container %s with error: %v", c.ID, err)
792 792
 	}
793 793
 
794
-	c.WaitStop(-1 * time.Second)
794
+	// Wait without timeout for the container to exit.
795
+	// Ignore the result.
796
+	_ = <-c.Wait(context.Background(), container.WaitConditionNotRunning)
795 797
 	return nil
796 798
 }
797 799
 
... ...
@@ -134,6 +134,7 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
134 134
 	if e := daemon.removeMountPoints(container, removeVolume); e != nil {
135 135
 		logrus.Error(e)
136 136
 	}
137
+	container.SetRemoved()
137 138
 	stateCtr.del(container.ID)
138 139
 	daemon.LogContainerEvent(container, "destroy")
139 140
 	return nil
... ...
@@ -253,7 +253,7 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
253 253
 		return fmt.Errorf("context cancelled")
254 254
 	case err := <-attachErr:
255 255
 		if err != nil {
256
-			if _, ok := err.(stream.DetachError); !ok {
256
+			if _, ok := err.(term.EscapeError); !ok {
257 257
 				return fmt.Errorf("exec attach failed with error: %v", err)
258 258
 			}
259 259
 			d.LogContainerEvent(c, "exec_detach")
... ...
@@ -153,7 +153,7 @@ func (daemon *Daemon) getInspectData(container *container.Container) (*types.Con
153 153
 		Dead:       container.State.Dead,
154 154
 		Pid:        container.State.Pid,
155 155
 		ExitCode:   container.State.ExitCode(),
156
-		Error:      container.State.Error(),
156
+		Error:      container.State.ErrorMsg,
157 157
 		StartedAt:  container.State.StartedAt.Format(time.RFC3339Nano),
158 158
 		FinishedAt: container.State.FinishedAt.Format(time.RFC3339Nano),
159 159
 		Health:     containerHealth,
... ...
@@ -1,6 +1,7 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"fmt"
5 6
 	"runtime"
6 7
 	"strings"
... ...
@@ -8,7 +9,7 @@ import (
8 8
 	"time"
9 9
 
10 10
 	"github.com/Sirupsen/logrus"
11
-	"github.com/docker/docker/container"
11
+	containerpkg "github.com/docker/docker/container"
12 12
 	"github.com/docker/docker/pkg/signal"
13 13
 )
14 14
 
... ...
@@ -54,7 +55,7 @@ func (daemon *Daemon) ContainerKill(name string, sig uint64) error {
54 54
 // to send the signal. An error is returned if the container is paused
55 55
 // or not running, or if there is a problem returned from the
56 56
 // underlying kill command.
57
-func (daemon *Daemon) killWithSignal(container *container.Container, sig int) error {
57
+func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int) error {
58 58
 	logrus.Debugf("Sending kill signal %d to container %s", sig, container.ID)
59 59
 	container.Lock()
60 60
 	defer container.Unlock()
... ...
@@ -110,7 +111,7 @@ func (daemon *Daemon) killWithSignal(container *container.Container, sig int) er
110 110
 }
111 111
 
112 112
 // Kill forcefully terminates a container.
113
-func (daemon *Daemon) Kill(container *container.Container) error {
113
+func (daemon *Daemon) Kill(container *containerpkg.Container) error {
114 114
 	if !container.IsRunning() {
115 115
 		return errNotRunning{container.ID}
116 116
 	}
... ...
@@ -131,7 +132,10 @@ func (daemon *Daemon) Kill(container *container.Container) error {
131 131
 			return nil
132 132
 		}
133 133
 
134
-		if _, err2 := container.WaitStop(2 * time.Second); err2 != nil {
134
+		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
135
+		defer cancel()
136
+
137
+		if status := <-container.Wait(ctx, containerpkg.WaitConditionNotRunning); status.Err() != nil {
135 138
 			return err
136 139
 		}
137 140
 	}
... ...
@@ -144,12 +148,15 @@ func (daemon *Daemon) Kill(container *container.Container) error {
144 144
 		return err
145 145
 	}
146 146
 
147
-	container.WaitStop(-1 * time.Second)
147
+	// Wait for exit with no timeout.
148
+	// Ignore returned status.
149
+	_ = <-container.Wait(context.Background(), containerpkg.WaitConditionNotRunning)
150
+
148 151
 	return nil
149 152
 }
150 153
 
151 154
 // killPossibleDeadProcess is a wrapper around killSig() suppressing "no such process" error.
152
-func (daemon *Daemon) killPossiblyDeadProcess(container *container.Container, sig int) error {
155
+func (daemon *Daemon) killPossiblyDeadProcess(container *containerpkg.Container, sig int) error {
153 156
 	err := daemon.killWithSignal(container, sig)
154 157
 	if err == syscall.ESRCH {
155 158
 		e := errNoSuchProcess{container.GetPID(), sig}
... ...
@@ -159,6 +166,6 @@ func (daemon *Daemon) killPossiblyDeadProcess(container *container.Container, si
159 159
 	return err
160 160
 }
161 161
 
162
-func (daemon *Daemon) kill(c *container.Container, sig int) error {
162
+func (daemon *Daemon) kill(c *containerpkg.Container, sig int) error {
163 163
 	return daemon.containerd.Signal(c.ID, sig)
164 164
 }
... ...
@@ -1,13 +1,14 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"fmt"
5 6
 	"net/http"
6 7
 	"time"
7 8
 
8 9
 	"github.com/Sirupsen/logrus"
9 10
 	"github.com/docker/docker/api/errors"
10
-	"github.com/docker/docker/container"
11
+	containerpkg "github.com/docker/docker/container"
11 12
 )
12 13
 
13 14
 // ContainerStop looks for the given container and terminates it,
... ...
@@ -40,7 +41,7 @@ func (daemon *Daemon) ContainerStop(name string, seconds *int) error {
40 40
 // process to exit. If a negative duration is given, Stop will wait
41 41
 // for the initial signal forever. If the container is not running Stop returns
42 42
 // immediately.
43
-func (daemon *Daemon) containerStop(container *container.Container, seconds int) error {
43
+func (daemon *Daemon) containerStop(container *containerpkg.Container, seconds int) error {
44 44
 	if !container.IsRunning() {
45 45
 		return nil
46 46
 	}
... ...
@@ -60,7 +61,10 @@ func (daemon *Daemon) containerStop(container *container.Container, seconds int)
60 60
 		// So, instead we'll give it up to 2 more seconds to complete and if
61 61
 		// by that time the container is still running, then the error
62 62
 		// we got is probably valid and so we force kill it.
63
-		if _, err := container.WaitStop(2 * time.Second); err != nil {
63
+		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
64
+		defer cancel()
65
+
66
+		if status := <-container.Wait(ctx, containerpkg.WaitConditionNotRunning); status.Err() != nil {
64 67
 			logrus.Infof("Container failed to stop after sending signal %d to the process, force killing", stopSignal)
65 68
 			if err := daemon.killPossiblyDeadProcess(container, 9); err != nil {
66 69
 				return err
... ...
@@ -69,11 +73,15 @@ func (daemon *Daemon) containerStop(container *container.Container, seconds int)
69 69
 	}
70 70
 
71 71
 	// 2. Wait for the process to exit on its own
72
-	if _, err := container.WaitStop(time.Duration(seconds) * time.Second); err != nil {
72
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(seconds)*time.Second)
73
+	defer cancel()
74
+
75
+	if status := <-container.Wait(ctx, containerpkg.WaitConditionNotRunning); status.Err() != nil {
73 76
 		logrus.Infof("Container %v failed to exit within %d seconds of signal %d - using the force", container.ID, seconds, stopSignal)
74 77
 		// 3. If it doesn't, then send SIGKILL
75 78
 		if err := daemon.Kill(container); err != nil {
76
-			container.WaitStop(-1 * time.Second)
79
+			// Wait without a timeout, ignore result.
80
+			_ = <-container.Wait(context.Background(), containerpkg.WaitConditionNotRunning)
77 81
 			logrus.Warn(err) // Don't return error because we only care that container is stopped, not what function stopped it
78 82
 		}
79 83
 	}
... ...
@@ -1,32 +1,22 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
-	"time"
5
-
4
+	"github.com/docker/docker/container"
6 5
 	"golang.org/x/net/context"
7 6
 )
8 7
 
9
-// ContainerWait stops processing until the given container is
10
-// stopped. If the container is not found, an error is returned. On a
11
-// successful stop, the exit code of the container is returned. On a
12
-// timeout, an error is returned. If you want to wait forever, supply
13
-// a negative duration for the timeout.
14
-func (daemon *Daemon) ContainerWait(name string, timeout time.Duration) (int, error) {
15
-	container, err := daemon.GetContainer(name)
16
-	if err != nil {
17
-		return -1, err
18
-	}
19
-
20
-	return container.WaitStop(timeout)
21
-}
22
-
23
-// ContainerWaitWithContext returns a channel where exit code is sent
24
-// when container stops. Channel can be cancelled with a context.
25
-func (daemon *Daemon) ContainerWaitWithContext(ctx context.Context, name string) error {
26
-	container, err := daemon.GetContainer(name)
8
+// ContainerWait waits until the given container is in a certain state
9
+// indicated by the given condition. If the container is not found, a nil
10
+// channel and non-nil error is returned immediately. If the container is
11
+// found, a status result will be sent on the returned channel once the wait
12
+// condition is met or if an error occurs waiting for the container (such as a
13
+// context timeout or cancellation). On a successful wait, the exit code of the
14
+// container is returned in the status with a non-nil Err() value.
15
+func (daemon *Daemon) ContainerWait(ctx context.Context, name string, condition container.WaitCondition) (<-chan container.StateStatus, error) {
16
+	cntr, err := daemon.GetContainer(name)
27 17
 	if err != nil {
28
-		return err
18
+		return nil, err
29 19
 	}
30 20
 
31
-	return container.WaitWithContext(ctx)
21
+	return cntr.Wait(ctx, condition), nil
32 22
 }
... ...
@@ -28,6 +28,7 @@ keywords: "API, Docker, rcli, REST, documentation"
28 28
  the swarm, the desired CA key for the swarm (if not using an external certificate), and an optional parameter to force swarm to
29 29
  generate and rotate to a new CA certificate/key pair.
30 30
 * `POST /service/create` and `POST /services/(id or name)/update` now take the field `Platforms` as part of the service `Placement`, allowing to specify platforms supported by the service.
31
+* `POST /containers/(name)/wait` now accepts a `condition` query parameter to indicate which state change condition to wait for. Also, response headers are now returned immediately to acknowledge that the server has registered a wait callback for the client.
31 32
 
32 33
 ## v1.29 API changes
33 34
 
... ...
@@ -21,7 +21,7 @@ Usage:  docker wait CONTAINER [CONTAINER...]
21 21
 Block until one or more containers stop, then print their exit codes
22 22
 
23 23
 Options:
24
-      --help   Print usage
24
+      --help        Print usage
25 25
 ```
26 26
 
27 27
 > **Note**: `docker wait` returns `0` when run against a container which had
... ...
@@ -83,11 +83,13 @@ func privilegedTestChunkExecutor(autoRemove bool) testChunkExecutor {
83 83
 		}
84 84
 		var b bytes.Buffer
85 85
 		teeContainerStream(&b, os.Stdout, os.Stderr, stream)
86
-		rc, err := cli.ContainerWait(context.Background(), id)
87
-		if err != nil {
86
+		resultC, errC := cli.ContainerWait(context.Background(), id, "")
87
+		select {
88
+		case err := <-errC:
88 89
 			return 0, "", err
90
+		case result := <-resultC:
91
+			return result.StatusCode, b.String(), nil
89 92
 		}
90
-		return rc, b.String(), nil
91 93
 	}
92 94
 }
93 95
 
... ...
@@ -188,5 +188,11 @@ func waitForContainerCompletion(cli *client.Client, stdout, stderr io.Writer, co
188 188
 	}
189 189
 	stdcopy.StdCopy(stdout, stderr, stream)
190 190
 	stream.Close()
191
-	return cli.ContainerWait(context.Background(), containerID)
191
+	resultC, errC := cli.ContainerWait(context.Background(), containerID, "")
192
+	select {
193
+	case err := <-errC:
194
+		return 1, err
195
+	case result := <-resultC:
196
+		return result.StatusCode, nil
197
+	}
192 198
 }
193 199
new file mode 100644
... ...
@@ -0,0 +1,74 @@
0
+package term
1
+
2
+import (
3
+	"io"
4
+)
5
+
6
+// EscapeError is special error which returned by a TTY proxy reader's Read()
7
+// method in case its detach escape sequence is read.
8
+type EscapeError struct{}
9
+
10
+func (EscapeError) Error() string {
11
+	return "read escape sequence"
12
+}
13
+
14
+// escapeProxy is used only for attaches with a TTY. It is used to proxy
15
+// stdin keypresses from the underlying reader and look for the passed in
16
+// escape key sequence to signal a detach.
17
+type escapeProxy struct {
18
+	escapeKeys   []byte
19
+	escapeKeyPos int
20
+	r            io.Reader
21
+}
22
+
23
+// NewEscapeProxy returns a new TTY proxy reader which wraps the given reader
24
+// and detects when the specified escape keys are read, in which case the Read
25
+// method will return an error of type EscapeError.
26
+func NewEscapeProxy(r io.Reader, escapeKeys []byte) io.Reader {
27
+	return &escapeProxy{
28
+		escapeKeys: escapeKeys,
29
+		r:          r,
30
+	}
31
+}
32
+
33
+func (r *escapeProxy) Read(buf []byte) (int, error) {
34
+	nr, err := r.r.Read(buf)
35
+
36
+	preserve := func() {
37
+		// this preserves the original key presses in the passed in buffer
38
+		nr += r.escapeKeyPos
39
+		preserve := make([]byte, 0, r.escapeKeyPos+len(buf))
40
+		preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...)
41
+		preserve = append(preserve, buf...)
42
+		r.escapeKeyPos = 0
43
+		copy(buf[0:nr], preserve)
44
+	}
45
+
46
+	if nr != 1 || err != nil {
47
+		if r.escapeKeyPos > 0 {
48
+			preserve()
49
+		}
50
+		return nr, err
51
+	}
52
+
53
+	if buf[0] != r.escapeKeys[r.escapeKeyPos] {
54
+		if r.escapeKeyPos > 0 {
55
+			preserve()
56
+		}
57
+		return nr, nil
58
+	}
59
+
60
+	if r.escapeKeyPos == len(r.escapeKeys)-1 {
61
+		return 0, EscapeError{}
62
+	}
63
+
64
+	// Looks like we've got an escape key, but we need to match again on the next
65
+	// read.
66
+	// Store the current escape key we found so we can look for the next one on
67
+	// the next read.
68
+	// Since this is an escape key, make sure we don't let the caller read it
69
+	// If later on we find that this is not the escape sequence, we'll add the
70
+	// keys back
71
+	r.escapeKeyPos++
72
+	return nr - r.escapeKeyPos, nil
73
+}