I'm not sure if this is a bug or intended behaviour. I've been investigating a strange case where Clojure's binding conveyance wasn't behaving as we had expected. The repro case below shows the issue that occurred, using just clojure.core
functions. In reality, what had happened was that we had used clojure.lang.Agent/soloExecutor
as the ExecutorService
for a Langohr RabbitMQ subscription. Threads handling RabbitMQ messages were able to read dynamic variables which were set in unrelated code which ran via an agent.
Symptoms
We were setting a dynamic var like this:
(defn http-handler [url]
(binding [*url* url]
(log/info "doing a http request")))
(defn rabbit-handler [message]
(log/info "handling a RabbitMQ message"))
Our logging system picks up the current value of *url*
and adds it to the log lines. We were seeing log lines from other, unrelated threads logging a value for *url*
when the var should have had no value. In the toy example above, we were seeing a value for *url*
set in the "handling a RabbitMQ message"
log lines.
The cause of the observed behaviour is in send-via
and in the way binding-conveyor-fn
sets a thread binding. binding-conveyor-fn
sets the threadBindingFrame in thread-local storage, and never un-sets it.
If that same thread is subsequently re-used for another purpose, the dynamic bindings remain in place from the previous use of the thread.
Repro Case
(ns binding
(:import java.util.concurrent.Executors))
(def thread-pool
(Executors/newFixedThreadPool 1))
(set-agent-send-off-executor! thread-pool)
(def counter (agent 0))
(add-tap println)
(def ^:dynamic *some-context* nil)
(tap> "start")
(defn inspect []
(tap>
(format "tap: thread=%s context=%s"
(.getId (Thread/currentThread))
*some-context*)))
(binding [*some-context* "in the agent"]
(send-off counter (fn [& _]
(inspect))))
(.submit thread-pool inspect)
;; Output
;; start
;; tap: thread=43 context=in the agent
;; tap: thread=43 context=in the agent
Gist