Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8313630

Arrays.parallelSort hangs indefinitely when invoked from multiple threads of ForkJoinPool.commonPool() and array length is > 8192

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Won't Fix
    • Icon: P4 P4
    • None
    • 17
    • core-libs

      Consider the following trivial code (also attached as Test.java). This code does the following:

      - Uses a Long[] of size 8193 (or anything above 8192). It's important that a non-primitive array be used, because the issue isn't reproducible for primitive arrays due to a different implementation code path in Arrays.parallelSort(). It's also important that the number of elements in the array be greater than 1 << 13 (i.e. 8192), because the Arrays.parallelSort() follows a different code path when the elements are lesser or equal to that value and the issue isn't reproduced in such cases. When the element count is 8193 or higher it uses an instance of java.util.ArraysParallelSortHelpers.FJObject.Sorter which is a java.util.concurrent.CountedCompleter to do the sorting and it's only then that the issue is reproduced.
      - Uses ForkJoinPool.commonPool() as a ExecutorService and submits N (= 10) tasks to this executor
      - Each task only just calls Arrays.parallelSort() on that Long[] array.
      - Waits for all these tasks to complete. If the tasks don't complete in 1 MINUTE then the program dumps stacktrace of all live threads and exits with an error code. The 1 MINUTE timeout is just artificial to automate this issue. In reality the test will hang forever, when this issue is reproduced.

      Most of the runs this test completes successfully i.e. finishes the Arrays.parallelSort() instantly and every task completes successfully. However, on some occasions the Arrays.parallelSort() never completes in any of the threads and the CPU usage is consistently at 100%. Thread dump has been included later in this description when the issue is reproduced.

      Interestingly this only affect Java 17. I have tried to reproduce this on Java 11, 19, 20, 21 and mainline and it never hangs in any of these versions. It's only on Java 17 where it is consistenly reproducible. To be able to automate reproducing the issue, I have a (bash script) which continuously launches the java program until it hangs. The script is attached to this issue.

      To reproduce the issue, download both the run-test.sh and Test.java in some directory and then first set JAVA_HOME to JDK 17 installation and run:

      ./run-test.sh

      It will continue to run till the issue is reproduced and when the issue is reproduced, please check the contents of out.log which has all necessary details captured. That includes details from `-XshowSettings` and the program launch count (i.e. the number of launches it took to reproduce the issue) and the parallelism in use by ForkJoinPool and the complete thread dump when the code hung.

      Here's the trivial code that reproduces it:

      import java.util.concurrent.*;
      import java.util.*;
      import java.util.stream.*;

      public class Test {

          public static void main(final String[] args) throws Exception {
              System.out.println("Program launch count: " + args[0]);
              // anything <= 8192 doesn't reproduce the issue given the implementation in Arrays.parallelSort()
              final int numElements = 8193;
              final Long[] data = new Long[numElements];
              for (int i = 0; i < data.length; i++) {
                  data[i] = Long.valueOf(i);
              }
              // fire off tasks using ForkJoinPool.commonPool(). the tasks will operate
              // over the map and use a parallel sorted stream to do some computation
              final int numTasks = 10;
              final ForkJoinPool executor = ForkJoinPool.commonPool();
              System.out.println("ForkJoinPool parallelism: " + executor.getParallelism());
              final List<Future<Void>> results = new ArrayList<>();
              for (int i = 0; i < numTasks; i++) {
                  final Future<Void> f = executor.submit(new Task(data));
                  results.add(f);
              }

              // wait for results
              try {
                  for (final Future<Void> result : results) {
                      result.get(1, TimeUnit.MINUTES);
                  }
              } catch (TimeoutException e) {
                  System.err.println("FAILURE - Tasks did not complete in expected time, this indicates a hang, thread dump available below:");
                  dumpAllThreadStackTrace();
                  System.exit(1); // indicate a failed execution
              }
              System.out.println("Done");
          }

          private static final class Task implements Callable<Void> {
              private final Long[] data;

              private Task(final Long[] data) {
                  this.data = data;
              }

              @Override
              public Void call() {
                  Arrays.parallelSort(data);
                  System.out.println("Computation completed in " + Thread.currentThread().getName());
                  return null;
              }
          }

          private static void dumpAllThreadStackTrace() {
              final Map<Thread, StackTraceElement[]> allThreadDumps = Thread.getAllStackTraces();
              final StringBuilder sb = new StringBuilder("Thread dump generated at " + new Date() + " on Java version: " + System.getProperty("java.version") + "\n");
              for (final Map.Entry<Thread, StackTraceElement[]> entry : allThreadDumps.entrySet()) {
                  final Thread t = entry.getKey();
                  sb.append("\n" + t.getName() + ":\n");
                  final StackTraceElement[] trace = entry.getValue();
                  if (trace.length == 0) {
                      sb.append("\t<no stacktrace>\n");
                  } else {
                      for (final StackTraceElement traceElement : trace) {
                          sb.append("\tat " + traceElement + "\n");
                      }
                  }
              }
              System.err.println(sb.toString());
          }

      }

      I'm on macOS M1 system but the original issue report from which this reproducer was conceived was on Oracle Linux 7 x64 system. The java version in use is:

      java -version
      java version "17.0.9" 2023-10-17 LTS
      Java(TM) SE Runtime Environment (build 17.0.9+4-LTS-194)
      Java HotSpot(TM) 64-Bit Server VM (build 17.0.9+4-LTS-194, mixed mode, sharing)

      Thread dump from this reproducer is as follows:

      Program launch count: 1
      ForkJoinPool parallelism: 7
      FAILURE - Tasks did not complete in expected time, this indicates a hang, thread dump available below:
      Thread dump generated at Wed Aug 02 20:51:07 IST 2023 on Java version: 17.0.9

      ForkJoinPool.commonPool-worker-1:
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.helpComplete(ForkJoinPool.java:1228)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.helpComplete(ForkJoinPool.java:1915)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.awaitDone(ForkJoinTask.java:433)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:687)
          at java.base@17.0.9/java.util.Arrays.parallelSort(Arrays.java:805)
          at app//Test$Task.call(Test.java:48)
          at app//Test$Task.call(Test.java:39)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1428)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
          at java.base@17.0.9/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

      ForkJoinPool.commonPool-worker-5:
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.helpComplete(ForkJoinPool.java:1233)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.helpComplete(ForkJoinPool.java:1915)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.awaitDone(ForkJoinTask.java:433)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:687)
          at java.base@17.0.9/java.util.Arrays.parallelSort(Arrays.java:805)
          at app//Test$Task.call(Test.java:48)
          at app//Test$Task.call(Test.java:39)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1428)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
          at java.base@17.0.9/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

      ForkJoinPool.commonPool-worker-3:
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.helpComplete(ForkJoinPool.java:1228)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.helpComplete(ForkJoinPool.java:1915)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.awaitDone(ForkJoinTask.java:433)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:687)
          at java.base@17.0.9/java.util.Arrays.parallelSort(Arrays.java:805)
          at app//Test$Task.call(Test.java:48)
          at app//Test$Task.call(Test.java:39)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1428)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
          at java.base@17.0.9/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

      ForkJoinPool.commonPool-worker-2:
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.helpComplete(ForkJoinPool.java:1228)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.helpComplete(ForkJoinPool.java:1915)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.awaitDone(ForkJoinTask.java:433)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:687)
          at java.base@17.0.9/java.util.Arrays.parallelSort(Arrays.java:805)
          at app//Test$Task.call(Test.java:48)
          at app//Test$Task.call(Test.java:39)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1428)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
          at java.base@17.0.9/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

      ForkJoinPool.commonPool-worker-4:
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.helpComplete(ForkJoinPool.java:1228)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.helpComplete(ForkJoinPool.java:1915)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.awaitDone(ForkJoinTask.java:433)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:687)
          at java.base@17.0.9/java.util.Arrays.parallelSort(Arrays.java:805)
          at app//Test$Task.call(Test.java:48)
          at app//Test$Task.call(Test.java:39)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1428)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
          at java.base@17.0.9/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

      Finalizer:
          at java.base@17.0.9/java.lang.Object.wait(Native Method)
          at java.base@17.0.9/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)
          at java.base@17.0.9/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:176)
          at java.base@17.0.9/java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:172)

      main:
          at java.base@17.0.9/java.lang.Thread.dumpThreads(Native Method)
          at java.base@17.0.9/java.lang.Thread.getAllStackTraces(Thread.java:1673)
          at app//Test.dumpAllThreadStackTrace(Test.java:55)
          at app//Test.main(Test.java:33)

      Reference Handler:
          at java.base@17.0.9/java.lang.ref.Reference.waitForReferencePendingList(Native Method)
          at java.base@17.0.9/java.lang.ref.Reference.processPendingReferences(Reference.java:253)
          at java.base@17.0.9/java.lang.ref.Reference$ReferenceHandler.run(Reference.java:215)

      ForkJoinPool.commonPool-worker-6:
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.helpComplete(ForkJoinPool.java:1228)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.helpComplete(ForkJoinPool.java:1915)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.awaitDone(ForkJoinTask.java:433)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:687)
          at java.base@17.0.9/java.util.Arrays.parallelSort(Arrays.java:805)
          at app//Test$Task.call(Test.java:48)
          at app//Test$Task.call(Test.java:39)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1428)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
          at java.base@17.0.9/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

      Common-Cleaner:
          at java.base@17.0.9/java.lang.Object.wait(Native Method)
          at java.base@17.0.9/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)
          at java.base@17.0.9/jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:140)
          at java.base@17.0.9/java.lang.Thread.run(Thread.java:842)
          at java.base@17.0.9/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:162)

      Signal Dispatcher:
          <no stacktrace>

      ForkJoinPool.commonPool-worker-7:
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.helpComplete(ForkJoinPool.java:1228)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.helpComplete(ForkJoinPool.java:1915)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.awaitDone(ForkJoinTask.java:433)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:687)
          at java.base@17.0.9/java.util.Arrays.parallelSort(Arrays.java:805)
          at app//Test$Task.call(Test.java:48)
          at app//Test$Task.call(Test.java:39)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1428)
          at java.base@17.0.9/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
          at java.base@17.0.9/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
          at java.base@17.0.9/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

      Notification Thread:
          <no stacktrace>


        1. JDK8313630.java
          3 kB
          Weibing Xiao
        2. run-test.sh
          0.3 kB
          Jaikiran Pai
        3. Test.java
          2 kB
          Jaikiran Pai

            wxiao Weibing Xiao
            jpai Jaikiran Pai
            Votes:
            0 Vote for this issue
            Watchers:
            7 Start watching this issue

              Created:
              Updated:
              Resolved: