请分享您的想法,参加 2024 年 Clojure 状态调查!

欢迎!有关如何操作的更多信息,请参见 关于 页面。

0 投票
core.async
大家好,这里有一个有点棘手的错误需要报告... 我们在使用 ClojureScript 上的 core.async 时遇到了一些问题。我们的应用围绕一个大型事件循环构建,该循环会阻塞在来自用户活动或 API 调用对应的一个或多达多个通道的消息上。问题似乎出在这个事件循环中 - 我们正在使用 alts! 从任何可用通道中提取消息,但有时日志显示我们会达到 alts! 但从未解除阻塞。然而,通过更多的日志记录,我可以看到列表中的一个通道有后续写入操作,因此我不太清楚发生了什么。

这就是高层次概述,现在谈一下代码。

我们主要的事件循环如下


  (log "进入主事件循环。")
  (go
    (while true
      (log "alts! 通道散列: " (map hash (:channels @app)))
      (let [[message channel] (alts! (seq (:channels @app)))]
        (log "alts! 解除阻塞,调用我们的 process-message"))
        (swap! app process-message message channel)
        (log "process-message 完成,循环"))))


{{process-message}} 是我们应用内部的一个函数,但我不认为它的细节是必要的。在 Safari 卡住的场景中,日志看起来像


[日志] process-message 完成,循环 (main.js,行 62)
[日志] alts! 通道散列:  (16 12 19 33) (main.js,行 82)
[日志] Socket 已连接。 (socket.js,行 309)
[日志] 向散列 19 的通道 put! (socket.js,行 86)
[日志] 消息是 [:metronome [:staff [{:description nil, :deletable true, :email nil, :isAdmin true, :isTrainer false, :telephone nil, :name "Fynder Admin", :picture nil, :userId 1} {:description nil, :deletable fa... (socket.js,行 87)
[日志] put! 回调给出了 true (socket.js,行 89)
[调试] Metronome:staff 数据解码。 put! 完成:12.282ms (socket.js,行 93)
[日志] 向散列 19 的通道 put! (socket.js,行 86)
[日志] 消息是 [:metronome [:class-types [{:deletable false, :picture nil, :name "CycleCore", :id 2, :description "CycleCore 是一个 55 分钟的双工锻炼概念,结合了 30 分钟的激烈的心肺 ... (socket.js,行 87)
[日志] put! 回调给出了 true (socket.js,行 89)
[调试] Metronome:class-types 数据解码。 put! 完成:1.288ms (socket.js,行 93)
[日志] 向散列 19 的通道 put! (socket.js,行 86)
[日志] 消息是 [:metronome [:locations [{:studios [{:deletable false, :name "Kensington", :id 1, :locationId 1, :description "Studio (11a) sits just off Stratford Road in Stratford Studios. To find us, just pass ... (socket.js,行 87)
[调试] Metronome:locations 数据解码。 put! 完成:0.884ms (socket.js,行 93)


请注意,我们发现了一条“alts! channel hashes”的日志记录,但我们从未看到过“alts! unblocked”。然而,请注意传递给 alts! 的哈希列表。提到了通道 19,但我们随后将其放入了通道 19...但仍然没有得到解除阻塞。还有一件让我感到可疑的事情,那就是在我们被阻塞在 alts! 时,两个对 put! 的调用立即成功了,但对于仅包含一个元素的通道来说,我并不期望立即 put 回调被调用多次。有趣的是,最后的 put! 没有触发回调。

遗憾的是,重现此错误相对困难。我可以通过退出 Safari、重新打开它并导航到开发服务器的方式在某种程度上可靠地重现它。大约每 15 次尝试中就有一次会以这种方式卡住。我想知道这是否与 Safari 的 MessageChannel 实现有关 - 您可以在日志条目中看到 nexttick.js 调用其回调,这看起来就是在我的浏览器中分发的机制。

我非常愿意提供任何有用的更多信息,但现在我已无法调试它。虽然代码是专有的,但我会很高兴暂时添加人员到 Github 项目中,以尝试修复这个问题。我们有一组开发 API 服务器,您可以将它们指向目标,这样只需运行 {{lein cljs}} 就可以了。

我已经附上了我们的 Socket.io 框架和主事件循环的代码。遗憾的是,我还没有最小的测试用例 - 我真的不知道从哪里开始。

6 答案

0 投票
by

评论由:ocharles

我深入研究了 Google Closure 库的内部结构,并将 {{getSetImmediateEmulator_}} 改为

`
goog.async.nextTick.getSetImmediateEmulator_ = function() {
// 回退到 setTimeout 且计时为 0。在浏览器中这会产生 5ms 或更长时间的延迟
//。
return function(cb) {

goog.global.setTimeout(cb, 0);

};
};
`

而且我还没有让其卡住。所以也许 Safari 中的 MessageChannel 有问题...

0 投票
by

评论由:gshayban

嗨 Oliver,这似乎是一个竞争条件,我们会弄清楚的。

您能否比较在 0.1.319.0-6b1aca-alpha 与 0.1.346.0-17112a-alpha 上的运行结果?

alts! 应该传递一个带索引的集合/向量。传递一个序列不会引起这个错误,只是要注意的事。

0 投票

评论由:ocharles

嗨Ghadi,

0.1.319.0-6b1aca-alpha是这个初始报告针对的版本 - 我应该提到这一点。因此,0.1.319.0-6b1aca-alpha确实会卡死。

然而,0.1.346.0-17112a-alpha则不会卡死,这很奇怪 - 因为我相信我已经尝试升级到这个版本!我在两个通常有问题的Mac上尝试过,它们一次都没有卡死。我将这个版本推送给更多测试人员,看看会发生什么。

0 投票

评论由:ocharles

啊哈,我知道它不会那么简单!将这个版本推出到生产环境后,它立即又冻结了。但是开发服务器运行的是非常不同的优化,所以我将构建一个生产版本并在本地提供它 - 我们将看看那里会发生什么。

0 投票
_由:ocharles_发表的评论

是的,这确实是一个优化问题。以下是我的影子构建配置


(ns fynder.shadowbuild
  (:require [clojure.java.io :as io]
            [shadow.cljs.build :as cljs]))

(defn define-modules [state]
  (-> state
      (cljs/step-configure-module :cljs '[cljs.core clojure.walk clojure.string cljs.reader cljs.core.async] #{})
      (cljs/step-configure-module :test-support '[inflections.core no.en.core enfocus.bind fynder.winchan] #{:cljs})
      (cljs/step-configure-module :devel '[fynder.devel] #{:cljs})
      (cljs/step-configure-module :admin '[fynder-admin.main] #{:cljs})
      (cljs/step-configure-module :trainer '[fynder-trainer.main] #{:cljs})
      (cljs/step-configure-module :mobile '[fynder-mobile.main] #{:cljs})
      (cljs/step-configure-module :sweatybetty '[fynder-sweatybetty.main] #{:cljs})
      (cljs/step-configure-module :loader '[fynder-loader.loader] #{:cljs})))

(defn dev
  "构建项目,等待文件更改,重复"
  [& args]
  (let [state (-> (cljs/init-state)
                  (cljs/enable-source-maps)
                  (assoc :optimizations :advanced
                         :pretty-print false
                         :work-dir (io/file "target/cljs-work")
                         :public-dir (io/file "resources/dev")
                         :public-path "")
                  (cljs/step-find-resources-in-jars)
                  (cljs/step-find-resources "src/cljs/")
                  (cljs/step-finalize-config)
                  (cljs/step-compile-core)
                  (define-modules))]
    ;; 编译、刷新、重新加载、重复
    (循环 [state state]
      (let [new-state (try
                        (-> state
                            (cljs/step-compile-modules)
                            (cljs/flush-unoptimized)
                            (cljs/wait-and-reload!))
                        (catch Throwable t
                          (prn [:failed-to-compile t])
                        (.printStackTrace t)
                          (cljs/wait-and-reload! state)))]
        (recur new-state)))))

(定义 prod
  "构建项目,等待文件更改,重复"
  [& args]
  (-> (cljs/init-state)
      (cljs/enable-emit-constants)
      (cljs/enable-source-maps)
      (assoc :optimizations :advanced
             :pretty-print false
             :work-dir (io/file "target/cljs-work")
             :public-dir (io/file "resources/prod")
             :externs ["react/externs/react.js"
                       "externs/base32.js"
                       "externs/jquery.js"
                       "externs/react_addons.js"
                       "externs/fastclick.js"
                       "externs/socket.io.js"
                       "externs/moment.js"
                       "externs/papa.js"
                       "externs/markdown.js"
                       "externs/xss.js"
                       "externs/facebook.js"
                       "externs/checkout.js"])
      (cljs/step-find-resources-in-jars)
      (cljs/step-find-resources "src/cljs/")
      (cljs/step-finalize-config)
      (cljs/step-compile-core)
      (定义-modules)
      (cljs/step-compile-modules)
      (cljs/closure-optimize)
      (cljs/flush-to-disk)
      (cljs/flush-modules-to-disk)))


如果在开发配置下运行,则不会卡住。如果切换到生产配置(并用 python 的 SimpleHTTPServer 提供点 lein publish 的结果),那么 Safari 就会卡住。
0 投票
answered by
参考资料: https://clojure.atlassian.net/browse/ASYNC-97 ( 由 alex+import 报告)
...