Does async functionality change in protocols? For example I have a protocol and a record defined like this:
(defprotocol DataWriter
"Protocol for writing data to a file"
(write [this path data opts])
(write-async [this path data opts]))
(defrecord DefaultDataWriter []
DataWriter
(write [this path data _options]
(try
(with-open [w (io/writer path)]
(.write w data)
{:path path})
(catch Exception e
(throw (ex-info "Error writing data" {:path path :writer-type :default} e)))))
(write-async [this path data options]
(async/thread
(write this path data options))))
When I call the write-async function on the record, the file gets created but no content is written. If I just define a function like this:
(defn write-async
[path data]
(async/thread
(try
(with-open [w (io/writer path)]
(.write w data)
{:path path})
(catch Exception e
(throw (ex-info "Error writing file" {:path path} e))))))
This works. Am I doing something wrong here?> Does async functionality change in protocols? Not that I'm aware. I strongly suspect the problem is somewhere else.
I have noticed that running code in go blocks or thread blocks might cause errors not to be printed to the repl. You may want to add some logging, printing, tapping, or otherwise to see if an error is happening inside the asynchronous scope.
Generally speaking, it's usually a bad idea to have my-operation and my-operation-async. If someone wants to make an operation asynchronous, it's pretty trivial to wrap it in a go block, thread block, or run the operation using an Executor.
Providing a my-operation-async is making a policy decision in a library that is usually made globally with more context.
My guess would be something is throwing an exception before the write (file created no content)
Channels are queues between threads of control, they don't convey errors between threads like say futures
Okay, I found the issue. It was a separate issue, but here goes. The code snippet culprit is below:
(defn file-writer-worker
[in]
(async/go-loop []
(when-let [{:keys [path data metadata]} (async/<! in)]
(let [{:keys [ok? error]} (async/<! (write-data-async path data))]
(if ok?
(println (str "File written: " path " metadata: " metadata))
(println (str "Error writing file: " path " metadata: " metadata " error: " error))))
(recur))))
The recur is outside the body of the let but in the when-let. This apparently was causing an issue. I moved the recur within the let and it works.I don't quite understand why that would cause an issue.
Hard to say exactly, if you are hacking at the repl it is easy to have core.async related state in the repl persist beyond what you expect and alter the behavior of subsequent runs, can be tricky to debug just looking at code. A good rule of thumb is if you get unexpected behavior, restart your process (new repl or whatever) and see if it persists in a fresh environment
Thanks for the advice.
I highly recommend either wrapping async threads/go macros with try/catch with logging or using a macro that does it for you.