Skip to content

Commit 2ae334d

Browse files
authored
Merge pull request #1561 from rnorth/zombie-threads-poc
Zombie threads poc
2 parents a4591a4 + d17370b commit 2ae334d

2 files changed

Lines changed: 55 additions & 12 deletions

File tree

core/src/main/java/org/testcontainers/utility/ducttape/Timeouts.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,28 @@
99
*/
1010
public class Timeouts {
1111

12-
private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool(new ThreadFactory() {
12+
private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0);
1313

14-
final AtomicInteger threadCounter = new AtomicInteger(0);
14+
private static final ThreadFactory THREAD_FACTORY = r -> {
15+
Thread thread = new Thread(r, "ducttape-" + THREAD_COUNTER.getAndIncrement());
16+
thread.setDaemon(true);
17+
return thread;
18+
};
1519

16-
@Override
17-
public Thread newThread(Runnable r) {
18-
Thread thread = new Thread(r, "ducttape-" + threadCounter.getAndIncrement());
19-
thread.setDaemon(true);
20-
return thread;
20+
private static volatile ExecutorService executorService;
21+
22+
private static synchronized ExecutorService getExecutorService() {
23+
if (executorService == null || executorService.isShutdown()) {
24+
executorService = Executors.newCachedThreadPool(THREAD_FACTORY);
2125
}
22-
});
26+
return executorService;
27+
}
2328

24-
public static void shutdown() {
25-
EXECUTOR_SERVICE.shutdown();
29+
public static synchronized void shutdown() {
30+
if (executorService != null) {
31+
executorService.shutdown();
32+
executorService = null;
33+
}
2634
}
2735

2836
/**
@@ -40,7 +48,7 @@ public static <T> T getWithTimeout(final int timeout, final TimeUnit timeUnit, f
4048

4149
check("timeout must be greater than zero", timeout > 0);
4250

43-
Future<T> future = EXECUTOR_SERVICE.submit(lambda);
51+
Future<T> future = getExecutorService().submit(lambda);
4452
return callFuture(timeout, timeUnit, future);
4553
}
4654

@@ -57,7 +65,7 @@ public static void doWithTimeout(final int timeout, final TimeUnit timeUnit, fin
5765

5866
check("timeout must be greater than zero", timeout > 0);
5967

60-
Future<?> future = EXECUTOR_SERVICE.submit(lambda);
68+
Future<?> future = getExecutorService().submit(lambda);
6169
callFuture(timeout, timeUnit, future);
6270
}
6371

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.testcontainers.utility;
2+
3+
import org.junit.Test;
4+
import org.testcontainers.utility.ducttape.Timeouts;
5+
6+
import java.util.concurrent.TimeUnit;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
/**
11+
* Verifies that {@link Timeouts} works correctly across shutdown/reuse cycles.
12+
* After {@code shutdown()} the executor is re-created on next use.
13+
*/
14+
public class TimeoutsShutdownTest {
15+
16+
@Test
17+
public void timeoutsWorkAfterShutdown() {
18+
// First use
19+
String result1 = Timeouts.getWithTimeout(5, TimeUnit.SECONDS, () -> "container-1-ready");
20+
assertThat(result1).isEqualTo("container-1-ready");
21+
22+
// Shutdown (as GenericContainer.stop() does)
23+
Timeouts.shutdown();
24+
25+
// Second use — should transparently create a fresh executor
26+
String result2 = Timeouts.getWithTimeout(5, TimeUnit.SECONDS, () -> "container-2-ready");
27+
assertThat(result2).isEqualTo("container-2-ready");
28+
29+
// Shutdown and use again to confirm repeatable
30+
Timeouts.shutdown();
31+
32+
String result3 = Timeouts.getWithTimeout(5, TimeUnit.SECONDS, () -> "container-3-ready");
33+
assertThat(result3).isEqualTo("container-3-ready");
34+
}
35+
}

0 commit comments

Comments
 (0)