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 中),当 futures 被解引用时(在此情况下这是在进程结束后)。
我曾经调用 git 并使用 sh
,生成的输出足够多以至于产生 OutOfMemoryError
。这就是您会遇到“死锁”的地方(实际上它不是一个死锁)。
- Git 正在将数据推入管道,而 Java 进程正在 future 中读取它,主线程正在等待进程结束
- 读取
out
的 future 遇到 OutOfMemory 异常,该异常被存储并为 out
读取器循环终止
- 进程之间的管道缓冲区变得满载,因为 git 正在写入,但没有人在读取
- Git 无限期地陷入尝试写入管道的僵局中
- Java 进程主线程无限期地等待 git 进程结束
- 未来永远未被解引用,因此永远不会产生异常堆栈跟踪或消息
这可能在两个 future 读取过程中的任何类型的异常都会发生。另一个可能的场景是,如果指定了编码,而底层进程返回一些无法解析为该编码的字节。
这个“错误”相当令人沮丧,因为它没有错误消息,并且几乎没有关于问题的指示。在我的情况下,它在生产环境中偶尔发生,并且很难追踪。
我不知道补救办法,但可能需要对此进行某种形式的缓解。