2024 年 Clojure 调查问卷! 中分享您的想法。

欢迎!请在 关于 页面上查看更多有关如何使用本站的信息。

0
core.async
大家好,这里有一个难以报告的问题... 我们在使用 ClojureScript 上的 core.async 时发现了一些问题。我们的应用程序围绕着一个大型的消息循环,该循环在来自许多与用户活动或 API 调用对应的通道之一的消息上阻塞。问题似乎存在于这个消息循环中 - 我们正在使用 alts! 从任何可用的通道中拉取消息,但有时日志显示我们到达 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.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! 回调给出 us true (socket.js,第 89 行)
[调试] Metronome:staff 数据解码。 put! 完成: 12.282ms (socket.js,第 93 行)
[日志] 将消息放入具有哈希  19 的通道中 (socket.js,第 86 行)
[日志] 消息是 [:metronome [:class-types [{:deletable false, :picture nil, :name "CycleCore", :id 2, :description "CycleCore 是一种 55 分钟的双练概念,结合了 30 分钟的高强度有氧运动 ... (socket.js,第 87 行)
[日志] put! 回调给出 us true (socket.js,第 89 行)
[调试] Metronome:class-types 数据解码。 put! 完成: 1.288ms (socket.js,第 93 行)
[日志] 将消息放入具有哈希  19 的通道中 (socket.js,第 86 行)
[日志] 消息是 [:metronome [:locations [{:studios [{:deletable false, :name "Kensington", :id 1, :locationId 1, :description "Studio (11a) 位于 Stratford 交通枢纽tad ... (socket.js,第 87 行)
【调试】节拍器:位置数据解码。写入完成:0.884毫秒(socket.js,第93行)


请注意,我们看到了“alts!信道散列”的日志条目,但我们从未看到“alts!解除阻塞”。然而,请注意传递给alts!的散列列表。19号信道被提及,但我们随后将其写入19号信道...但我们仍然没有得到解除阻塞。我也觉得有些可疑的是,当我们被阻塞在alts!时,对于仅绑定容纳一次元素的信道,两次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() {
//回退到0毫秒的setTimeout。在浏览器中,这会产生5毫秒或更长的延迟
//或更多。
return function(cb) {

goog.global.setTimeout(cb, 0);

};
};
`

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

0

评论由:gshayban

嗨奥利弗,看起来像是竞争条件,我们会找出这个问题的。

你介意compare在0.1.319.0-6b1aca-alpha与0.1.346.0-17112a-alpha上运行吗?

alts!应该传递一个索引集合/向量。传递一个seq不会引起这个问题,只是一些要注意的事情。

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_

是的,这绝对是优化问题。以下是我的 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)
    ;; 编译、刷新、重新加载、重复
    (loop [state state]
      (let [new-state (try
                        (-> state
                            (cljs/step-compile-modules)
                            (cljs/flush-unoptimized)
                            (cljs/wait-and-reload!))
                       (carry Throwable t
                         (print [:failed-to-compile 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
回答 by
参考: https://clojure.atlassian.net/browse/ASYNC-97 (由 alex+import 报告)
...