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 到采取某种形式的缓解措施。