
Let this post be a little field report about exception handling in the Java Executor Framework.
The famous UncaughtExceptionHandler
One of the most useful things for exception handling in multithreaded systems (since Java 5) is the Thread.UncaughtExceptionHandler which can be used to handle unchecked exceptions being thrown from inside the run()-method of a Runnable. Using it in addition with the new Executor framework works like a charm.
Here we create an ExecutorService which should use our custom ThreadFactory implementation to (surprise, surprise) create new Threads.
final ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable target) {
final Thread thread = new Thread(target);
log.debug("Creating new worker thread");
thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("Uncaught Exception", e);
}
});
return thread;
}
};
final ExecutorService executor = Executors.newCachedThreadPool(factory);
If the service executes a task which fails due to an unchecked exception, our exception handler will log the failure. (I am avoiding the word “handle” here, because logging an exception should not be considered handling it, but that's another story.)
Scheduled task execution
It's getting interesting if you want to use a ScheduledExecutorService.
final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(factory);
scheduler.scheduleAtFixedRate(task, delay, period, unit);
Let's assume factory
is the same ThreadFactory as defined above. My first assumption was that my exception logging technique would work the same. Long story short, it didn't. Of course your factory will be used by the executor, but the uncaught exeception handler just never gets called, which makes all exceptions just disappear without notice - a debugging nightmare.
Have you seen my exception?
There is a totally different, and in my opinion not intuitive, way to receive exceptions when scheduling tasks for future executions.
final ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(task, delay, period, unit);
The scheduleAtFixedRate
method (like all schedule
and submit
-methods) returns a Future which represents a pending completion. You can use this object e.g. to fetch the state of the execution. In case of a scheduled task, you will receive a ScheduledFuture which adds another element to the nature of a Future. It does not only represent a pending completion, it also represents a pending execution. This does make totally sense in case there is a task which is scheduled to run exactly once. But in case of a periodic execution, the meaning of a pending completion is absurd, because the only thing we can assure is that it will never finish. It's like waiting for cron to stop running jobs. That's his job, we don't expect him to stop ever.
This code block shows how to actually receive the exception.
scheduler.execute(new Runnable() {
@Override
public void run() {
try {
future.get();
// dead code here?
} catch (InterruptedException e) {
log.error("Scheduled execution was interrupted", e);
} catch (CancellationException e) {
log.warn("Watcher thread has been cancelled", e);
} catch (ExecutionException e) {
log.error("Uncaught exception in scheduled execution", e.getCause());
}
}
});
We call future.get()
, which blocks until the execution completes (haha!). It will throw an ExecutionException in case an unchecked exception is thrown. That's what we were looking for, hooray!
The comment after the get
() call indicates where you can place code you never want to get executed. In case of a periodically scheduled execution, the future will never return normally. (You shouldn't read too much into the last part.) If the corresponding scheduled task gets removed from the schedule you will get the CancellationException. So again, the only way the jvm leaves the try-block is by throwing exceptions.
To make the above example work, it's necessary to make the scheduler
a threadpool, instead of a single thread. Otherwise the watcher thread will block at the get()
call and therefore prevent the scheduler from executing any other task.
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
Where is ScheduledExecutorService.awaitTermination()?
I would have expected a method called awaitTermination
(or something similar), which blocks until someone made the executor stop executing my task. The javadoc of get()
states:
Waits if necessary for the computation to complete, and then retrieves its result.
Everything after Waits is basically a lie when dealing with periodically scheduled tasks and should, in my opinion, be overriden and re-documented in ScheduledFuture to point out the difference.