ADDITIONAL SYSTEM INFORMATION :
Bug reproduced on Mac/Windows/Linux for Java 11.0.14 and 11.0.12.
Does not happen on either Java 8 or 17.
A DESCRIPTION OF THE PROBLEM :
Sometimes calling ForkJoinPool.shutdownNow() results in the fork join pool getting stuck in a 'Terminating' state, but where it never actually gets to being 'Terminated'. ForkJoinPool.awaitTermination will hang until it times out and ForkJoinPool.isTerminated() returns false forever.
During that time exactly one ForkJoinWorkerThread is active and is stuck in a loop where it cycles between ForkJoinTask.internalWait(), ForkJoinPool.awaitJoin(), and ForkJoinPool.tryCompensate() where it creates and registers a new worker thread (which immediately gets terminated and garbage collected).
REGRESSION : Last worked in version 8u321
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the given code. It's some sort of race condition, so it won't fail every time, but it fails more than half the time for me.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Prints 16 lines to the console and terminates almost immediately.
ACTUAL -
Prints 16 lines to the console and then hangs indefinitely with the single ForkJoinWorkerThread behaving as described.
---------- BEGIN SOURCE ----------
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.IntStream;
import com.google.common.util.concurrent.Uninterruptibles;
public class TestForkJoin {
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(16);
IntStream.range(0, 16).forEach(i -> {
threadPool.submit(() -> testForkJoin());
});
Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
threadPool.shutdown();
threadPool.awaitTermination(3, TimeUnit.HOURS);
}
private static void testForkJoin() {
ForkJoinPool forkJoinPool = new ForkJoinPool(16);
AtomicBoolean done = new AtomicBoolean();
forkJoinPool.submit(() -> IntStream.range(0, 10000).parallel().forEach(i -> {
Uninterruptibles.sleepUninterruptibly(1, TimeUnit.MILLISECONDS);
if (done.compareAndSet(false, true)) {
System.out.println(forkJoinPool);
}
}));
Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
forkJoinPool.shutdown();
forkJoinPool.shutdownNow();
while (true) {
try {
if (forkJoinPool.isTerminated() || forkJoinPool.awaitTermination(1, TimeUnit.DAYS)) {
return;
}
} catch (InterruptedException e) {
Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
}
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
None found other than not calling shutdownNow().
FREQUENCY : occasionally