Question

I've had the case several times where exception in futures would prove hard to track down (and already asked a question here about why some exception seemed never to happen and this question is not a dupe of my older question) and decided to try to set a "default uncaught exception handler".

However I can't make it work. I tried using reify and I tried using a proxy. It's as if nothing was happening.

Here's a minimal case reproducing the issue:

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>

I tried println, I tried logging to a file using timbre, I tried to spit to a temp file, I tried to force the future to be run by deref'ing... Apparently the default uncaught exception handler is never called.

Could anyone show me an interactive / REPL example of a working default uncaught exception handler actually catching an exception?

As a bonus question: once a default uncaught exception handler is set, is there an easy way to "see" that it is set? And what happens in the REPL when you are testing that feature and call setDefaultUncaughtExceptionHandler several time? Is only the last handler taken into account?

Était-ce utile?

La solution

Futures don't quite work like this. The exception does not escape.

 (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)

At the core of Java's FutureTask.run() is the answer...

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

This saves the problem for the eventual consumer of the future's response. If we reframe the test case slightly, we can clearly see the exception handler work:

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

I removed the getCause as we don't necessarily have one...

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

Yields on stdout...

**** Divide by zero

If all you are after (as suggested by your comment) is a thread with an appropriate exception handler, have you considered something like the following:

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

This is (to me) a more standard Java idiom than using the uEH - i.e. if you're going to deal with errors, then deal with them in your code directly. Whether you start it using a bare Java thread or future is irrelevant.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top