javaschedulerscheduledexecutorserviceuncaughtexceptionhandler

SchduledExecutorService not executing UncaughtExceptionHandler when exception happens in the thread


I am using ScheduledExecutorService which uses ThreadFactory to create new threads. However when some exception occurs in the scheduled task, the UncaughtExceptionHandler in the threadfactory is not executed. Why is it happening?

Thread factory is as follows:

public class CustomThreadFactory {
  public static ThreadFactory defaultFactory(String namePrefix) {
    return new ThreadFactoryBuilder()
      .setNameFormat(String.format("%s-%%d", namePrefix))
      .setDaemon(false)
      .setUncaughtExceptionHandler(new CustomExceptionHandler())
      .build();
  }
}

The Exception handler:

public class CustomExceptionHandler implements Thread.UncaughtExceptionHandler {
  @Override
  public void uncaughtException(Thread thread, Throwable t) {
    log.error("Received uncaught exception {}", thread.getName(), t);
  }
}

The main function:

public static void main(String[] args) {
  ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(
          CustomThreadFactory.defaultFactory("scheduler"));
  ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 20L,
          TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1));

  scheduler.scheduleWithFixedDelay(
      () -> {
          executor.submit(() -> Thread.sleep(10000));
      },0,1,TimeUnit.SECONDS);
}

I am stating my exact issue so that it does not end up with being XY problem. In the above snippet, the blocking queue has size one and tasks are added to blocking queue every second. So on adding third task, the blocking queue executor gives RejectionExecutionException which is not printed by UncaughtExceptionHandler.


Solution

  • When a thread is about to terminate due to an uncaught exception the Java Virtual Machine will query the thread for its UncaughtExceptionHandler using Thread.getUncaughtExceptionHandler() and will invoke the handler's uncaughtException method, passing the thread and the exception as arguments.

    UncaughtExceptionHandler will be called if the submitted task throws an uncaught exception but RejectionExecutionException does not throw by the task itself. However, you can pass RejectedExecutionHandler to the ThreadPoolExecutor.

        public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
    

    ThreadPoolExecutor takes RejectedExecutionHandler as an argument.

    EDIT :

    In your case UncaughtExceptionHandler is not called/notified because when you call scheduleWithFixedDelay does not execute your Runnable directly however it warps it in ScheduledFutureTask. Now when executor.submit(() -> Thread.sleep(10000)); throws an exception (in your case RejectionExecutionException ) it does not remain uncaught because FutureTask#run caught the exception and set the exception as Future result/outcome. Look at the part of FutureTask#run:

    try {
        result = c.call();
        ran = true;
    } catch (Throwable ex) {
        result = null;
        ran = false;
        setException(ex);
    }
    

    And setException() method set exception object to the outcome of FutureTask.

    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }
    

    Now the question can we get the exception/notification of the Exception somehow?

    Yes, it is possible. When you call the Future#get on the Future the exception will be thrown however the Exception will be wrap in ExecutionException. Take a look at the Future#get method.

      public V get() throws InterruptedException, ExecutionException {
            int s = state;
            if (s <= COMPLETING)
                s = awaitDone(false, 0L);
            return report(s);
        }
    
        @SuppressWarnings("unchecked")
        private V report(int s) throws ExecutionException {
            Object x = outcome;
            if (s == NORMAL)
                return (V)x;
            if (s >= CANCELLED)
                throw new CancellationException();
            throw new ExecutionException((Throwable)x);
       }
    

    I have seen all the methods available (submit, execute, schedule etc) on ScheduledThreadPoolExecutor wrap the task in a future task. So I think there is no way to be in case of exception through UncaughtExceptionHandler.

    However in the case of ThreadPoolExecutor if you submit the task using ThreadPoolExecutor#submit your UncaughtExceptionHandler will not be notified but if you use ThreadPoolExecutor#execute then UncaughtExceptionHandler will be notified as ThreadPoolExecutor#execute does not wrap your task in Future.