| 189 | } |
| 190 | |
| 191 | func waitForDaemonStart(ctx context.Context, deps commandDeps, child daemonProcess) (DaemonStatus, error) { |
| 192 | if child == nil { |
| 193 | return DaemonStatus{}, errors.New("cli: detached daemon process is required") |
| 194 | } |
| 195 | childPID := child.PID() |
| 196 | if childPID <= 0 { |
| 197 | return DaemonStatus{}, errors.New("cli: detached daemon process pid is required") |
| 198 | } |
| 199 | waitCtx := ctx |
| 200 | if waitCtx == nil { |
| 201 | waitCtx = context.Background() |
| 202 | } |
| 203 | if _, hasDeadline := waitCtx.Deadline(); !hasDeadline { |
| 204 | var cancel context.CancelFunc |
| 205 | waitCtx, cancel = context.WithTimeout(waitCtx, deps.startTimeout) |
| 206 | defer cancel() |
| 207 | } |
| 208 | |
| 209 | client, err := clientFromDeps(deps) |
| 210 | if err != nil { |
| 211 | return DaemonStatus{}, err |
| 212 | } |
| 213 | |
| 214 | processAlive := deps.processAlive |
| 215 | if processAlive == nil { |
| 216 | processAlive = procutil.Alive |
| 217 | } |
| 218 | |
| 219 | ticker := time.NewTicker(deps.pollInterval) |
| 220 | defer ticker.Stop() |
| 221 | |
| 222 | for { |
| 223 | select { |
| 224 | case <-child.Done(): |
| 225 | if err := child.Wait(); err != nil { |
| 226 | return DaemonStatus{}, fmt.Errorf("cli: detached daemon exited before readiness: %w", err) |
| 227 | } |
| 228 | return DaemonStatus{}, errors.New("cli: detached daemon exited before readiness") |
| 229 | case <-waitCtx.Done(): |
| 230 | return DaemonStatus{}, fmt.Errorf("cli: daemon did not become ready before timeout: %w", waitCtx.Err()) |
| 231 | case <-ticker.C: |
| 232 | status, statusErr := client.DaemonStatus(waitCtx) |
| 233 | if statusErr == nil && status.PID == childPID { |
| 234 | return status, nil |
| 235 | } |
| 236 | if !processAlive(childPID) { |
| 237 | return DaemonStatus{}, errors.New("cli: detached daemon exited before readiness") |
| 238 | } |
| 239 | } |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | func waitForDaemonStop( |
| 244 | ctx context.Context, |