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

Uncaught exceptions from ScheduledThreadPoolExecutor.execute aren't reported

XMLWordPrintable

      ADDITIONAL SYSTEM INFORMATION :
      This is a bug in the implementation of ScheduledThreadPoolExecutor, and applies to all operating systems. I have verified that the bug exists in all current java releases from jdk8 through jdk14. It likely exists in much older releases as well.

      A DESCRIPTION OF THE PROBLEM :
      Uncaught exceptions from ScheduledThreadPoolExecutor.execute aren't reported to the uncaught exception handler and there is no future to check for failure. This behavior differs from ThreadPoolExecutor.execute which does notify the uncaught exception handler on failure.
      Note that this bug does not apply to submit, which returns a future and is handled correctly.

      Given a ThreadPoolExecutor we assume that uncaught exceptions are handled by `Thread.getUncaughtExceptionHandler()`
      when a future is not returned. There is a common misconception that ScheduledThreadPoolExecutor schedule methods should use the uncaught exception handler, however they return futures, so the uncaught exception handler is not (and should not be) called.

      The 'ScheduledThreadPoolExecutor.execute(Runnable)` method currently neither returns a future nor calls the
      `UncaughtExceptionHandler' because internally it delegates to
      'ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)' with a delay of zero, and ignores the return value.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      I have built a minimal reproducer here:
      https://github.com/carterkozak/scheduled-executor-execute-uncaught-reproducer

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      UncaughtExceptionHandler should be called with the throwable
      ACTUAL -
      The UncaughtExceptionHandler is not invoked, this is a void method so no future is returned to report the problem.

      ---------- BEGIN SOURCE ----------
      // from my reproducer: https://github.com/carterkozak/scheduled-executor-execute-uncaught-reproducer
      import java.util.List;
      import java.util.concurrent.CopyOnWriteArrayList;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ScheduledThreadPoolExecutor;
      import java.util.concurrent.ThreadFactory;
      import java.util.concurrent.TimeUnit;
      import java.util.concurrent.atomic.AtomicInteger;

      public final class ScheduledThreadPoolExecutorUncaughtException {

          public static void main(String[] args) throws InterruptedException {
              // Track uncaught exceptions for verification
              List<Throwable> uncaught = new CopyOnWriteArrayList<>();
              ExecutorService executor = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
                  private final AtomicInteger counter = new AtomicInteger();
                  @Override
                  public Thread newThread(Runnable runnable) {
                      Thread thread = Executors.defaultThreadFactory().newThread(runnable);
                      thread.setName("scheduled-" + counter.getAndIncrement());
                      thread.setUncaughtExceptionHandler((thread1, throwable) -> {
                          uncaught.add(throwable);
                          throwable.printStackTrace();
                      });
                      return thread;
                  }
              });
              try {
                  // Failure should be reported in some way.
                  // Using a ThreadPoolExecutor it will be reported to the UncaughtExceptionHandler
                  // (we can replace 'new ScheduledThreadPoolExecutor(' with 'Executors.newFixedThreadPool('
                  // to observe the expected behavior.
                  executor.execute(() -> { throw new RuntimeException(); });
              } finally {
                  executor.shutdown();
              }
              if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
                  throw new IllegalStateException("Executor failed to stop within 1 second");
              }
              // Task completion may race termination
              Thread.sleep(100);
              if (uncaught.size() != 1) {
                  System.err.println("Expected an uncaught exception but received: " + uncaught);
                  System.exit(1);
              } else {
                  System.out.println("Success");
              }
          }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      subclassing ScheduledThreadPoolExecutor.execute to wrap the runnable with a simple class to call the uncaught exception handler when exceptions are thrown. This only works in cases that use my executor, not executors used in third party libraries.

      FREQUENCY : always


            martin Martin Buchholz
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated: