Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java ForkJoinPool hangs in JDK17

The following code works with JDK16 reproducibly and hangs with JDK17 reproducibly on my laptop (4/8-core) with basic command line options: "-ea". A JDK-ticket exists (https://bugs.openjdk.org/browse/JDK-8281524), but there was disagreement whether the usage is "good" or not. Then, radio silence in the last 6 months. Can anybody help pinpoint my usage error (if any) and how to fix it?

import java.util.Arrays;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.Stream;
import org.junit.Test;

public class FjpTest {

    @Test
    public void testFjp() {
        final ForkJoinPool fjPool = new ForkJoinPool(1);
        final String[] objs = Stream.generate(() -> "").limit(10_000).toArray(String[]::new);
        // the following line should sort the array,
        // but instead causes a single-threaded spin-wait
        fjPool.invoke(ForkJoinTask.adapt(() -> Arrays.parallelSort(objs))); // this hangs!
    }
}

Update 1 (in response to Stephen C's comment):

Consider this example with considerable logic executed in a custom pool. This appears not only a "major loss of function" (hence "Priority=P3" on the Open-JDK ticket), because this worked in JDK16. It also displays an incompatibility within the JDK17 itself, because, we can't apparently use custom pools together with the collections framework anymore. I'm still not sure, how to solve this. The only thing I can conceive of is that anything that was originally designed to be run in the common pool must be explicitely submitted to the common pool, which would seem like a tough design choice. What am I overlooking?

    new ForkJoinPool(1).invoke(ForkJoinTask.adapt(() -> {
        // ... some really smart and deep business logic in a ginormous application using
        // a custom ForkJoinPool, when an innocent developer or a library for that matter 
        // (not even realizing that it runs inside a custom ForkJoinPool) decides
        // to use the collections framework ...
        Arrays.parallelSort(Stream.generate(() -> "").limit(10_000).toArray(String[]::new));
        // ... dead code from here ...
    }));
like image 609
Uwe Avatar asked Oct 20 '25 15:10

Uwe


1 Answers

My interpretation of what Doug Lea is saying on the ticket is that that cause of the problem is that your use-case is causing work sharing between two ForkJoinPool instances, and that was never guaranteed to work.

Now I can see why the sharing could be happening. The javadoc for public static <T extends Comparable<? super T>> void parallelSort(T[] a) state:

The ForkJoin common pool is used to execute any parallel tasks.

So by launching a task in a custom pool that calls parallelSort you are setting up the conditions for cross-pool work sharing.

The answer seems to be "don't do that". Don't call parallelSort from a task in a custom ForkJoinPool. The Arrays.parallelSort methods don't provide a way to specify the pool you want to use. Instead, just launch your tasks in the common pool.

I have tentatively confirmed this by changing:

    final ForkJoinPool fjPool = new ForkJoinPool(1);

in your example to

    final ForkJoinPool fjPool = ForkJoinPool.commonPool();

and it seems to work. This may not be the solution you want, but I think that it is the best you will get, unless you can convince Doug Lea et al that:

  • EITHER cross-pool work sharing should be supported,
  • OR a way should be provided to tell Arrays.parallelSort to use a specified ForkJoinPool.

Personally ... I'm not convinced that either of those fixes would be a good idea.

like image 65
Stephen C Avatar answered Oct 23 '25 05:10

Stephen C



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!