2024 Clojure 状态调查中分享您的想法!

欢迎!请查看关于页面获取更多关于如何使用本站的信息。

+1
Clojure

我不确定这是不是一个bug,还是预期的行为。我已经调查了一个奇特的案例,Clojure的绑定传递并没有我们期望的那样表现。下面的复现案例展示了出现的问题,仅使用clojure.core函数。实际上,发生的事情是我们使用了clojure.lang.Agent/soloExecutor作为Langohr RabbitMQ订阅的ExecutorService。处理RabbitMQ消息的线程能够读取在独立代码中设置的动态变量,这些代码通过代理运行。

症状

我们以这种方式设置了一个动态变量

(defn http-handler [url]
  (binding [*url* url]
    (log/info "doing a http request")))

(defn rabbit-handler [message]
  (log/info "handling a RabbitMQ message"))

我们的日志系统捕捉到当前*url*的值并将其添加到日志行中。当我们看到其他,无关的线程在日志中记录一个与*url*相关的值,而这个变量应该没有值时,我们很困惑。在上面的玩具例子中,我们看到了在“处理RabbitMQ消息”的日志行中设置的*url*的值。

观察到的行为的原因在于 binding-conveyor-fn如何设置线程绑定的方式,以及send-via中的原因。binding-conveyor-fn设置了线程绑定到线程本地存储中,并且永远不会将其解除。

如果相同的线程随后被用于其他目的,则之前的线程使用中的动态绑定将保留。

复现案例

(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

代码片段

1 个答案

0

编辑了

我还可以说,无论是像这样用 bound-fn 包装 inspect

(def inspect (bound-fn []
  (tap>
   (format "tap: thread=%s context=%s"
           (.getId (Thread/currentThread))
           *some-context*))))

还是把 inspect 函数包装在示例末尾的调用中

(.submit thread-pool (bound-fn* inspect))

都无法解决这个问题。

但是用替换最后一行

(.submit thread-pool (#'clojure.core/binding-conveyor-fn inspect))

确实可以解决这个问题。

旧问题,相关: https://clojure.atlassian.net/browse/CLJ-2476

如果用户足够勇敢地重用 Clojure 的 soloExecutor,那么问题同样存在。我们能使其私有化吗?

https://gist.github.com/karolinepauls/9c976d95950141bc3b7d8b179b65b1c4
...