diff --git a/core/src/main/java/org/testcontainers/containers/GenericContainer.java b/core/src/main/java/org/testcontainers/containers/GenericContainer.java index 0fe944433ae..f7954db6369 100644 --- a/core/src/main/java/org/testcontainers/containers/GenericContainer.java +++ b/core/src/main/java/org/testcontainers/containers/GenericContainer.java @@ -169,7 +169,7 @@ public class GenericContainer> */ @Setter(AccessLevel.NONE) @VisibleForTesting - String containerId; + volatile String containerId; @Setter(AccessLevel.NONE) private InspectContainerResponse containerInfo; @@ -307,7 +307,7 @@ public String getContainerId() { */ @Override @SneakyThrows({ InterruptedException.class, ExecutionException.class }) - public void start() { + public synchronized void start() { if (containerId != null) { return; } @@ -634,7 +634,7 @@ private void connectToPortForwardingNetwork(String networkMode) { * Kill and remove the container. */ @Override - public void stop() { + public synchronized void stop() { if (containerId == null) { return; } diff --git a/modules/clickhouse/src/main/java/org/testcontainers/clickhouse/ClickHouseR2DBCDatabaseContainer.java b/modules/clickhouse/src/main/java/org/testcontainers/clickhouse/ClickHouseR2DBCDatabaseContainer.java index be6d8af67c2..9b703fbb9cb 100644 --- a/modules/clickhouse/src/main/java/org/testcontainers/clickhouse/ClickHouseR2DBCDatabaseContainer.java +++ b/modules/clickhouse/src/main/java/org/testcontainers/clickhouse/ClickHouseR2DBCDatabaseContainer.java @@ -24,12 +24,12 @@ public static ConnectionFactoryOptions getOptions(ClickHouseContainer container) } @Override - public void start() { + public synchronized void start() { this.container.start(); } @Override - public void stop() { + public synchronized void stop() { this.container.stop(); } diff --git a/modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/ParallelDependsOnTest.java b/modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/ParallelDependsOnTest.java new file mode 100644 index 00000000000..5c1d7f0b778 --- /dev/null +++ b/modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/ParallelDependsOnTest.java @@ -0,0 +1,66 @@ +package org.testcontainers.junit.jupiter; + +import org.junit.jupiter.api.Test; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.lifecycle.Startables; +import org.testcontainers.utility.DockerImageName; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies that concurrent {@link GenericContainer#start()} calls on the same + * container do not result in {@link GenericContainer#doStart()} being + * called more than once. + * + *

A background thread calls {@code container.start()} during class initialization, + * racing with the extension's {@link Startables#deepStart(Stream)} which also starts + * the container as part of the {@code @Container} lifecycle.

+ */ +@Testcontainers(parallel = true) +class ParallelDependsOnTest { + static { + // Pre-initialize the Docker client to increase the chance of a race + // and to emulate a test suite where an earlier test class already + // triggered the initialization. + DockerClientFactory.instance().client(); + } + + private static final AtomicInteger doStartCount = new AtomicInteger(); + + @Container + private static final StartCountingContainer container = new StartCountingContainer( + JUnitJupiterTestImages.HTTPD_IMAGE, + doStartCount + ); + + static { + // Race with the extension's Startables.deepStart. + new Thread(() -> container.start()).start(); + } + + @Test + void containerShouldBeStartedOnlyOnce() { + assertThat(container.isRunning()).isTrue(); + assertThat(doStartCount).as("doStart() invocations").hasValue(1); + } + + private static class StartCountingContainer extends GenericContainer { + + private final AtomicInteger doStartCount; + + StartCountingContainer(DockerImageName image, AtomicInteger doStartCount) { + super(image); + this.doStartCount = doStartCount; + } + + @Override + protected void doStart() { + doStartCount.incrementAndGet(); + super.doStart(); + } + } +} diff --git a/modules/mariadb/src/main/java/org/testcontainers/mariadb/MariaDBR2DBCDatabaseContainer.java b/modules/mariadb/src/main/java/org/testcontainers/mariadb/MariaDBR2DBCDatabaseContainer.java index 4c9e6350d23..e840d659799 100644 --- a/modules/mariadb/src/main/java/org/testcontainers/mariadb/MariaDBR2DBCDatabaseContainer.java +++ b/modules/mariadb/src/main/java/org/testcontainers/mariadb/MariaDBR2DBCDatabaseContainer.java @@ -42,12 +42,12 @@ public Set getDependencies() { } @Override - public void start() { + public synchronized void start() { this.container.start(); } @Override - public void stop() { + public synchronized void stop() { this.container.stop(); } diff --git a/modules/mssqlserver/src/main/java/org/testcontainers/mssqlserver/MSSQLR2DBCDatabaseContainer.java b/modules/mssqlserver/src/main/java/org/testcontainers/mssqlserver/MSSQLR2DBCDatabaseContainer.java index d8d7740e01d..7543a2863de 100644 --- a/modules/mssqlserver/src/main/java/org/testcontainers/mssqlserver/MSSQLR2DBCDatabaseContainer.java +++ b/modules/mssqlserver/src/main/java/org/testcontainers/mssqlserver/MSSQLR2DBCDatabaseContainer.java @@ -43,12 +43,12 @@ public Set getDependencies() { } @Override - public void start() { + public synchronized void start() { this.container.start(); } @Override - public void stop() { + public synchronized void stop() { this.container.stop(); } diff --git a/modules/mysql/src/main/java/org/testcontainers/mysql/MySQLR2DBCDatabaseContainer.java b/modules/mysql/src/main/java/org/testcontainers/mysql/MySQLR2DBCDatabaseContainer.java index d2a272559d9..b5a08ff6708 100644 --- a/modules/mysql/src/main/java/org/testcontainers/mysql/MySQLR2DBCDatabaseContainer.java +++ b/modules/mysql/src/main/java/org/testcontainers/mysql/MySQLR2DBCDatabaseContainer.java @@ -42,12 +42,12 @@ public Set getDependencies() { } @Override - public void start() { + public synchronized void start() { this.container.start(); } @Override - public void stop() { + public synchronized void stop() { this.container.stop(); } diff --git a/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleR2DBCDatabaseContainer.java b/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleR2DBCDatabaseContainer.java index ae480027f25..a178be27539 100644 --- a/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleR2DBCDatabaseContainer.java +++ b/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleR2DBCDatabaseContainer.java @@ -41,12 +41,12 @@ public Set getDependencies() { } @Override - public void start() { + public synchronized void start() { this.container.start(); } @Override - public void stop() { + public synchronized void stop() { this.container.stop(); } diff --git a/modules/postgresql/src/main/java/org/testcontainers/postgresql/PostgreSQLR2DBCDatabaseContainer.java b/modules/postgresql/src/main/java/org/testcontainers/postgresql/PostgreSQLR2DBCDatabaseContainer.java index d99d638b100..cb815c2d23f 100644 --- a/modules/postgresql/src/main/java/org/testcontainers/postgresql/PostgreSQLR2DBCDatabaseContainer.java +++ b/modules/postgresql/src/main/java/org/testcontainers/postgresql/PostgreSQLR2DBCDatabaseContainer.java @@ -42,12 +42,12 @@ public Set getDependencies() { } @Override - public void start() { + public synchronized void start() { this.container.start(); } @Override - public void stop() { + public synchronized void stop() { this.container.stop(); } diff --git a/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java b/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java index 53ee8ba5577..2636c9efe1d 100644 --- a/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java +++ b/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java @@ -351,7 +351,7 @@ public void afterTest(TestDescription description, Optional throwable } @Override - public void stop() { + public synchronized void stop() { if (driver != null) { try { driver.quit(); diff --git a/modules/selenium/src/main/java/org/testcontainers/selenium/BrowserWebDriverContainer.java b/modules/selenium/src/main/java/org/testcontainers/selenium/BrowserWebDriverContainer.java index 97ac23f5d55..ff11b5df6a3 100644 --- a/modules/selenium/src/main/java/org/testcontainers/selenium/BrowserWebDriverContainer.java +++ b/modules/selenium/src/main/java/org/testcontainers/selenium/BrowserWebDriverContainer.java @@ -202,7 +202,7 @@ public void afterTest(TestDescription description, Optional throwable } @Override - public void stop() { + public synchronized void stop() { if (vncRecordingContainer != null) { try { vncRecordingContainer.stop();