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

欢迎!请参阅 关于 页面以获取更多有关该功能的说明。

+1
IO

clojure.java.shell/sh 函数在末尾具有以下结构

(with-open [stdout (.getInputStream proc)
            stderr (.getErrorStream proc)]
  (let [out (future (stream-to-enc stdout out-enc))
        err (future (stream-to-string stderr))
        exit-code (.waitFor proc)]
    {:exit exit-code :out @out :err @err}))

因此,进程的输出将读入两个 future,而当前线程等待进程结束(并生成退出代码)。如果在 future 执行期间发生异常,则该异常被存储并抛出(包装在 ExecutionException 中),当 deref futures 时(在这种情况下,这是在进程结束时)。

我曾使用 sh 调用 git 时,生成输出量足够大,产生了 OutOfMemoryError。这正是您会遇到“死锁”的地方(从技术上讲,它不是)。

  • git 正在将数据推入管道,而 Java 进程正在未来的线程中读取它,主线程正在等待进程结束
  • Future 读取 out 时遇到 OutOfMemory 异常,该异常被存储,并且 out 读取循环终止
  • 进程间的管道缓冲区变得满载,因为 git 正在写入,但没有人在读取了
  • git 无限期地尝试写入管道
  • Java 进程的主线程无限期地等待 git 进程结束
  • Future 从未被 deref,因此永远不会有异常堆栈跟踪或消息产生

这种情况可能会在任何一种异常发生在这两个 future 读取期间时发生。另一种真实场景将是我指定了一个编码,而底层进程返回了一些在该编码中无法解析的字节。

这个“问题”还是挺令人沮丧的,因为它既没有错误消息,也没有太多关于错误原因的提示。在我的情况下,它在生产环境中随机发生,并且很难追踪。

我不了解解决方案是什么,但可能 smart 到采取某种形式的缓解措施。

1 个答案

+1
by
by
目前我编写了自己的sh脚本,在futures中的代码被try-catch包围,在catch中通过循环读取输入流到一个非常小的缓冲区(而缓冲区从未被使用),直到耗尽。在使用OOM时,读取到堆栈上的int可能会更智能。
by
解决这个问题的一个方法是使用Clojure 1.12中引入的新clojure.java.process api。

    (require '[clojure.java.process :as process])
    (def result (process/exec "git" "whatever")))

如果遇到OOME,你会得到这样的异常

    在clojure.java.process/exec (process.clj:171)处执行错误。
    进程以退出码141失败

我认为141通常表示SIGPIPE,所以它至少不会挂起。
...