Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

More elegant way to handle error and timeouts in core.async?

Of course I want to wrap various requests to external services with core.async, while still returning results from these operations through some chan.

I want to take care of both thrown exceptions and timeouts (ie that the operation takes longer than expected to return, or to be able to choose among various services for the same task but with different approaches or qualities of service.

The smallest viable example to show examples of both being able to handle an error, a timeout and a correct returning result seems to be these:

(require '[clojure.core.async :refer [chan go timeout <! >! alt!]])

(def logchan (chan 1))

(go (loop []
      (when-let [v (<! logchan)]
        (println v)
        (recur))))

(dotimes [_ 10] 
  (go 
    (let [result-chan  (chan 1)
          error-chan   (chan 1)
          timeout-chan (timeout 100)]
      (go
        (try 
          (do (<! (timeout (rand-int 200)))
              (>! result-chan (/ 1 (rand-int 2))))
          (catch Exception e (>! error-chan :error))))
      (>! logchan (alt! [result-chan error-chan timeout-chan] 
                    ([v] (if v v :timeout)))))))

This code prints something like

1
:error
1
:error
:error
:timeout
:error
:timeout
:timeout

This is not very elegant. I especially don't like the way of returning :error and :timeout. The nil-check in alt! is clearly not what I want either.

Is there some better way to accomplish the three goals of returning result, protect from long timeouts and handle errors? The syntax is quite OK (most things above are really to provoke those three errors).

like image 934
claj Avatar asked Mar 19 '23 03:03

claj


2 Answers

The core.async chan-function has the ex-handler, so the following construct is possible

(chan buf-or-n xform ex-handler)

where ex-handler is a one-argument function that gets the exception. When the function returns nil, this is not put on the channel, otherwise the function has the chance to transform the exception to something viable for the data in question.

like image 184
claj Avatar answered May 16 '23 07:05

claj


Put output in one channel, errors and results.

Define records (maps) for errors such as

(defrecord SomeError [cause context etc...])

This way the errors will hold relevant info.

Verify output in consumers

(if-not (instance? SomeError result) ...)

There is no one 'correct' way, you can use pub/sub for the same purpose, pass in one error-chan or create several and async/merge them together.

like image 32
edbond Avatar answered May 16 '23 08:05

edbond