Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix race condition when looking up reaper (ryuk) container #2508

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions docker.go
Expand Up @@ -86,6 +86,8 @@ type DockerContainer struct {
logProductionTimeout *time.Duration
logger Logging
lifecycleHooks []ContainerLifecycleHooks

healthStatus string // container health status, will default to healthStatusNone if no healthcheck is present
}

// SetLogger sets the logger for the container
Expand Down Expand Up @@ -1590,6 +1592,11 @@ func containerFromDockerResponse(ctx context.Context, response types.Container)
return nil, err
}

// the health status of the container, if any
if health := container.raw.State.Health; health != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any case where State can be nil? If so, we can return the container with a default healthStatus 🤔

container.healthStatus = health.Status
}

return &container, nil
}

Expand Down
29 changes: 29 additions & 0 deletions reaper.go
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"context"
"fmt"
"github.com/docker/docker/api/types"
"math/rand"
"net"
"strings"
Expand Down Expand Up @@ -117,6 +118,13 @@ func lookUpReaperContainer(ctx context.Context, sessionID string) (*DockerContai
return err
}

// if a health status is present on the container, and the container is not healthy, error
if r.healthStatus != "" {
if r.healthStatus != types.Healthy && r.healthStatus != types.NoHealthcheck {
return fmt.Errorf("container %s is not healthy, wanted status=%s, got status=%s", resp[0].ID[:8], types.Healthy, r.healthStatus)
}
}

Comment on lines +121 to +127
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we could add this check as a readiness hook? Then I think this fix would be more intentional. Thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, we do have a wait.ForHealth, that could be of our interest 🤔 Wdyt? It will prevent for nil Health (see https://github.com/testcontainers/testcontainers-go/pull/2508/files#r1607874443). See code here

Then, I think we can remove the healtStatus field and simply delegate to the wait for strategy for the reaper

reaperContainer = r

return nil
Expand Down Expand Up @@ -162,6 +170,27 @@ func reuseOrCreateReaper(ctx context.Context, sessionID string, provider ReaperP
if err != nil {
return nil, err
}

Logger.Printf("⏳ Waiting for Reaper port to be ready")

var containerJson *types.ContainerJSON

if containerJson, err = reaperContainer.Inspect(ctx); err != nil {
return nil, fmt.Errorf("failed to inspect reaper container %s: %w", reaperContainer.ID[:8], err)
}

if containerJson != nil && containerJson.NetworkSettings != nil {
for port := range containerJson.NetworkSettings.Ports {
err := wait.ForListeningPort(port).
WithPollInterval(100*time.Millisecond).
WaitUntilReady(ctx, reaperContainer)
if err != nil {
return nil, fmt.Errorf("failed waiting for reaper container %s port %s/%s to be ready: %w",
reaperContainer.ID[:8], port.Proto(), port.Port(), err)
}
}
}

Comment on lines +174 to +193
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we can add this logic to the reuseReaperContainer function so that all invocations of that function benefit for this code?

return reaperInstance, nil
}

Expand Down