github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/client/container_wait.go (about) 1 package client // import "github.com/docker/docker/client" 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "io" 9 "net/url" 10 11 "github.com/docker/docker/api/types/container" 12 "github.com/docker/docker/api/types/versions" 13 ) 14 15 const containerWaitErrorMsgLimit = 2 * 1024 /* Max: 2KiB */ 16 17 // ContainerWait waits until the specified container is in a certain state 18 // indicated by the given condition, either "not-running" (default), 19 // "next-exit", or "removed". 20 // 21 // If this client's API version is before 1.30, condition is ignored and 22 // ContainerWait will return immediately with the two channels, as the server 23 // will wait as if the condition were "not-running". 24 // 25 // If this client's API version is at least 1.30, ContainerWait blocks until 26 // the request has been acknowledged by the server (with a response header), 27 // then returns two channels on which the caller can wait for the exit status 28 // of the container or an error if there was a problem either beginning the 29 // wait request or in getting the response. This allows the caller to 30 // synchronize ContainerWait with other calls, such as specifying a 31 // "next-exit" condition before issuing a ContainerStart request. 32 func (cli *Client) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) { 33 resultC := make(chan container.WaitResponse) 34 errC := make(chan error, 1) 35 36 // Make sure we negotiated (if the client is configured to do so), 37 // as code below contains API-version specific handling of options. 38 // 39 // Normally, version-negotiation (if enabled) would not happen until 40 // the API request is made. 41 if err := cli.checkVersion(ctx); err != nil { 42 errC <- err 43 return resultC, errC 44 } 45 if versions.LessThan(cli.ClientVersion(), "1.30") { 46 return cli.legacyContainerWait(ctx, containerID) 47 } 48 49 query := url.Values{} 50 if condition != "" { 51 query.Set("condition", string(condition)) 52 } 53 54 resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", query, nil, nil) 55 if err != nil { 56 defer ensureReaderClosed(resp) 57 errC <- err 58 return resultC, errC 59 } 60 61 go func() { 62 defer ensureReaderClosed(resp) 63 64 body := resp.body 65 responseText := bytes.NewBuffer(nil) 66 stream := io.TeeReader(body, responseText) 67 68 var res container.WaitResponse 69 if err := json.NewDecoder(stream).Decode(&res); err != nil { 70 // NOTE(nicks): The /wait API does not work well with HTTP proxies. 71 // At any time, the proxy could cut off the response stream. 72 // 73 // But because the HTTP status has already been written, the proxy's 74 // only option is to write a plaintext error message. 75 // 76 // If there's a JSON parsing error, read the real error message 77 // off the body and send it to the client. 78 if errors.As(err, new(*json.SyntaxError)) { 79 _, _ = io.ReadAll(io.LimitReader(stream, containerWaitErrorMsgLimit)) 80 errC <- errors.New(responseText.String()) 81 } else { 82 errC <- err 83 } 84 return 85 } 86 87 resultC <- res 88 }() 89 90 return resultC, errC 91 } 92 93 // legacyContainerWait returns immediately and doesn't have an option to wait 94 // until the container is removed. 95 func (cli *Client) legacyContainerWait(ctx context.Context, containerID string) (<-chan container.WaitResponse, <-chan error) { 96 resultC := make(chan container.WaitResponse) 97 errC := make(chan error) 98 99 go func() { 100 resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil) 101 if err != nil { 102 errC <- err 103 return 104 } 105 defer ensureReaderClosed(resp) 106 107 var res container.WaitResponse 108 if err := json.NewDecoder(resp.body).Decode(&res); err != nil { 109 errC <- err 110 return 111 } 112 113 resultC <- res 114 }() 115 116 return resultC, errC 117 }