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

ForkJoin thread hangs indefinitely if exception is thrown in worker thread

XMLWordPrintable

      A DESCRIPTION OF THE PROBLEM :
      When executing Arrays.parallelSort() with a custom Comparator and this Comparator can throw an unchecked exception in its compare()-Method the exception is swallowed, the worker dies (or stops work) and the thread calling parallelSort() waits indefinitely for all workers to complete the task.
      Adding a UncaughtExceptionHandler to the Common Pool accomplishes nothing as it is not used by the framework.

      The behaviour is present in versions 11 to 23. Only the LTS versions were tested, however.

      Excerpts from generated thread dumps:
      JDK 23
      "SorterThread" #29 [9108] prio=5 os_prio=0 cpu=15.62ms elapsed=14.11s tid=0x0000026545d9f5e0 nid=9108 waiting on condition [0x00000067eddfe000]
         java.lang.Thread.State: WAITING (parking)
      at jdk.internal.misc.Unsafe.park(java.base@23/Native Method)
      - parking to wait for <0x000000011db8b128> (a java.util.ArraysParallelSortHelpers$FJObject$Sorter)
      at java.util.concurrent.locks.LockSupport.park(java.base@23/LockSupport.java:371)
      at java.util.concurrent.ForkJoinTask.awaitDone(java.base@23/ForkJoinTask.java:441)
      at java.util.concurrent.ForkJoinTask.awaitDone(java.base@23/ForkJoinTask.java:496)
      at java.util.concurrent.ForkJoinTask.join(java.base@23/ForkJoinTask.java:662)
      at java.util.concurrent.ForkJoinTask.invoke(java.base@23/ForkJoinTask.java:677)
      at java.util.Arrays.parallelSort(java.base@23/Arrays.java:913)
      at Main$SorterThread.run(Main.java:41)

         Locked ownable synchronizers:
      - None

      JDK 21.0.2
      "SorterThread" #29 [2752] prio=5 os_prio=0 cpu=0.00ms elapsed=15.47s tid=0x0000026e0c8cd0f0 nid=2752 waiting on condition [0x00000082f5fff000]
         java.lang.Thread.State: WAITING (parking)
      at jdk.internal.misc.Unsafe.park(java.base@21.0.2/Native Method)
      - parking to wait for <0x000000011db89b98> (a java.util.ArraysParallelSortHelpers$FJObject$Sorter)
      at java.util.concurrent.locks.LockSupport.park(java.base@21.0.2/LockSupport.java:371)
      at java.util.concurrent.ForkJoinTask.awaitDone(java.base@21.0.2/ForkJoinTask.java:461)
      at java.util.concurrent.ForkJoinTask.invoke(java.base@21.0.2/ForkJoinTask.java:668)
      at java.util.Arrays.parallelSort(java.base@21.0.2/Arrays.java:914)
      at Main$SorterThread.run(Main.java:42)

         Locked ownable synchronizers:
      - None

      JDK 17.0.2
      "SorterThread" #23 prio=5 os_prio=0 cpu=0.00ms elapsed=10.39s tid=0x000001bb685d6b20 nid=0x2f84 waiting on condition [0x0000000f8f3ff000]
         java.lang.Thread.State: WAITING (parking)
      at jdk.internal.misc.Unsafe.park(java.base@17.0.2/Native Method)
      - parking to wait for <0x00000000ff3dadb0> (a java.util.ArraysParallelSortHelpers$FJObject$Sorter)
      at java.util.concurrent.locks.LockSupport.park(java.base@17.0.2/LockSupport.java:341)
      at java.util.concurrent.ForkJoinTask.awaitDone(java.base@17.0.2/ForkJoinTask.java:468)
      at java.util.concurrent.ForkJoinTask.invoke(java.base@17.0.2/ForkJoinTask.java:687)
      at java.util.Arrays.parallelSort(java.base@17.0.2/Arrays.java:913)
      at Main$SorterThread.run(Main.java:41)

         Locked ownable synchronizers:
      - None

      JDK 11.0.2
      "SorterThread" #21 prio=5 os_prio=0 cpu=0.00ms elapsed=40.36s tid=0x0000020267d2b000 nid=0x77c in Object.wait() [0x00000025318ff000]
         java.lang.Thread.State: WAITING (on object monitor)
      at java.lang.Object.wait(java.base@11.0.2/Native Method)
      - waiting on <0x0000000101818d10> (a java.util.ArraysParallelSortHelpers$FJObject$Sorter)
      at java.util.concurrent.ForkJoinTask.externalAwaitDone(java.base@11.0.2/ForkJoinTask.java:330)
      - waiting to re-lock in wait() <0x0000000101818d10> (a java.util.ArraysParallelSortHelpers$FJObject$Sorter)
      at java.util.concurrent.ForkJoinTask.doInvoke(java.base@11.0.2/ForkJoinTask.java:412)
      at java.util.concurrent.ForkJoinTask.invoke(java.base@11.0.2/ForkJoinTask.java:736)
      at java.util.Arrays.parallelSort(java.base@11.0.2/Arrays.java:1122)
      at Main$SorterThread.run(Main.java:41)

         Locked ownable synchronizers:
      - None

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Execute Arrays.parallelSort() with a faulty Comparator throwing an exception

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Thread calling Arrays.parallelSort() receives thrown exception and terminates abnormally
      ACTUAL -
      Thread calling Arrays.parallelSort() waits idefinitely for ForkJoin-workers to terminate

      ---------- BEGIN SOURCE ----------
      import java.time.Duration;
      import java.time.temporal.ChronoUnit;
      import java.util.*;
      import java.util.concurrent.ThreadLocalRandom;
      import java.util.concurrent.TimeUnit;
      import java.util.concurrent.ForkJoinPool;
      import java.lang.Thread.UncaughtExceptionHandler;

      public class Main {

          public static void main(String[] args) throws Exception {
      UncaughtExceptionHandler handler = ForkJoinPool.commonPool().getUncaughtExceptionHandler();
      System.out.printf("UEH set to %s%n", handler);

              int size = 20_000;
              var strings = new String[size];
              for (int i = 0; i < size; i++) {
                  strings[i] = Integer.toString(ThreadLocalRandom.current().nextInt(size));
              }

              var t = new SorterThread(strings);
              t.start();
              TimeUnit.of(ChronoUnit.SECONDS).sleep(3);

              var terminated = false;
              while (!terminated) {
                  t.join(1000L);
      terminated = !t.isAlive();
                  if (!terminated) {
                      System.out.printf("Thread %s has not terminated, is in state %s%n", t.getName(), t.getState());
                  }
              }
          }

          private static class SorterThread extends Thread {

              private final String[] strings;

              SorterThread(String[] strings) {
                  super("SorterThread");
                  this.strings = strings;
              }

              @Override
              public void run() {
                  Arrays.parallelSort(strings, new StringComparator(getName()));
              }
          }

          private static class StringComparator implements Comparator<String> {

              private final String parentThread;

              StringComparator(String parentThread) {
                  this.parentThread = parentThread;
              }

              @Override
              public int compare(String o1, String o2) {
                  if (!Thread.currentThread().getName().equals(parentThread) && ThreadLocalRandom.current().nextInt(10) < 1) {
                      throw new RuntimeException("Killing ForkJoinThread");
                  }
                  return o1.compareTo(o2);
              }
          }

      public static class ExceptionHandler implements UncaughtExceptionHandler {

      @Override
      public void uncaughtException(Thread t, Throwable e) {
      System.out.printf("Uncaught exception in Thread %s: %s%n", t.getName(), e.getMessage());
      }
      }
      }

      ---------- END SOURCE ----------

      FREQUENCY : always


            vklang Viktor Klang
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: