11package org .testcontainers .utility ;
22
3+ import com .github .dockerjava .api .DockerClient ;
34import com .github .dockerjava .api .model .Container ;
45import com .github .dockerjava .api .model .PruneType ;
6+ import org .slf4j .Logger ;
7+ import org .slf4j .LoggerFactory ;
58
9+ import java .util .ArrayList ;
610import java .util .Arrays ;
11+ import java .util .Collections ;
712import java .util .List ;
813import java .util .Map ;
914
1318 */
1419class JVMHookResourceReaper extends ResourceReaper {
1520
21+ private static final Logger LOGGER = LoggerFactory .getLogger (JVMHookResourceReaper .class );
22+
1623 @ Override
1724 public void init () {
1825 setHook ();
1926 }
2027
28+ /**
29+ * Perform a cleanup.
30+ * @deprecated no longer supported API, use {@link DockerClient} directly
31+ */
2132 @ Override
33+ @ Deprecated
2234 public synchronized void performCleanup () {
2335 super .performCleanup ();
2436 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 ));
37+ tryPrune (PruneType .CONTAINERS );
38+ tryPrune (PruneType .NETWORKS );
39+ tryPrune (PruneType .VOLUMES );
40+ tryPrune (PruneType .IMAGES );
41+ }
42+ }
43+
44+ private void tryPrune (PruneType pruneType ) {
45+ try {
46+ DEATH_NOTE .forEach (filters -> prune (pruneType , filters ));
47+ } catch (Exception e ) {
48+ LOGGER .warn ("Exception pruning {} resources: {}" , pruneType , e .getMessage (), e );
2949 }
3050 }
3151
@@ -35,28 +55,45 @@ private void prune(PruneType pruneType, List<Map.Entry<String, String>> filters)
3555 .filter (it -> "label" .equals (it .getKey ()))
3656 .map (Map .Entry ::getValue )
3757 .toArray (String []::new );
38- switch (pruneType ) {
58+
59+ if (pruneType == PruneType .CONTAINERS ) {
3960 // Docker only prunes stopped containers, so we have to do it manually
40- case CONTAINERS :
41- List <Container > containers = dockerClient
42- .listContainersCmd ()
43- .withFilter ("label" , Arrays .asList (labels ))
44- .withShowAll (true )
45- .exec ();
46-
47- containers
48- .parallelStream ()
49- .forEach (container -> {
50- dockerClient
51- .removeContainerCmd (container .getId ())
52- .withForce (true )
53- .withRemoveVolumes (true )
54- .exec ();
55- });
56- break ;
57- default :
58- dockerClient .pruneCmd (pruneType ).withLabelFilter (labels ).exec ();
59- break ;
61+ removeContainers (labels );
62+ } else {
63+ dockerClient .pruneCmd (pruneType ).withLabelFilter (labels ).exec ();
64+ }
65+ }
66+
67+ private void removeContainers (String [] labels ) {
68+ List <Container > containers = listContainers (labels );
69+ int retries = 5 ;
70+
71+ while (!containers .isEmpty () && retries -- > 0 ) {
72+ List <Throwable > errors = new ArrayList <>();
73+
74+ containers .parallelStream ().forEach (container -> removeContainer (container , errors ));
75+
76+ if (errors .isEmpty ()) {
77+ containers = Collections .emptyList ();
78+ } else if (retries < 1 ) {
79+ RuntimeException removeError = new RuntimeException ("Error removing one or more containers" );
80+ errors .forEach (removeError ::addSuppressed );
81+ throw removeError ;
82+ } else {
83+ containers = listContainers (labels );
84+ }
85+ }
86+ }
87+
88+ private List <Container > listContainers (String [] labels ) {
89+ return dockerClient .listContainersCmd ().withFilter ("label" , Arrays .asList (labels )).withShowAll (true ).exec ();
90+ }
91+
92+ private void removeContainer (Container container , List <Throwable > errors ) {
93+ try {
94+ dockerClient .removeContainerCmd (container .getId ()).withForce (true ).withRemoveVolumes (true ).exec ();
95+ } catch (Exception e ) {
96+ errors .add (e );
6097 }
6198 }
6299}
0 commit comments