How to handle uncaught exceptions from CompletableFuture.runAsync

你离开我真会死。 提交于 2021-01-28 08:55:49

问题


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
        );
    }
}

回答1:


So, this is a terrible hack, but it does handle the case where you forget to call exceptionally when using runAsync. I'd love to see more generic and less hacky solutions.

It works by intercepting the AsyncRun before it's executed and patching on an exceptionally block.

Seriously janky, though. But it'll work, maybe, until Oracle changes how runAsync works.

    ExecutorService executorService = new ThreadPoolExecutor(
        1,
        1,
        0L,
        TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue()
    ) {
        @Override
        protected void beforeExecute(final Thread t, final Runnable r) {
            super.beforeExecute(t, r);

            if (r.getClass().getName().equals("java.util.concurrent.CompletableFuture$AsyncRun")) {
                try {
                    final Field f = r.getClass().getDeclaredField("dep");
                    f.setAccessible(true);
                    ((CompletableFuture<?>) f.get(r)).exceptionally(e -> {
                        LoggerFactory.getLogger(ExceptionTest.class).error("Error in runAsync " + r, e);
                        UnsafeUtils.getUnsafe().throwException(e);
                        return null;
                    });
                } catch (Exception e) {
                    System.out.println("Failed to hack CompletableFuture$AsyncRun to report exceptions.");
                }
            }
        }

        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);

            if (t == null && r instanceof Future<?>) {
                try {
                    Future<?> future = (Future<?>) r;
                    if (future.isDone()) {
                        future.get();
                    }
                } catch (CancellationException ce) {
                    t = ce;
                } catch (ExecutionException ee) {
                    t = ee.getCause();
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
            if (t != null) {
                LoggerFactory.getLogger(ExceptionTest.class).error("Error in async task " + r, t);
            }
        }
    };


来源:https://stackoverflow.com/questions/53218333/how-to-handle-uncaught-exceptions-from-completablefuture-runasync

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!