Repository navigation
feat: Wait for the test container up to a configurable startup timeout, replace one that does not become reachable, and remove one whose start fails. - #24
Merged
Conversation
…t, replace one that does not become reachable, and remove one whose start fails. start() now follows the start contract that all client SDKs share: withStartupTimeout(Duration) sets the time per container, 30 seconds by default, counted from creating the container, without pulling the image. The ping must answer 200, and the port is read anew for every ping. A container that runs but does not become reachable is replaced, up to 3 containers. A container that exits, or a Docker error, fails at once. Each attempt labels its container, and a failed attempt removes the containers with its label. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QykegdHe7vhHMBN3kWxgfH
…ted, and reject a startup timeout that is not positive. An interrupt now cancels the start: start() waits for it to give up for a grace period of five seconds at most, keeps the interrupt status, and throws an IllegalStateException with the message "failed to start the container: interrupted". What the start has not removed by then, e.g. a container that Docker is still creating, it removes in the background. withStartupTimeout rejects zero and negative durations with an IllegalArgumentException, like the stall timeout of the client. The Javadoc of start() keeps the exceptions it named in 2.0, and says that a container that exits before Docker publishes its port fails only after the fixed wait of Testcontainers for the port. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QykegdHe7vhHMBN3kWxgfH
…pass errors of the start on as they are, and let an interrupted start finish before the next start or stop. While Testcontainers reuses containers, start() gives the container no label of its attempt, which would keep Testcontainers from finding it again, and neither replaces nor removes it. An error that ends the start reaches the caller as it is, instead of wrapped in a CompletionException. A thread that is interrupted already does not even pull the image, and an interrupt while pulling keeps the start from creating a container. start() and stop() wait, for 30 seconds at most, for what an interrupted start still does in the background. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QykegdHe7vhHMBN3kWxgfH
…e the containers of a start that an interrupt ended once more later. start() runs on the thread of the caller again, so that modifiers run where Testcontainers runs them, and relies on docker-java ending a call that is pending once the thread is interrupted. After an interrupt, the start clears the interrupt status, removes the containers of its attempt by their label, restores the status, and throws. As Docker may still create a container for a call to create one that the interrupt cut short, a task in the background removes the containers of the attempt once more, five seconds later. It knows the attempt only by its label, and touches nothing else, so start() and stop() no longer wait for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QykegdHe7vhHMBN3kWxgfH
…st fails before it is created. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QykegdHe7vhHMBN3kWxgfH
…ners pulls the image can get lost. Testcontainers catches an interrupt while it waits for a pull, which clears the interrupt status, and pulls the image again. An interrupt 700 ms into a real pull of an image that was not there let the start succeed, with the interrupt status cleared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QykegdHe7vhHMBN3kWxgfH
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the common start contract for the test container.
startpingsGET /api/v1/pinguntil it answers 200, and reads the mapped port anew from Docker on every poll.withStartupTimeout(Duration)now changes it, and rejects zero and negative durations like the stall timeout of the client; before,configure()replaced the wait, so the setting had no effect.io.eventsourcingdb.container-start=<random id>, and a failed attempt removes every container with that label, also one that Testcontainers does not know of. A failed removal is added as a suppressed exception, so the message stays as it is. While Testcontainers reuses containers (withReuse(true)and the environment allow it), the start adds no label, and neither replaces nor removes the container, as Testcontainers' reuse does not.startruns on the caller's thread, so modifiers run where Testcontainers runs them, and an interrupt ends a pending call to Docker at once. The start then clears the interrupt status, so that its cleanup is not cut short, removes the attempt's containers by their label, restores the status, and throws anIllegalStateExceptionwithfailed to start the container: interrupted. As Docker may still create a container for a create call that the interrupt cut short, a background task removes the attempt's containers once more five seconds later, by the label alone. A thread that is interrupted already fails before the pull. An error that ends the start reaches the caller as it is.The tests use real Docker with short startup timeouts, plus unit tests for the wait and the ping. Mutations of each part of the change (accepting any 2xx, reading the port once, counting the pull, starting the clock at the wait, counting a Docker error as unreachable, skipping the label removal or the delayed one, keeping the interrupt status during cleanup, labeling a reused container) each make a test fail.
make qais green locally againstlatestandpreview.🤖 Generated with Claude Code
https://claude.ai/code/session_01QykegdHe7vhHMBN3kWxgfH