在浏览器REPL中的评估偶尔会卡住。看起来REPL环境和浏览器有时会错过对方,它们的“约会”失败了。浏览器正在等待POST回复,而REPL正在尝试发送一个命令,但它们没有相遇。
当我们将我们的测试从nodejs切换到浏览器环境时,我发现了这个问题。幸运的是,我找到了一个非常小的示例,它在执行过程中会卡住。看起来(模拟的)重负载增加了“卡住”的概率。
最小配置
(ns race.condition
(:require [cljs.repl.browser :as browser]
[cljs.repl :as repl]
[cljs.env :as env]
[cljs.build.api :as api]))
(api/build '[(ns race.repl
(:require [clojure.browser.repl]))
(clojure.browser.repl/connect "https://127.0.0.1:9000/repl")]
{:output-to "target/cljs-race/main.js"
:output-dir "target/cljs-race"
:main 'race.repl})
(spit "target/cljs-race/index.html"
(str "<html>" "<body>"
"<script type=\"text/javascript\" src=\"main.js\">"
"</script>" "</body>" "</html>"))
现在启动环境
(def env (browser/repl-env :static-dir ["target/cljs-race" "."] :port 9000 :src nil))
(env/with-compiler-env (env/default-compiler-env)
(repl/-setup env {}))
交叉手指并启动这个无尽的循环
(loop [i 0]
(println (java.util.Date.) i)
(dotimes [j 100]
(let [result (repl/-evaluate env "<exec>" "1" "true")]
(when-not (= :success (:status result))
(println i j result))))
(recur (inc i)))
通过附件中的heavy-load.sh模拟重负载。
经过一些迭代(例如55个大循环i)后,执行停止。如果您调查堆栈(race-condition.jstack),您可以在一个线程中看到
at clojure.core$promise$reify__6779.deref(core.clj:6816)
at clojure.core$deref.invoke(core.clj:2206)
at cljs.repl.browser$send_for_eval.invoke(browser.clj:65)
at cljs.repl.browser$browser_eval.invoke(browser.clj:193)
at cljs.repl.browser.BrowserEnv._evaluate(browser.clj:262)
代码正在等待一个有连接的承诺(它已经到达)。
我认为可疑的代码在cljs.repl.server函数connection和set-connection中。这两个函数以非标准方式访问一个原子。他们分两步进行deref一个值并swap!。
有谁更了解REPL内部的人可以调查吗?谢谢。