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

欢迎!请参阅关于页面了解有关如何使用本网站的更多信息。

0
core.async
大家好,这里有一个有点棘手的虫子报告……我们在使用 ClojureScript 中的 core.async 在 Safari 7 中发现了一些问题。我们的应用程序围绕一个大型事件循环构建,该循环阻塞在用户活动或 API 调用相关的一个或多个通道之一的消息上。问题似乎在于这个事件循环中 - 我们正在使用 alts! 从任何可用的通道中提取消息,但有时日志显示,我们在达到 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 的通道 (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)
[Debug] Metronome: staff data decoded. put! complete.: 12.282ms (socket.js,行 93)
[日志] 发送到带有哈希  19 的通道 (socket.js,行 86)
[日志] 消息是 [:metronome [:class-types [{:deletable false, :picture nil, :name "CycleCore", :id 2, :description "CycleCore is a 55-minute dual workou... (socket.js,行 87)
[日志] put! 回调提供了 true (socket.js,行 89)
[Debug] Metronome: class-types data decoded. put! complete.: 1.288ms (socket.js,行 93)
[日志] 发送到带有哈希  19 的通道 (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)
[Debug] Metronome: locations data decoded. put! complete.: 0.884ms (socket.js,行 93)


请注意,我们在日志中看到了“alts! channel hashes”的条目,但从未看到“alts! unblocked”。然而,请注意传递给alts!的哈希列表,提到了通道19,但是随后我们将...放入通道19,但我们仍然无法取消阻止。还有一件事让我感觉可疑,那就是当我们被阻断在alts!时,对put!的两个调用立即成功,而对通道只有一次元素的限制。也许我误解了某些东西,但我没有想到立即调用的回调会被调用多于一次。有趣的是,最后的put!并没有调用回调。

遗憾的是,重现这个错误相当困难。我可以通过退出Safari,重新打开它并导航到开发服务器来有一定程度的可靠性地重现它。大约有15%的尝试会以这种方式卡住。我觉得这可能和Safari的MessageChannel实现有关——你可以在日志条目中看到nexttick.js调用其回调,这好像是我的浏览器中分配的工作方式。

我非常乐意提供任何有用的更多信息,但现在我超出了解决问题的能力。虽然代码是专有的,但我愿意暂时将人们添加到Github项目中,以尝试修复这个问题。我们有一些开发API服务器,你可以指向它们,所以这只是一个运行{{lein cljs}}的问题。

我已经附上了我们Socket.io包装和主事件循环的代码。遗憾的是,我还没有一个最小测试用例——我根本不知道从哪里开始。

6 个答案

0

评论者:ocharles

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

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

goog.global.setTimeout(cb, 0);

};
};
`

等等,但我还没有能卡住它。所以也许MessageChannel在Safari中存在问题...

0

评论者: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))
    ;; 编译、刷新、重新加载、重复
    (loop [状态 状态]
      (let [新状态 (try
                        (-> 状态
                            (cljs/step-compile-modules)
                            (cljs/flush-unoptimized)
                            (cljs/wait-and-reload!))
                       (catch Throwable t
                          (prn [:编译失败 t])
                          (.printStackTrace t)
                          (cljs/wait-and-reload! 状态)))]
        (recur 新状态)))))

(defn 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)
      (define-modules)
      (cljs/step-compile-modules)
      (cljs/closure-optimize)
      (cljs/flush-to-disk)
      (cljs/flush-modules-to-disk)


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