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

欢迎!请参阅关于页面了解有关如何使用此功能的一些更多信息。

0
core.async
大家好,这里有一个棘手的错误需要报告……我们在 ClojureScript 中的 Safari 7 环境下使用 core.async 时遇到一些问题。我们的应用围绕着一个大事件循环,该循环阻塞在来自众多用户活动或 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 的通道 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个频道被提及,但我们随后又将'put!'操作应用于第19个频道...但我们仍未获得解锁。一些让我感到可疑的事情,就是当我们被在"alts!"封锁时,对于只有一个元素绑定的频道,有两个"put!"操作成功立即执行。也许我误解了某些东西,但我不期望立即的"put!"回调被调用多次。有趣的是,最后一次"put!"没有调用回调。

不幸的是,这个错误的复现相当困难。我可以通过退出Safari,重新打开它并导航到开发服务器来有几分可靠地复现它。大约有1/15的尝试会以这种方式卡住。我想知道这与Safari的MessageChannel实现有关 - 你可以在日志条目中看到nexttick.js调用其回调,这看起来似是我浏览器中处理分发的方式。

我将非常乐意提供任何有用的信息,但这个问题的调试现在已经超出了我的能力。尽管代码是专有代码,但我愿意暂时将人员添加到GitHub项目中,以尝试修复这个问题。我们有开发API服务器,你可以指向这些服务器,所以这应该只是运行{{lein cljs}}的事情。

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

6 个答案

0
通过

由ocharles发表的评论

我深入到Google Closure库的内心深处,并将{{getSetImmediateEmulator_}}更改为

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

goog.global.setTimeout(cb, 0);

};
};
`

并且我还没有能够让它卡住。所以也许Safari的MessageChannel存在问题...

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 [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)))))

(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
参考: https://clojure.atlassian.net/browse/ASYNC-97(由 alex+import 报告)
...