Skip to content

[Bug]: GenericContainer.start() and stop() are not thread-safe #11719

@remal

Description

@remal

Module

Core

Testcontainers version

2.0.4

Using the latest Testcontainers version?

Yes

What happened?

GenericContainer.start() guards against double-start with if (containerId != null) return, but start() is not synchronized. When two threads call start() on the same container concurrently, both can pass the guard before either sets containerId, creating two Docker containers for one logical dependency.

This can happen when @Testcontainers is used and the container is also started from another context. For example, custom test infrastructure that handles @Container annotations alongside the JUnit extension:

@Testcontainers
class MyTest {

    // Custom infrastructure starts this container during context setup.
    // The @Testcontainers extension also starts it via Startables.deepStart().
    // Both run concurrently - the second start() should be a no-op, but without
    // synchronization both threads pass the containerId == null check.
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
}

The workaround is to not annotate dependencies with @Container when custom infrastructure already handles them, but this is not obvious to developers and error-prone.

I can't provide the exact scenario because we faced this in a closed-source project. The example above illustrates the general pattern.

Additional Information

I submitted a PR with a fix: #11702

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions