| 210 | } |
| 211 | |
| 212 | func waitPreStart(ctx context.Context, serviceName string, index int, waitRes client.ContainerWaitResult) error { |
| 213 | // ContainerWait can deliver on Result and Error at the same instant. Two |
| 214 | // races have to be closed deterministically here: |
| 215 | // 1. The daemon closing a successful stream cleanly sends nil on Error |
| 216 | // AND the exit code on Result — a plain 3-case select would let Go |
| 217 | // pick the Error branch and report a spurious "wait ended" failure. |
| 218 | // 2. A real transport error on Error can race with a stale Result — if |
| 219 | // the scheduler picks Result, we would silently drop the error and |
| 220 | // let the service start. |
| 221 | // Loop until Result is delivered, nil-ing the Error channel after a nil |
| 222 | // receive so a closed channel cannot busy-loop. After Result lands, do a |
| 223 | // non-blocking check on Error so a real error still wins over Result. |
| 224 | errCh := waitRes.Error |
| 225 | for { |
| 226 | select { |
| 227 | case <-ctx.Done(): |
| 228 | return ctx.Err() |
| 229 | case res := <-waitRes.Result: |
| 230 | select { |
| 231 | case err := <-errCh: |
| 232 | if err != nil { |
| 233 | return err |
| 234 | } |
| 235 | default: |
| 236 | } |
| 237 | return preStartResultErr(serviceName, index, res) |
| 238 | case err := <-errCh: |
| 239 | if err != nil { |
| 240 | return err |
| 241 | } |
| 242 | // nil on Error: stream closed cleanly. Disable this case so a |
| 243 | // closed channel can't fire repeatedly. |
| 244 | errCh = nil |
| 245 | } |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | func preStartResultErr(serviceName string, index int, res container.WaitResponse) error { |
| 250 | if res.Error != nil { |