Skip to content

[Bug]: LogMessageWaitStrategy waits for the full startup timeout after the container exits #12151

Open

Description

@hashhar

Module

Core

Testcontainers version

2.0.5

Using the latest Testcontainers version?

Yes

Host OS

Linux in CI, repro below run on macOS

Host Arch

x86_64 in CI, arm64 locally

Docker version

29.8.2 (API 1.56) locally

What happened?

If the container dies before the log line shows up LogMessageWaitStrategy still waits the whole startup timeout. We hit this in CI where a container crashes during startup and each attempt burns the full 10 min timeout before failing.

FrameConsumerResultCallback sends OutputFrame.END to the consumer when the log stream closes but WaitingConsumer.waitUntil treats it like any other frame that doesn't match and keeps polling until the deadline. HostPortWaitStrategy already has .failFast("container is no longer running", ...) - log waits don't have anything like that.

Repro:

try (GenericContainer<?> c = new GenericContainer<>("alpine:3.20")
        .withCommand("sh", "-c", "echo started; sleep 2; exit 1")
        .waitingFor(Wait.forLogMessage(".*never.*", 1).withStartupTimeout(Duration.ofSeconds(60)))) {
    c.start();
}

Container is gone after ~2 s but start() only fails after the timeout.

Would be nice if the wait failed as soon as it sees END before a match, e.g. with "container stopped before the expected log output". DockerHealthcheckWaitStrategy and HttpWaitStrategy look the same to me (retry until timeout) so maybe they need it too.

We worked around it with a wrapper wait strategy that cancels the wait from a waitContainerCmd callback when the container exits.

Relevant log output

failed after 61.8 s: Timed out waiting for log output matching '.*never.*'

Additional Information

Maybe related: #11860

Activity

  1. sygkanrt30 commented on Nov 4, 2026

    @sygkanrt30

    Hi @kiview! I'd like to take this issue if nobody is working on it.
    Can you assign it to me? Thanks!

  2. Inf1mum commented on Nov 4, 2026

    @Inf1mum

    Hi! I'd like to pick this up.

    Proposed approach: fail fast as soon as the target is no longer running, reusing the Awaitility failFast mechanism that HostPortWaitStrategy already uses:

    .failFast("container is no longer running", () -> !waitStrategyTarget.isRunning())

    The existing WaitingConsumer.waitUntil(...) call would run unchanged on AbstractWaitStrategy.EXECUTOR (same startup timeout, same times semantics), while the calling thread polls isRunning() alongside it. If the container exits before a match, the wait is aborted with Container stopped before the expected log output matching '<regEx>' was produced instead of burning the full startup timeout. A short grace period lets output buffered right before the exit still match, so a container that prints the expected message and then exits keeps succeeding.

    We considered detecting OutputFrame.END in the wait predicate instead, but frames are polled from the buffer newest-first (pollLast in WaitingConsumer), so END can be examined before earlier buffered frames — aborting on it could false-fail a container whose expected output is still in the buffer. Checking isRunning() avoids that.

    For the repro from the issue (alpine printing started, then exit 1 after ~2s, waiting for .*never.* with a 60s timeout), start() fails in a few seconds instead of after the full timeout. Timeout semantics for a still-running container are unchanged.

    PR to follow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions