Skip to content

Commit ca31b65

Browse files
authored
perf: Ensure Docker daemon exists AND is running (#15693)
1 parent 8e0b098 commit ca31b65

1 file changed

Lines changed: 56 additions & 14 deletions

File tree

pkg/workflow/docker_validation.go

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
//
1313
// # Validation Pattern: Warning vs Error
1414
//
15-
// Docker image validation uses a flexible approach:
16-
// - If Docker is not available, a warning is emitted but validation is skipped
15+
// Docker image validation returns errors for all failure cases. The caller
16+
// (validateContainerImages) collects these and surfaces them as compiler warnings:
17+
// - If Docker is not installed, returns an error
18+
// - If the Docker daemon is not running, returns an error (with fast timeout check)
1719
// - If an image cannot be pulled due to authentication (private repo), validation passes
18-
// - If an image truly doesn't exist, validation fails with an error
20+
// - If an image truly doesn't exist, returns an error
1921
// - Verbose mode provides detailed validation feedback
2022
//
2123
// # When to Add Validation Here
@@ -32,33 +34,73 @@
3234
package workflow
3335

3436
import (
37+
"context"
3538
"fmt"
36-
"os"
3739
"os/exec"
3840
"strings"
41+
"sync"
3942
"time"
4043

41-
"github.com/github/gh-aw/pkg/console"
4244
"github.com/github/gh-aw/pkg/constants"
4345
"github.com/github/gh-aw/pkg/logger"
4446
)
4547

4648
var dockerValidationLog = logger.New("workflow:docker_validation")
4749

48-
// validateDockerImage checks if a Docker image exists and is accessible
49-
// Returns nil if docker is not available (with a warning printed)
50+
// dockerDaemonCheckTimeout is how long to wait for `docker info` to respond.
51+
// If the daemon isn't running, this prevents long hangs on every docker command.
52+
const dockerDaemonCheckTimeout = 3 * time.Second
53+
54+
// Cached result of Docker daemon availability check.
55+
// Checked once per process to avoid repeated slow checks when daemon is down.
56+
var (
57+
dockerDaemonOnce sync.Once
58+
dockerDaemonAvailable bool
59+
)
60+
61+
// isDockerDaemonRunning checks if the Docker daemon is responsive.
62+
// Uses a short timeout to avoid hanging when Docker is installed but the daemon is stopped.
63+
// Results are cached for the process lifetime.
64+
func isDockerDaemonRunning() bool {
65+
dockerDaemonOnce.Do(func() {
66+
dockerValidationLog.Print("Checking if Docker daemon is running")
67+
ctx, cancel := context.WithTimeout(context.Background(), dockerDaemonCheckTimeout)
68+
defer cancel()
69+
70+
cmd := exec.CommandContext(ctx, "docker", "info")
71+
cmd.Stdout = nil
72+
cmd.Stderr = nil
73+
err := cmd.Run()
74+
75+
dockerDaemonAvailable = err == nil
76+
if !dockerDaemonAvailable {
77+
dockerValidationLog.Printf("Docker daemon not running or not responsive: %v", err)
78+
} else {
79+
dockerValidationLog.Print("Docker daemon is running")
80+
}
81+
})
82+
return dockerDaemonAvailable
83+
}
84+
85+
// validateDockerImage checks if a Docker image exists and is accessible.
86+
// Returns an error if Docker is not installed, the daemon is not running,
87+
// or the image cannot be found. The caller treats these as warnings.
5088
func validateDockerImage(image string, verbose bool) error {
5189
dockerValidationLog.Printf("Validating Docker image: %s", image)
5290

53-
// Check if docker is available
91+
// Check if docker CLI is available on PATH
5492
_, err := exec.LookPath("docker")
5593
if err != nil {
56-
dockerValidationLog.Print("Docker not available, skipping image validation")
57-
// Docker not available - print warning and skip validation
58-
if verbose {
59-
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Docker not available - skipping validation for container image '%s'", image)))
60-
}
61-
return nil
94+
dockerValidationLog.Print("Docker not installed, cannot validate image")
95+
return fmt.Errorf("Docker not installed - could not validate container image '%s'. Install Docker or remove container-based tools", image)
96+
}
97+
98+
// Check if Docker daemon is actually running (cached check with short timeout).
99+
// This prevents multi-minute hangs when Docker Desktop is installed but not running,
100+
// which is common on macOS development machines.
101+
if !isDockerDaemonRunning() {
102+
dockerValidationLog.Print("Docker daemon not running, cannot validate image")
103+
return fmt.Errorf("Docker daemon not running - could not validate container image '%s'. Start Docker Desktop or remove container-based tools", image)
62104
}
63105

64106
// Try to inspect the image (will succeed if image exists locally)

0 commit comments

Comments
 (0)