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 被解析(在本例中,这是在进程结束后)。
我遇到过一种情况,在使用 sh 调用 git 时,生成的输出足够大,以至于产生了 OutOfMemoryError
。这就是你得到“死锁”的地方(技术上并不是)。
- 当 git 把数据塞入管道时,Java 进程在未来的某个时刻读取它,主线程等待进程结束。
- 读取
out
的未来遇到 OutOfMemory 异常,它被存储,并且 out
读取循环终止
- 进程之间的管道缓冲区填满了,因为 git 正在写入,而没有人再读取了
- Git 试图无限期地向管道写入而停滞不前
- Java 进程的主线程无限期地等待 git 进程结束
- 未来从未被解析,因此从未产生异常堆栈跟踪或消息
这种情况可以在其中一个 future 的读取过程中,遇到任何类型的异常时发生。另一个实际的场景可能是,如果你指定了一个编码,而底层进程返回了一些不能在那个编码中解析的字节。
这个“错误”相当令人沮丧,因为没有错误消息,而且几乎没有错误指示。在我的情况下,它偶尔在生产中发生,很难追踪。
我不知道解决方案是什么,但为这种状况做些缓解可能是个好主意。