CosmoCode is a Berlin based IT service provider focusing on CMS, Wikis and Web2.0
Great software. Bright people. Happy customers!
Mail info@cosmocode.deTel +49 (30) 814504070
Let this post be a little field report about exception handling in the Java Executor Framework.
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.)
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.
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);
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.
About CosmoCode
Subscribe