Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get stacktraces from errors in clojure futures?

I have a few tasks that are quite independent that I've spun off using futures. These tasks communicate certain events back to the main app over a core.async/chan, or just talk to the db.

Some of these futures are now failing silently. I get no stacktraces in my logs, or on std{out,err}. I've tried surrounding the code in the fns called by the futures with

(try (do-stuff)
  (catch Exception e
    (log/error e))

just to get some output into my logs, but that--surprisingly!--didn't work.

Is my only option to spin up yet another thread which does the following in a loop?

(let [m (Thread/getAllStackTraces)]
    (doseq [e (.entrySet m)]
      (log/error (.toString (.getKey e)))
      (doseq [s (.getValue e)]
        (log/error " " (.toString s)))))

Is this a symptom indicating that I shouldn't be using futures at all? Should I be using agents, even though there's no need to send any messages to these agents?

like image 461
expez Avatar asked Jul 09 '14 21:07

expez


1 Answers

The behavior is very similar to Java Future. Inside the future block exceptions may be thrown and caught, and that behaves as you would expect. When an exception is not caught, the Future has no way of rethrowing it on the calling thread. It only does so in form of ExecutionException when you actually get its value. This corresponds to deref in Clojure.

Let's create a function that throws something:

(defn die [] (throw (RuntimeException.)))

If I just wrap it in future, it works fine:

user=> (def x (future (die)))
#'user/x
; Note: No exception here
user=> @x
RuntimeException   user/die (NO_SOURCE_FILE:1)
; Bam! Exception thrown on deref, think of it as
; ExecutionException when getting failed future's value in Java

So you can catch this exception on deref:

user=> (def x (future (die)))
#'user/x
(try @x (catch Exception e (println "Caught ya")))
Caught ya
nil

Or you can catch it inside the future:

user=> (def x 
  #_=>   (future 
  #_=>     (try (die)
  #_=>       (catch Exception e
  #_=>         (print "Caught ya!")
  #_=>         "Something"))))
#'user/x
Caught ya
user=> @x
"Something"

Note how in this case it prints "Caught ya" immediately when the error occurs on the background thread, before deref. Then on deref it returns value returned by the catch block in future.

Once again, the bottom line is - it works pretty much the same as Java futures.

like image 68
Konrad Garus Avatar answered Oct 25 '22 00:10

Konrad Garus