Say I have this ns:
(ns blah
(:require [clojure.core.async :as async]))
(def log-chan (async/chan (async/sliding-buffer 1000)))
(def logger
(async/go-loop []
(when-let [msg (async/<! log-chan)]
(do (println msg)
(recur)))))
(defn log
"Logs messages asynchronously, prints synchronously.
This allows logging to occur from multiple writers
asynchronously, while retaining serialized printing
for readability."
[msg]
(clojure.core.async/put! log-chan msg))
So we have a go routine parked waiting for messages on a global log chan, with a little log api for async logging. I go off to do some work and test, and as is my want, I reload another ns that depends on 'blah using :reload-all doing some iterative dev and testing.
(ns blee
(:require [blah]))
;;does lots of fascinating stuff
Sadly, I get some complaints:
user>(require 'blee :reload-all)
Execution error (IllegalArgumentException) at clojure.core.async.impl.protocols/eval105333$fn$G (protocols.clj:43).
No implementation of method: :exec of protocol: #'clojure.core.async.impl.protocols/Executor found for class: clojure.core.async.impl.exec.threadpool$thread_pool_executor$reify__9246
exec protocol function is now technically different (after reload), the executor is defined by protocol reification, but the original threadpool is defonce’d, so all future dispatches are applying the (new/current) exec protocol fn to the original defonced’ threadpool reified object here.
So the “new” logger goroutine is trying to be run (ultimately via the redefined clojure.core.async.impl.protocols/exec fn), which isn’t technically implemented by the retained clojure.core.async.impl.dispatch/executor object, and we get the protocol complaint.
My current way to sidestep this problem is to hack the require
functionality in clojure.core and allow namespaces to exclude on :reload-all. This is clunky but workable for now (I am pretty much manually/intermittently reloading via reload-all during dev and interspersing test suite runs).
Is there a more elegant way to respect the implicit singleton threadpool + brittle protocol relationship in core.async? I had thought about hacking the threadpool var manually to work around defonce (maybe shutting down gracefully as well), via some other namespace that loads earlier. That would let the stock require
machinery handle everything.