Как обработать необработанные исключения из CompletableFuture.runAsync - PullRequest
0 голосов
/ 09 ноября 2018

Наше приложение имеет некоторый код, который работает асинхронно и не работает. Как это:

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

Нам нужен код обработки исключений по умолчанию, который может отлавливать эти ошибки, в случае, если конкретные пользователи забывают обрабатывать исключения (это произошло из-за рабочей ошибки).

Это очевидно сложно. Ответ, данный в Обработка исключений из задач Java ExecutorService не работает.

Он полагается на задачу, являющуюся Future<?>, а затем вызывающую get(), в результате чего исключение выдается снова. Но это не относится к runAsync коду.

runAsync создает класс java.util.concurrent.CompletableFuture.AsyncRun, который пытается подавить все исключения. Несмотря на то, что он сам по себе Future, он не указывает на то, что он 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
        );
    }
}

1 Ответ

0 голосов
/ 09 ноября 2018

Итак, это ужасный взлом, но он справляется со случаем, когда вы забываете вызвать exceptionally при использовании runAsync. Я хотел бы видеть более общие и менее хакерские решения.

Он работает путем перехвата 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);
            }
        }
    };
...