Skip to content

Commit 902cb03

Browse files
ssheikinfindepiJan Waś
committed
Prevent ghost containers after retries
reportLeakedContainers adapted from trinodb/trino#20297 trinodb/trino#21280 Co-authored-by: Piotr Findeisen <[email protected]> Co-authored-by: Jan Waś <[email protected]>
1 parent 4aa1b6c commit 902cb03

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

core/src/main/java/org/testcontainers/containers/GenericContainer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ private void tryStart() {
550550
} else {
551551
logger().error("There are no stdout/stderr logs available for the failed container");
552552
}
553+
stop();
553554
}
554555

555556
throw new ContainerLaunchException("Could not create/start container", e);

core/src/test/java/org/testcontainers/containers/GenericContainerTest.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
import com.github.dockerjava.api.DockerClient;
66
import com.github.dockerjava.api.command.InspectContainerResponse;
77
import com.github.dockerjava.api.command.InspectContainerResponse.ContainerState;
8+
import com.github.dockerjava.api.model.Container;
89
import com.github.dockerjava.api.model.ExposedPort;
910
import com.github.dockerjava.api.model.Info;
1011
import com.github.dockerjava.api.model.Ports;
12+
import com.google.common.base.MoreObjects;
13+
import com.google.common.collect.ImmutableList;
1114
import lombok.RequiredArgsConstructor;
1215
import lombok.SneakyThrows;
1316
import lombok.experimental.FieldDefaults;
@@ -28,9 +31,12 @@
2831
import org.testcontainers.utility.DockerImageName;
2932
import org.testcontainers.utility.MountableFile;
3033

34+
import java.time.Duration;
3135
import java.util.Arrays;
36+
import java.util.Collections;
3237
import java.util.List;
3338
import java.util.Map;
39+
import java.util.Optional;
3440
import java.util.concurrent.TimeUnit;
3541
import java.util.function.Predicate;
3642
import java.util.stream.Collectors;
@@ -42,6 +48,21 @@
4248

4349
public class GenericContainerTest {
4450

51+
@Test
52+
public void testStartupTimeoutWithAttemptsNotLeakingContainers() {
53+
try (
54+
GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)
55+
.withStartupAttempts(3)
56+
.waitingFor(
57+
Wait.forLogMessage("this text does not exist in logs", 1).withStartupTimeout(Duration.ofMillis(1))
58+
)
59+
.withCommand("tail", "-f", "/dev/null");
60+
) {
61+
assertThatThrownBy(container::start).hasStackTraceContaining("Retry limit hit with exception");
62+
}
63+
assertThat(reportLeakedContainers()).isEmpty();
64+
}
65+
4566
@Test
4667
public void shouldReportOOMAfterWait() {
4768
Info info = DockerClientFactory.instance().client().infoCmd().exec();
@@ -273,6 +294,46 @@ public void shouldRespectWaitStrategy() {
273294
}
274295
}
275296

297+
private static Optional<String> reportLeakedContainers() {
298+
@SuppressWarnings("resource") // Throws when close is attempted, as this is a global instance.
299+
DockerClient dockerClient = DockerClientFactory.lazyClient();
300+
301+
List<Container> containers = dockerClient
302+
.listContainersCmd()
303+
.withLabelFilter(
304+
Collections.singletonMap(
305+
DockerClientFactory.TESTCONTAINERS_SESSION_ID_LABEL,
306+
DockerClientFactory.SESSION_ID
307+
)
308+
)
309+
// ignore status "exited" - for example, failed containers after using `withStartupAttempts()`
310+
.withStatusFilter(Arrays.asList("created", "restarting", "running", "paused"))
311+
.exec()
312+
.stream()
313+
.collect(ImmutableList.toImmutableList());
314+
315+
if (containers.isEmpty()) {
316+
return Optional.empty();
317+
}
318+
319+
return Optional.of(
320+
String.format(
321+
"Leaked containers: %s",
322+
containers
323+
.stream()
324+
.map(container -> {
325+
return MoreObjects
326+
.toStringHelper("container")
327+
.add("id", container.getId())
328+
.add("image", container.getImage())
329+
.add("imageId", container.getImageId())
330+
.toString();
331+
})
332+
.collect(Collectors.joining(", ", "[", "]"))
333+
)
334+
);
335+
}
336+
276337
static class NoopStartupCheckStrategy extends StartupCheckStrategy {
277338

278339
@Override

0 commit comments

Comments
 (0)