如何处理来自CompletableFuture.runAsync的未捕获异常 [英] How to handle uncaught exceptions from CompletableFuture.runAsync

查看:2674
本文介绍了如何处理来自CompletableFuture.runAsync的未捕获异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们的应用程序包含一些异步运行的代码,但失败了.像这样:

CompletableFuture.runAsync(
    () -> { throw new RuntimeException("bad"); },
    executorService
);

我们希望使用默认的异常处理代码来捕获这些错误,以防万一特定用途忘记处理异常(这是由生产错误引起的).

这显然很棘手. 处理Java ExecutorService任务中的异常给出的答案不起作用. >

它依赖于任务为Future<?>,然后在其上调用get(),从而导致再次引发异常.但这不是runAsync代码的情况.

runAsync创建一个java.util.concurrent.CompletableFuture.AsyncRun类,该类似乎试图抑制所有异常.尽管本身是Future,但它 not 并不表示自己是isDone(),并且似乎没有提供摆脱异常的方法.

因此,考虑到以下样板,我们应该如何捕获这些过时的异常?

请注意,我们确实希望某些东西能够捕获runAsync代码中的所有未处理的异常,而不是我们可以添加到每个runAsync调用中的东西.忘记将处理代码添加到每个代码中太容易了.

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

解决方案

因此,这是一个可怕的骇客,但确实可以解决在使用runAsync时忘记调用exceptionally的情况.我希望看到更通用,更简单的解决方案.

它的工作原理是在执行前拦截AsyncRun 并在exceptionally块上进行修补.

但是,很严重.但这可能会起作用,直到Oracle更改runAsync的工作方式.

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

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

解决方案

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

这篇关于如何处理来自CompletableFuture.runAsync的未捕获异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆