Our application has some code that runs asynchronously that is failing. Like this:
CompletableFuture.runAsync(
() -> { throw new RuntimeException("bad"); },
executorService
);
We want default exception handling code that can catch these errors, in case specific uses forget to handle exceptions (this came from a production bug).
This is apparently tricky. The answer given in Handling exceptions from Java ExecutorService tasks does not work.
It relies on the task being a Future<?>
and then calling get()
on it, resulting in the exception being thrown again. But this is not the case for runAsync
code.
runAsync
creates a java.util.concurrent.CompletableFuture.AsyncRun
class that seems to try to supress all exceptions. Despite being a Future
itself, it does not indicate being isDone()
, and seems to provide no way to get exceptions out of it.
So, given the following boilerplate, how should we catch these gnarly exceptions?
Note that we really want something that will catch all unhandled exceptions in runAsync
code, not something we can add to each runAsync
invocation. It's just too easy to forget to add handling code to each one.
public class ExceptionTest {
public static void main(String[] args) throws RuntimeException {
ExecutorService executorService = new ThreadPoolExecutor(
1, 1, 0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()
) {
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
// TODO: Magically extract the exception from `r`
}
};
CompletableFuture.runAsync(
() -> { throw new RuntimeException("bad"); },
executorService
);
}
}