2024 State of Clojure Survey!中分享您的想法!

欢迎!请参阅关于页面以获取有关此页如何工作的更多信息。

0
core.async
大家好,这里有一个棘手的bug要报告......我们在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, line 62)
[日志] alts! 通道哈希值:  (16 12 19 33)(main.js, line 82)
[日志] Socket 已连接。 (socket.js, line 309)
[日志] put! 到具有散列  19 的通道 (socket.js, line 86)
[日志] 消息是[:metronome [:staff [{:description null, :deletable true, :email null, :isAdmin true, :isTrainer false, :telephone null, :name "Fynder Admin", :picture null, :userId 1} {:description null, :deletable fa... (socket.js, line 87)
[日志] put! 回调给我们 true (socket.js, line 89)
[调试] Metronome: staff 数据解码。 put! 完成:12.282ms (socket.js, line 93)
[日志] put! 到具有散列  19 的通道 (socket.js, line 86)
[日志] 消息是[:metronome [:class-types [{:deletable false, :picture null, :name "CycleCore", :id 2, :description "CycleCore 是一个 55 分钟的双工作概念,结合 30 分钟的高强度心血管 ... (socket.js, line 87)
[日志] put! 回调给我们 true (socket.js, line 89)
[调试] Metronome: class-types 数据解码。 put! 完成:1.288ms (socket.js, line 93)
[日志] put! 到具有散列  19 的通道 (socket.js, line 86)
[日志] 消息是[:metronome [:locations [{:studios [{:deletable false, :name "Kensington", :id 1, :locationId 1, :description "Studio (11a) 位于 Stratford Road 的 Stratford Studios 附近。要找到我们,请穿过 ... (socket.js, line 87)
【调试】节拍器:获取位置数据解码完成。put! 完成状态:0.884ms(socket.js,行93)


请注意,我们看到了“alts!频道哈希”的日志条目,但我们从未看到“alts!解锁”。然而,请注意传给alts!的哈希列表。第19个频道被提到,但随后我们将put!应用到第19个频道上……但我们仍然没有被解锁。还有一件事让我感到可疑,那就是当我们被阻塞在alts!时,对put!的两个调用立即成功,而对一个仅需要在一次只包含一个元素的通道,我不会期望立即put回调被调用超过一次。也许我理解错了,但我不期望立即put回调被调用两次。有趣的是,最后一次put!没有调用回调。

遗憾的是,重现此错误相当困难。我可以通过退出Safari,重新打开它,然后导航到开发服务器来无故障地重现它。大约1/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);

};
};
`

我没有遇到卡住的情况。所以也许MessageChannel在Safari中有问题...

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版本并没有陷入停滞,这很奇怪 - 因为我相信我已经尝试将其升级到这个版本!我在两个通常有问题的情况下尝试过它,但它们一次也没有停滞。我正向更多的测试人员推出此版本,并看看会发生什么。

0

评论者:ocharles

哎呀,我知道这不会那么简单!在生产环境中部署后,它立即再次冻结。但是开发服务器运行的是非常不同的优化,所以我会构建一个生产版本并在本地提供服务 - 我们将看看会发生什么。

0
_评论由:ocharles_做出

是的,这绝对是一个优化问题。以下是我的Shadow Build配置


(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)
                         :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))]
    ;; 编译,刷新,重新加载,重复
    (循环 [状态 状态]
      (设 [新状态 (try
                              (-> 状态
                                 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! 状态)))]
         (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
参考: https://clojure.atlassian.net/browse/ASYNC-97(由 alex+import 报告)
...