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

欢迎!请查看 关于 页面,了解更多有关此工作方式的信息。

+5
REPL
已关闭

在 prepl 会话中,Clojure 警告会保留在缓冲区中,除非客户端显式刷新。

以下是一个示例 prepl 会话,使用 netcat(以 ;; 开头的行表示来自 prepl 服务器的响应)

$ clojure -X clojure.core.server/start-server :name prepl :port 5555 :accept clojure.core.server/io-prepl :server-daemon false &
$ nc localhost 5555

(set! *warn-on-reflection* true)
;; {:tag :ret, :val "true", :ns "user", :ms 2, :form "(set! *warn-on-reflection* true)"}

(.toString (identity "foo"))
;; {:tag :ret, :val "\"foo\"", :ns "user", :ms 4, :form "(.toString (identity \"foo\"))"}
(binding [*out* *err*] (flush))
;; {:tag :err, :val "Reflection warning, NO_SOURCE_PATH:2:1 - reference to field toString can't be resolved.\n"}
;; {:tag :ret, :val "nil", :ns "user", :ms 4, :form "(binding [*out* *err*] (flush))"}

(defn identity [x] x)
;; {:tag :ret, :val "#'user/identity", :ns "user", :ms 3, :form "(defn identity [x] x)"}
(binding [*out* *err*] (flush))
;; {:tag :err, :val "WARNING: identity already refers to: #'clojure.core/identity in namespace: user, being replaced by: #'user/identity\n"}
;; {:tag :ret, :val "nil", :ns "user", :ms 2, :form "(binding [*out* *err*] (flush))"}

这可能是由于 prepl 的 *err* PrintWriter 未启用 自动刷新,以及大多数 Clojure 警告都不会显式刷新。

这是否是一个需要解决的问题?或者这是有意为之的行为,客户端有责任在一定时间内刷新错误流吗?

注:已关闭,发布在 Clojure 1.12.0-beta1 中

4 个答案

+2

以下是一个补丁。我没有其他方式可以贡献这个。

From 52fe3111bf0f8f769121bdc2c383a3f11aac695f Mon Sep 17 00:00:00 2001
From: Ray McDermott <[email protected]>
Date: Tue, 26 Apr 2022 21:52:21 +0200
Subject: [PATCH] Fix for #CLJ-2645

---
 src/clj/clojure/core/server.clj |  4 ++--
 src/clj/clojure/core_print.clj  | 41 ++++++++++++++++++---------------
 2 files changed, 24 insertions(+), 21 deletions(-)

diff --git a/src/clj/clojure/core/server.clj b/src/clj/clojure/core/server.clj
index 3cda4d1f74..c774cab3b5 100644
--- a/src/clj/clojure/core/server.clj
+++ b/src/clj/clojure/core/server.clj
@@ -220,8 +220,8 @@
     (m/with-bindings
       (in-ns 'user)
       (binding [*in* (or stdin in-reader)
-                *out* (PrintWriter-on #(out-fn {:tag :out :val %1}) nil)
-                *err* (PrintWriter-on #(out-fn {:tag :err :val %1}) nil)]
+                *out* (PrintWriter-on #(out-fn {:tag :out :val %1}) nil true)
+                *err* (PrintWriter-on #(out-fn {:tag :err :val %1}) nil true)]
         (try
           (add-tap tapfn)
           (loop []
diff --git a/src/clj/clojure/core_print.clj b/src/clj/clojure/core_print.clj
index 18c5101c1d..4f10b780a1 100644
--- a/src/clj/clojure/core_print.clj
+++ b/src/clj/clojure/core_print.clj
@@ -559,23 +559,26 @@
 (defn ^java.io.PrintWriter PrintWriter-on
   "implements java.io.PrintWriter given flush-fn, which will be called
   when .flush() is called, with a string built up since the last call to .flush().
-  if not nil, close-fn will be called with no arguments when .close is called"
+  if not nil, close-fn will be called with no arguments when .close is called.
+  autoflush? determines if the PrintWriter will autoflush, false by default."
   {:added "1.10"}
-  [flush-fn close-fn]
-  (let [sb (StringBuilder.)]
-    (-> (proxy [Writer] []
-          (flush []
-                 (when (pos? (.length sb))
-                   (flush-fn (.toString sb)))
-                 (.setLength sb 0))
-          (close []
-                 (.flush ^Writer this)
-                 (when close-fn (close-fn))
-                 nil)
-          (write [str-cbuf off len]
-                 (when (pos? len)
-                   (if (instance? String str-cbuf)
-                     (.append sb ^String str-cbuf ^int off ^int len)
-                     (.append sb ^chars str-cbuf ^int off ^int len)))))
-        java.io.BufferedWriter.
-        java.io.PrintWriter.)))
+  ([flush-fn close-fn]
+   (PrintWriter-on flush-fn close-fn false))
+  ([flush-fn close-fn autoflush?]
+   (let [sb (StringBuilder.)]
+     (-> (proxy [Writer] []
+           (flush []
+             (when (pos? (.length sb))
+               (flush-fn (.toString sb)))
+             (.setLength sb 0))
+           (close []
+             (.flush ^Writer this)
+             (when close-fn (close-fn))
+             nil)
+           (write [str-cbuf off len]
+             (when (pos? len)
+               (if (instance? String str-cbuf)
+                 (.append sb ^String str-cbuf ^int off ^int len)
+                 (.append sb ^chars str-cbuf ^int off ^int len)))))
+         java.io.BufferedWriter.
+         (java.io.PrintWriter. ^boolean autoflush?)))))
已添加到JIRA问题中
我们已将其添加到1.12候选列表,并会尽快再次讨论。感谢您的贡献!
谢谢您的鼓励 :)
0

此问题似乎已作为 CLJ-2645 在JIRA中提交。

0

我们最近在 Clojurian Slack 上讨论了这个话题,@alex 希望能得到进一步的跟进。

客户端进行刷新时的问题在于消息会失序显示。

刷新的要求尚未记录在案 - 尽管可能是这样的。

然而,在同一个 ns 中维护的 io-prepl 客户端(它是一个 prepl 客户端)的情况下,io-prepl 或其客户端不能强制刷新,因此错误消息会丢失。

prepl 的文档字符串表明将恰好有一个 :ret

我假设一旦接收到 :ret 标签,执行就完成了。我不知道一个 prepl 客户端程序还应该如何理解执行的完成。

代码暗示这应该是最後一条消息,但我明白它可以有所改变。无论如何,如果它不是那条消息,则需要某种完成信号。

Slack 用户 @flowthing 提出了一个解决问题的方法,类似于你上面所展示的。

(when-not (= :repl/quit ret)
  (set! *3 *2)

替换为:

(when-not (= :repl/quit ret)
  (binding [*out* *err*] (flush)) ; this is the only difference to the original
  (set! *3 *2)

这是一个简单的解决方案,确保所有 prepl 客户端都能及时且按正确顺序(即在 :ret 之前)接收错误。

在没有这个修复的情况下,我不得不克隆 prepl 和 io-prepl,因为没有其他明显的方法从外部代码解决这个问题。

0

这里的基本问题在于 *err* 并没有得到有序的控制刷新。

为了完整性,Slack 用户 @flowthing 还提出了一个可能性,即在 PrintWriter-on 或其他 PrintWriter 包装器中添加一个选项,以使底层Writer自动刷新。

这也会工作,因为 prepl 将 outerr 绑定到 PrintWriter-on

客户端必须将 outfn 传递给 prepl,而由 io-prepl 传递的函数将 *flush-on-newline* 绑定为 true,这会被 prn 所尊重 - 每个值都会调用它。

因此,对 prn 的调用确保了 out 总是得到刷新。

*err* 也可以提供一个与 prn 类似的机制,该机制观察到 *flush-on-newline*,这也可以提供解决方案所需的各个部分。

通过比较两种选项并进行一些测试后,添加一个autoflush选项到 `PrinteWriter-on` 通过一个新的arity工作得很好,感觉像是一个更干净的解决方案。

尽管如此,我不想同时再分支一个核心功能,所以我现在会坚持使用 `binding` 欺骗。

如果得到一些鼓励,我将很高兴提供补丁。
Go Ray Go! You can do this! :)
没想到是这样的。好的!
Patch is done. I have signed the CCA back in the day but don't have a JIRA account
...