4

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 ...
    }));
Uwe
  • 85
  • 1
  • 5

1 Answers1

2

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.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Hi Stephen, thanks for your response! I did interpret Dougs response exactly the same way. The solution you propose is (what Doug would propose, I guess and is also) wrong from my perspective. Please consider the code in "update 1", which I don't know, how to solve. – Uwe Aug 25 '22 at 06:35
  • Which "update 1" are you talking about? – Stephen C Aug 25 '22 at 06:54
  • Anyway, I think I have answered your question: "Can anybody help pinpoint my usage error (if any) and how to fix it?" - The usage error is doing something that causes cross-pool work sharing, and the way to fix it is to not do that. (And I doubt that there is any other possible fix open to a regular Java 17 user, such as yourself.) Like I said, you might not like the solution. But it is what it is. – Stephen C Aug 25 '22 at 06:59
  • Ah ... I see now. I think that the answer is the same. Don't do it. And maybe give the poor innocent developer an icecream. (Adding emotive language doesn't change the problem.) The point is that you can jump up and down all you like, but unless you can convince the Java team that what you are doing *should* work, they most likely won't fix it. – Stephen C Aug 25 '22 at 07:08
  • Why? Because a fix 1) may not even be possible, and 2) is liable make the implementation more complex and harder to maintain. If you can prove them wrong by developing a way to make it work and showing them the code, they may be more open to persuasion. But the ball is in your court! – Stephen C Aug 25 '22 at 07:09
  • Thanks again, Stephen. I'm still having a hard time, because the current JDK17 implementation makes it essentially a risk to use the collections framework altogether, because you don't generally know, whether your code ends up running a custom ForJoinPool. "Don't do it" is not a solution, is it. Rather, I'm having a hard believing, that this is the intended design. (ps: "innocent developer" referred to somebody, who knows the rules (i.e. don't run Arrays.parallelSort in custom pool), but doesn't know the context that the rules will be applied to (i.e. we are potentially running a custom pool)) – Uwe Aug 25 '22 at 07:11
  • Well like I said. If you want a solution, the ball is in your court to develop one. – Stephen C Aug 25 '22 at 07:11
  • (Note: I'm just an observer. I'm just suggesting what I think are the only pragmatic solutions. Either live with the limitation, or fix it yourself ... and optionally share your solution.) – Stephen C Aug 25 '22 at 07:20