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进程结束。
- 未来从未被解除引用,因此从没有异常堆栈跟踪或消息产生。
这种情况可能发生在这些两个未来读取的任何类型异常期间。另一个现实的场景可能是如果我指定了一个编码,而底层进程返回了某些无法在该编码中解析的字节。
这个“错误”相当令人沮丧,因为没有错误消息,几乎没有关于出错的提示。在我的情况下,这种情况在生产环境中偶尔发生,很难追踪。
我不知道补救办法,但可能要对此进行某种形式的缓解。