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
.
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
.