Skip to content

Commit 257c89b

Browse files
authored
Start Ryuk lazily when in the reusable mode (#4938)
* Lazy Ryuk * Add `ResourceReaper#init`
1 parent 3ff0bb2 commit 257c89b

File tree

9 files changed

+201
-147
lines changed

9 files changed

+201
-147
lines changed

core/build.gradle

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ tasks.japicmp {
4040

4141
classExcludes = []
4242

43-
methodExcludes = []
43+
methodExcludes = [
44+
"org.testcontainers.utility.ResourceReaper#start(java.lang.String,com.github.dockerjava.api.DockerClient)",
45+
"org.testcontainers.utility.ResourceReaper#start(com.github.dockerjava.api.DockerClient)",
46+
"org.testcontainers.utility.ResourceReaper#registerNetworkForCleanup(java.lang.String)",
47+
"org.testcontainers.utility.ResourceReaper#removeNetworks(java.lang.String)",
48+
]
4449

4550
fieldExcludes = []
4651
}

core/src/main/java/org/testcontainers/DockerClientFactory.java

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131
import org.testcontainers.utility.ImageNameSubstitutor;
3232
import org.testcontainers.utility.MountableFile;
3333
import org.testcontainers.utility.ResourceReaper;
34+
import org.testcontainers.utility.RyukResourceReaper;
3435
import org.testcontainers.utility.TestcontainersConfiguration;
3536

3637
import java.io.ByteArrayOutputStream;
3738
import java.io.IOException;
3839
import java.io.InputStream;
3940
import java.net.URI;
4041
import java.util.ArrayList;
42+
import java.util.HashMap;
4143
import java.util.List;
4244
import java.util.Map;
4345
import java.util.Optional;
@@ -61,8 +63,7 @@ public class DockerClientFactory {
6163
public static final String SESSION_ID = UUID.randomUUID().toString();
6264

6365
public static final Map<String, String> DEFAULT_LABELS = ImmutableMap.of(
64-
TESTCONTAINERS_LABEL, "true",
65-
TESTCONTAINERS_SESSION_ID_LABEL, SESSION_ID
66+
TESTCONTAINERS_LABEL, "true"
6667
);
6768

6869
private static final DockerImageName TINY_IMAGE = DockerImageName.parse("alpine:3.14");
@@ -73,7 +74,7 @@ public class DockerClientFactory {
7374
DockerClientProviderStrategy strategy;
7475

7576
@VisibleForTesting
76-
DockerClient dockerClient;
77+
DockerClient client;
7778

7879
@VisibleForTesting
7980
RuntimeException cachedClientFailure;
@@ -174,21 +175,20 @@ public String getRemoteDockerUnixSocketPath() {
174175
*/
175176
@Synchronized
176177
public DockerClient client() {
177-
178-
if (dockerClient != null) {
179-
return dockerClient;
180-
}
181-
182178
// fail-fast if checks have failed previously
183179
if (cachedClientFailure != null) {
184180
log.debug("There is a cached checks failure - throwing", cachedClientFailure);
185181
throw cachedClientFailure;
186182
}
187183

184+
if (client != null) {
185+
return client;
186+
}
187+
188188
final DockerClientProviderStrategy strategy = getOrInitializeStrategy();
189189

190190
log.info("Docker host IP address is {}", strategy.getDockerHostIpAddress());
191-
final DockerClient client = new DockerClientDelegate() {
191+
client = new DockerClientDelegate() {
192192

193193
@Getter
194194
final DockerClient dockerClient = strategy.getDockerClient();
@@ -209,25 +209,14 @@ public void close() {
209209
" Operating System: " + dockerInfo.getOperatingSystem() + "\n" +
210210
" Total Memory: " + dockerInfo.getMemTotal() / (1024 * 1024) + " MB");
211211

212-
final String ryukContainerId;
213-
214-
boolean useRyuk = !Boolean.parseBoolean(System.getenv("TESTCONTAINERS_RYUK_DISABLED"));
215-
if (useRyuk) {
216-
log.debug("Ryuk is enabled");
217-
try {
218-
//noinspection deprecation
219-
ryukContainerId = ResourceReaper.start(client);
220-
} catch (RuntimeException e) {
221-
cachedClientFailure = e;
222-
throw e;
223-
}
224-
log.info("Ryuk started - will monitor and terminate Testcontainers containers on JVM exit");
225-
} else {
226-
log.debug("Ryuk is disabled");
227-
ryukContainerId = null;
228-
// best-efforts cleanup at JVM shutdown, without using the Ryuk container
212+
final ResourceReaper resourceReaper;
213+
try {
214+
resourceReaper = ResourceReaper.instance();
229215
//noinspection deprecation
230-
ResourceReaper.instance().setHook();
216+
resourceReaper.init();
217+
} catch (RuntimeException e) {
218+
cachedClientFailure = e;
219+
throw e;
231220
}
232221

233222
boolean checksEnabled = !TestcontainersConfiguration.getInstance().isDisableChecks();
@@ -237,6 +226,12 @@ public void close() {
237226
try {
238227
log.info("Checking the system...");
239228
checkDockerVersion(version.getVersion());
229+
230+
//noinspection deprecation
231+
String ryukContainerId = resourceReaper instanceof RyukResourceReaper
232+
? ((RyukResourceReaper) resourceReaper).getContainerId()
233+
: null;
234+
240235
if (ryukContainerId != null) {
241236
checkDiskSpace(client, ryukContainerId);
242237
} else {
@@ -261,8 +256,7 @@ public void close() {
261256
log.debug("Checks are disabled");
262257
}
263258

264-
dockerClient = client;
265-
return dockerClient;
259+
return client;
266260
}
267261

268262
private void checkDockerVersion(String dockerVersion) {
@@ -378,8 +372,10 @@ private <T> T runInsideDocker(DockerClient client, Consumer<CreateContainerCmd>
378372
final String tinyImage = ImageNameSubstitutor.instance().apply(TINY_IMAGE).asCanonicalNameString();
379373

380374
checkAndPullImage(client, tinyImage);
375+
HashMap<String, String> labels = new HashMap<>(DEFAULT_LABELS);
376+
labels.putAll(ResourceReaper.instance().getLabels());
381377
CreateContainerCmd createContainerCmd = client.createContainerCmd(tinyImage)
382-
.withLabels(DEFAULT_LABELS);
378+
.withLabels(labels);
383379
createContainerCmdConsumer.accept(createContainerCmd);
384380
String id = createContainerCmd.exec().getId();
385381

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ private void tryStart(Instant startedAt) {
364364
CreateContainerCmd createCommand = dockerClient.createContainerCmd(dockerImageName);
365365
applyConfiguration(createCommand);
366366

367-
createCommand.getLabels().put(DockerClientFactory.TESTCONTAINERS_LABEL, "true");
367+
createCommand.getLabels().putAll(DockerClientFactory.DEFAULT_LABELS);
368368

369369
boolean reused = false;
370370
final boolean reusable;
@@ -406,7 +406,8 @@ private void tryStart(Instant startedAt) {
406406
}
407407

408408
if (!reusable) {
409-
createCommand.getLabels().put(DockerClientFactory.TESTCONTAINERS_SESSION_ID_LABEL, DockerClientFactory.SESSION_ID);
409+
//noinspection deprecation
410+
createCommand.getLabels().putAll(ResourceReaper.instance().getLabels());
410411
}
411412

412413
if (!reused) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ private String create() {
8787
Map<String, String> labels = createNetworkCmd.getLabels();
8888
labels = new HashMap<>(labels != null ? labels : Collections.emptyMap());
8989
labels.putAll(DockerClientFactory.DEFAULT_LABELS);
90+
//noinspection deprecation
91+
labels.putAll(ResourceReaper.instance().getLabels());
9092
createNetworkCmd.withLabels(labels);
9193

9294
return createNetworkCmd.exec().getId();

core/src/main/java/org/testcontainers/images/builder/ImageFromDockerfile.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ public void onNext(BuildResponseItem item) {
112112
labels.putAll(buildImageCmd.getLabels());
113113
}
114114
labels.putAll(DockerClientFactory.DEFAULT_LABELS);
115+
//noinspection deprecation
116+
labels.putAll(ResourceReaper.instance().getLabels());
115117
buildImageCmd.withLabels(labels);
116118

117119
prePullDependencyImages(dependencyImageNames);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.testcontainers.utility;
2+
3+
import com.github.dockerjava.api.model.Container;
4+
import com.github.dockerjava.api.model.PruneType;
5+
6+
import java.util.Arrays;
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
/**
11+
* A {@link ResourceReaper} implementation that uses {@link Runtime#addShutdownHook(Thread)}
12+
* to cleanup containers.
13+
*/
14+
class JVMHookResourceReaper extends ResourceReaper {
15+
16+
@Override
17+
public void init() {
18+
setHook();
19+
}
20+
21+
@Override
22+
public synchronized void performCleanup() {
23+
super.performCleanup();
24+
synchronized (DEATH_NOTE) {
25+
DEATH_NOTE.forEach(filters -> prune(PruneType.CONTAINERS, filters));
26+
DEATH_NOTE.forEach(filters -> prune(PruneType.NETWORKS, filters));
27+
DEATH_NOTE.forEach(filters -> prune(PruneType.VOLUMES, filters));
28+
DEATH_NOTE.forEach(filters -> prune(PruneType.IMAGES, filters));
29+
}
30+
}
31+
32+
private void prune(PruneType pruneType, List<Map.Entry<String, String>> filters) {
33+
String[] labels = filters.stream()
34+
.filter(it -> "label".equals(it.getKey()))
35+
.map(Map.Entry::getValue)
36+
.toArray(String[]::new);
37+
switch (pruneType) {
38+
// Docker only prunes stopped containers, so we have to do it manually
39+
case CONTAINERS:
40+
List<Container> containers = dockerClient.listContainersCmd()
41+
.withFilter("label", Arrays.asList(labels))
42+
.withShowAll(true)
43+
.exec();
44+
45+
containers.parallelStream().forEach(container -> {
46+
dockerClient.removeContainerCmd(container.getId())
47+
.withForce(true)
48+
.withRemoveVolumes(true)
49+
.exec();
50+
});
51+
break;
52+
default:
53+
dockerClient.pruneCmd(pruneType).withLabelFilter(labels).exec();
54+
break;
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)