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

欢迎!有关如何使用本站的信息,请参阅 关于 页面。

+2
REPL
已关闭

io-prepl 使用 valf 序列化 eval 结果,但如果序列化值在打印时抛出异常,则不会序列化异常本身。

复现

# terminal 1
clj -X clojure.core.server/start-server \
  :name '"io-prepl"' \
  :port 7777 \
  :accept clojure.core.server/io-prepl \
  :server-daemon false

# terminal 2
nc localhost 7777
(reify Object (toString [_] (/ 1 0)))
{:tag :ret, :val {:via [{:type java.lang.ArithmeticException, :message "Divide by zero", :at [clojure.lang.Numbers divide "Numbers.java" 188]}], :trace [[clojure.lang.Numbers divide "Numbers.java" 188] [clojure.lang.Numbers divide "Numbers.java" 3901] [user$eval220$reify__221 toString "NO_SOURCE_FILE" 3] [clojure.core$str invokeStatic "core.clj" 555] [clojure.core$print_object invokeStatic "core_print.clj" 117] [clojure.core$fn__7297 invokeStatic "core_print.clj" 120] [clojure.core$fn__7297 invoke "core_print.clj" 120] [clojure.lang.MultiFn invoke "MultiFn.java" 234] [clojure.core$pr_on invokeStatic "core.clj" 3662] [clojure.core$pr invokeStatic "core.clj" 3665] [clojure.core$pr invoke "core.clj" 3665] [clojure.lang.AFn applyToHelper "AFn.java" 154] [clojure.lang.RestFn applyTo "RestFn.java" 132] [clojure.core$apply invokeStatic "core.clj" 667] [clojure.core$pr_str invokeStatic "core.clj" 4724] [clojure.core$pr_str doInvoke "core.clj" 4724] [clojure.lang.RestFn invoke "RestFn.java" 408] [clojure.core.server$io_prepl$fn__8978$fn__8979 invoke "server.clj" 289] [clojure.core.server$io_prepl$fn__8978 invoke "server.clj" 288] [clojure.core.server$prepl$fn__8964 invoke "server.clj" 239] [clojure.core.server$prepl invokeStatic "server.clj" 228] [clojure.core.server$prepl doInvoke "server.clj" 191] [clojure.lang.RestFn invoke "RestFn.java" 425] [clojure.core.server$io_prepl invokeStatic "server.clj" 283] [clojure.core.server$io_prepl doInvoke "server.clj" 272] [clojure.lang.RestFn invoke "RestFn.java" 397] [clojure.lang.AFn applyToHelper "AFn.java" 152] [clojure.lang.RestFn applyTo "RestFn.java" 132] [clojure.lang.Var applyTo "Var.java" 705] [clojure.core$apply invokeStatic "core.clj" 667] [clojure.core.server$accept_connection invokeStatic "server.clj" 73] [clojure.core.server$start_server$fn__8902$fn__8903$fn__8905 invoke "server.clj" 117] [clojure.lang.AFn run "AFn.java" 22] [java.lang.Thread run "Thread.java" 834]], :cause "Divide by zero", :phase :print-eval-result}, :ns "user", :ms 3, :form "(reify Object (toString [_] (/ 1 0)))", :exception true}

这会导致与 io-prepl 连接的其他预处理器(如 remote-prepl)出现问题,因为这些预处理器始终使用其 valf 进行反序列化

# terminal 3
clj
Clojure 1.10.3
user=> ((requiring-resolve 'clojure.core.server/remote-prepl) "localhost" 7777 *in* prn)
(reify Object (toString [_] (/ 1 0)))
{:tag :ret, :val {:via [{:type java.lang.ClassCastException, :message "class clojure.lang.PersistentArrayMap cannot be cast to class java.lang.String (clojure.lang.PersistentArrayMap is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')", :at [clojure.core$read_string invokeStatic "core.clj" 3793]}], :trace [[clojure.core$read_string invokeStatic "core.clj" 3793] [clojure.core$read_string invoke "core.clj" 3793] [clojure.core.server$remote_prepl$fn__8992$fn__8994 invoke "server.clj" 322] [clojure.core.server$remote_prepl$fn__8992 invoke "server.clj" 321] [clojure.lang.AFn run "AFn.java" 22] [java.lang.Thread run "Thread.java" 834]], :cause "class clojure.lang.PersistentArrayMap cannot be cast to class java.lang.String (clojure.lang.PersistentArrayMap is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')", :phase :read-eval-result}, :ns "user", :ms 1, :form "(reify Object (toString [_] (/ 1 0)))", :exception true}

在这种情况下,原始异常(打印时的除零错误)被另一个异常(试图 read-string 一个映射类的类转换异常)所掩盖

关闭时的备注: 在 1.11.0-beta1 中发布

2 答案

0

我认为你建议 https://github.com/clojure/clojure/blob/master/src/clj/clojure/core/server.clj#L291 应当将 valf 应用到 ex->data 的结果上?

(只是为了确保我理解你的意思)

by
我尝试只陈述问题,而不提出考虑不周全的解决方案 :)

想了一会儿,我认为在打印阶段异常上调用 valf 是合理的。我认为这是 io-prepl 的 valf 正确处理异常的一个要求。另一个我考虑的替代方案是,在 remote-prepl 中吞掉读取异常并返回未读取的值,但这样做会使 io-prepl 的语义变得含糊和不可靠,并使对 remote-prepl 的值进行推理变得复杂。
by
io-prepl 产生并远程-prepl 消耗 :val 的值。它们都有通过 valf 定制该行为的函数,但默认的 valf 是 (pr / read),即字符串化。在 io-prepl 中,valf 在结果值上调用,但不调用异常值。在 remote-prepl 中,valf 总是调用。这造成了一种不对称。

我觉得另一个可能的解决方案是让远程-prepl 在异常 :val 的情况下做不同的事情(你的建议是一条路径,另一条路径是根据 :exception 主动选择不同的路径),但这可能阻止了 valf 的原始目标(这允许调用者定制处理方式)。

在提交工单之前,只是讨论一下。
https://www.landfordinjectionmolding.com/wp-content/uploads/2021/04/image-2-20.jpg width="20" height="19" class="qa-avatar-image" alt=""> by
我在思考这个问题是出在 io-prepl 上(文档字符串似乎很明确)还是在远程-prepl 上(应该为 :exception 定制 :val 的读取)。
by
> 在 io-prepl 中,valf 在结果值上调用,但不调用异常值。

我认为这是错误,当执行阶段发生异常时,valf 将在异常值上调用。

    # 终端 1
    clj -X clojure.core.server/start-server \
    :name "io-prepl" \
    :port 7777 \
    :accept clojure.core.server/io-prepl \
    :server-daemon false

    # 终端 2
     nc localhost 7777
    (/ 1 0)
    {:tag :ret, :val "{:via [{:type java.lang.ArithmeticException, :message \"除以零\", :at [clojure.lang.Numbers divide \\"Numbers.java\" 188]}], :trace [[clojure.lang.Numbers divide \\\"Numbers.java\\" 188] [clojure.lang.Numbers divide \\"Numbers.java\\" 3901] [user$eval214 invokeStatic \\"NO_SOURCE_FILE\\" 2] [user$eval214 invoke \\"NO_SOURCE_FILE\\" 2] [clojure.lang.Compiler eval \\"Compiler.java\\" 7177] [clojure.lang.Compiler eval \\"Compiler.java\\" 7132] [clojure.core$eval invokeStatic \\"core.clj\\" 3214] [clojure.core.server$prepl$fn__8941 invoke \\"server.clj\\" 232] [clojure.core.server$prepl invokeStatic \\"server.clj\\" 228] [clojure.core.server$prepl doInvoke \\"server.clj\\" 191] [clojure.lang.RestFn invoke \\"RestFn.java\\" 425] [clojure.core.server$io_prepl invokeStatic \\"server.clj\\" 283] [clojure.core.server$io_prepl doInvoke \\"server.clj\\" 272] [clojure.lang.RestFn invoke \\"RestFn.java\\" 397] [clojure.lang.AFn applyToHelper \\"AFn.java\\" 152] [clojure.lang.RestFn applyTo \\"RestFn.java\\" 132] [clojure.lang.Var applyTo \\"Var.java\\" 705] [clojure.core$apply invokeStatic \\"core.clj\\" 665] [clojure.core.server$accept_connection invokeStatic \\"server.clj\\" 73] [clojure.core.server$start_server$fn__8879$fn__8880$fn__8882 invoke \\"server.clj\\" 117] [clojure.lang.AFn run \\"AFn.java\\" 22] [java.lang.Thread run \\"Thread.java\\" 832]], :cause \"除以零\", :phase :execution}", :ns \\"user\\", :form \\"(/ 1 0)\\", :exception true}

基于以上,我认为这是一个 io-prepl 的问题,而不是远程-prepl 的问题。
by
因此,异常是使用 valf 进行序列化的,因为 io-prepl 包装了将抛出的异常放在 `:val` 键上的 prepl。我建议更改 CLJ-2620 中的问题说明,因为它说 io-prepl 不序列化错误值,但实际上 io-prepl 不序列化一些错误值,即序列化期间抛出的异常。
0
by
...