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}))

因此,进程的输出在两个未来中读取,而当前线程等待进程结束(并生成退出代码)。如果在未来的执行过程中发生异常,则该异常将被存储并抛出(在ExecutionException中封装),当未来解除引用时(在本例中,这是在进程结束后)。

我曾用sh调用git,产生的输出足够大,产生了OutOfMemoryError。这就是你得到“死锁”(从技术上讲,它不是)的地方。

  • Git将数据推入管道,而Java进程在将数据读入未来时,主线程正等待进程结束。
  • 当未来读取out时遇到OutOfMemory异常,它将存储并终止out读取循环。
  • 进程之间的管道缓冲区已满,因为git正在写入,但没有人在读取。
  • Git无限期地尝试将数据写入管道。
  • Java进程主线程无限期地等待git进程结束。
  • 未来从未被解除引用,因此从没有异常堆栈跟踪或消息产生。

这种情况可能发生在这些两个未来读取的任何类型异常期间。另一个现实的场景可能是如果我指定了一个编码,而底层进程返回了某些无法在该编码中解析的字节。

这个“错误”相当令人沮丧,因为没有错误消息,几乎没有关于出错的提示。在我的情况下,这种情况在生产环境中偶尔发生,很难追踪。

我不知道补救办法,但可能要对此进行某种形式的缓解。

1 答案

+1
通过
目前我已编写了自己的sh,其中将futures中的代码用try catch包裹,在catch中,通过循环将输入流读入一个非常小的缓冲区(缓冲区永远不会使用),直到耗尽。在OOM的情况下,将数据读入堆栈上的int可能会有所帮助。
通过
对此的一个解决方案是使用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,所以它至少不会挂起。
...