Конкретный пример defaultUncaughtExceptionHandler

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

Однако я не могу заставить его работать. Я пытался использовать reify и пробовал использовать прокси. Как будто ничего не происходит.

Вот минимальный случай, воспроизводящий проблему:

REPL> (Thread/setDefaultUncaughtExceptionHandler
  (proxy [Thread$UncaughtExceptionHandler] []
    (uncaughtException [thread throwable]
      (do (println (-> throwable .getCause .getMessage))
          ; (error "whatever...") ; some 'timbre' logging if you have timbre
      ))))
nil
REPL> (future (do (Thread/sleep 100) (/ 0 0)))
#<core$future_call$reify__6267@c2909a1: :pending>
REPL>

Пробовал println, пробовал логировать в файл с помощью timbre, пробовал плевать во временный файл, пробовал принудительно запускать future с помощью дерефинга... Видимо, неперехваченное исключение по умолчанию обработчик никогда не вызывается.

Может ли кто-нибудь показать мне пример интерактивного/REPL рабочего обработчика необработанных исключений по умолчанию, который фактически перехватывает исключение?

В качестве бонуса вопрос: как только установлен обработчик необработанных исключений по умолчанию, есть ли простой способ «увидеть», что он установлен? И что происходит в REPL, когда вы тестируете эту функцию и несколько раз вызываете setDefaultUncaughtExceptionHandler? Учитывается только последний обработчик?


person Cedric Martin    schedule 20.04.2014    source источник


Ответы (1)


Фьючерсы работают не совсем так. Исключение не исчезает.

 (deref (future (do (Thread/sleep 100) (/ 0 0))))

 ExecutionException java.lang.ArithmeticException: Divide by zero
    java.util.concurrent.FutureTask.report (FutureTask.java:122)
    java.util.concurrent.FutureTask.get (FutureTask.java:192)
    clojure.core/deref-future (core.clj:2108)
    clojure.core/future-call/reify--6267 (core.clj:6308)
    clojure.core/deref (core.clj:2128)

В основе Java FutureTask.run() лежит ответ...

boolean ran;
try {
    result = c.call();
    ran = true;
} catch (Throwable ex) {
    result = null;
    ran = false;
    setException(ex);
}

Это избавляет от проблемы конечного потребителя будущего ответа. Если мы немного переформулируем тестовый пример, мы ясно увидим работу обработчика исключений:

(Thread/setDefaultUncaughtExceptionHandler
    (proxy [Thread$UncaughtExceptionHandler] []
      (uncaughtException [thread throwable]
        (do (println "****" (-> throwable .getMessage))
        ))))

Я удалил getCause, так как он у нас не обязательно есть...

(.start (Thread. #(/ 0 0)))

Доходность на стандартном выходе...

**** Divide by zero

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

(defn exception-safe-queue-reader [source sink]
    (try
        (let [message (source)]
            (sink message))
        (catch Throwable t
            (println "****" t))
    (recur source sink))

Это (для меня) более стандартная идиома Java, чем использование uEH, т. е. если вы собираетесь иметь дело с ошибками, то работайте с ними напрямую в своем коде. Запускаете ли вы его с помощью голого потока Java или будущего, не имеет значения.

person pete23    schedule 20.04.2014
comment
спасибо, это помогает видеть вещи немного яснее, но... У меня есть система, в которой все довольно развязано, широко используются очереди и производители/потребители. Итак, в этом случае я начинаю будущее, в котором потребитель читает что-то из очереди. Так что мне никогда не нужно дерефировать это будущее. Не правильно ли использовать будущее в этом случае? Как я должен запустить поток, работающий без остановок, потребляющий вещи из очереди (и живой для жизненного цикла приложения)? Должен ли я вернуться к java-земле или есть абстракция clojure, которая будет хорошо работать и хорошо учитывать исключения? - person Cedric Martin; 20.04.2014
comment
Ах, я думаю, будущее прекрасно, как сахар, для того, чтобы просто зажечь и забыть нить. Пожалуйста, смотрите мой постскриптум выше, чтобы узнать, как я буду обрабатывать подобные вещи в Java или Clojure, не используя uEH. Если вы сталкиваетесь с большим потоком управления вокруг очередей, подумайте также о том, чтобы взглянуть на core.async. - person pete23; 20.04.2014
comment
+1, еще раз спасибо за вашу помощь ... Я, вероятно, сделаю что-то вроде того, что вы предлагаете. Я все еще хотел бы, чтобы был простой способ сообщить о любом отсутствующем исключении: например, чтобы иметь возможность определить, где в моем коде мне нужно иметь дело непосредственно с этими исключениями. Кстати, вы действительно имели в виду (повторяющийся исходный приемник) в последней строке? - person Cedric Martin; 20.04.2014
comment
Да, самое главное в циклах событий — это цикл! И события, наверное. Извините, если это было немного надумано, я изо всех сил пытаюсь собрать фрагменты сегодня. Удачи. - person pete23; 21.04.2014
comment
ага, не знал о конструкции Clojure recur: я не знал, что она рекурсивно переходит в Exception-safe-queue-reader, поэтому мне было интересно, где имя функции :) - person Cedric Martin; 21.04.2014