ProcessBuilder 挂起
ProcessBuilder hangs
我 运行 使用此代码的命令:
open class AppRunner {
fun run(
app: String,
args: Array<String>,
timeoutAmount: Long = 6000,
timeoutUnit: TimeUnit = TimeUnit.SECONDS
): AppResult {
val command = mutableListOf(app)
.apply {
addAll(args)
}
val commandString = command.joinToString(" ") { "\"$it\"" }
Kimber.d("Executing command: $commandString")
val processResult = ProcessBuilder(command)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
.apply {
waitFor(timeoutAmount, timeoutUnit)
}
val exitCode = processResult.exitValue()
val stdOut = processResult.inputStream.bufferedReader().readText()
val stdErr = processResult.errorStream.bufferedReader().readText()
return AppResult(exitCode, stdOut, stdErr)
}
data class AppResult(
val exitCode: Int,
val stdOut: String,
val stdErr: String
) {
fun isSuccessful(): Boolean = exitCode == 0
fun getStdOutLines(): List<String> = stdOut.split("\n")
fun getStdErrLines(): List<String> = stdOut.split("\n")
}
}
像这样:
val args = arrayOf(
audioFile.absolutePath,
"-r",
getRecognizer(language),
"-f",
"json",
"-q"
)
val result = appRunner.run(rhubarbBinary.absolutePath, args)
对于像 ffmpeg
这样的一些程序,它可以工作,但上面的例子没有。
«Raw» 命令是 "/Users/user/<path>/rhubarb" "/var/folders/g6/bmyctvjn7fl3m8kdr0cs1hk80000gn/T/lipsync_audio_14979831388784829375.wav" "-r" "phonetic" "-f" "json" "-q"
,如果我手动 运行 它,它工作正常。
但是如果我用上面的代码 运行 它,它就不会启动并冻结。
我确定它没有启动,因为此命令大约需要 30 秒才能完成并在 运行ning 时消耗 100% CPU,并且在 运行ning 时使用此命令它根本不加载 CPU 的代码。
我在 JVM 8、macOS 10.15.4 上使用 Kotlin 1.3.71。
怎么了?
你在读取管道输出之前等待程序结束,但是管道只有有限的缓冲区,所以当缓冲区已满时,程序将等待你使用缓冲输出,但是你'正在等待程序结束。 死锁!
总是在调用 waitFor()
之前消耗输出。
更新
建议您修改代码如下:
val process = ProcessBuilder(command)
.redirectErrorStream(true)
.start()
val stdOut = processResult.inputStream.bufferedReader().readText()
if (process.waitFor(timeoutAmount, timeoutUnit)) {
val exitCode = processResult.exitValue()
return AppResult(exitCode, stdOut, "")
}
// timeout: decide what to do here, since command hasn't terminated yet
无需指定 Redirect.PIPE
,因为这是默认值。如果你不像这里显示的那样加入 stderr 和 stdout,你需要创建线程来单独使用它们,因为它们都有缓冲区已满的问题,所以你不能先读取其中一个。
我 运行 使用此代码的命令:
open class AppRunner {
fun run(
app: String,
args: Array<String>,
timeoutAmount: Long = 6000,
timeoutUnit: TimeUnit = TimeUnit.SECONDS
): AppResult {
val command = mutableListOf(app)
.apply {
addAll(args)
}
val commandString = command.joinToString(" ") { "\"$it\"" }
Kimber.d("Executing command: $commandString")
val processResult = ProcessBuilder(command)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
.apply {
waitFor(timeoutAmount, timeoutUnit)
}
val exitCode = processResult.exitValue()
val stdOut = processResult.inputStream.bufferedReader().readText()
val stdErr = processResult.errorStream.bufferedReader().readText()
return AppResult(exitCode, stdOut, stdErr)
}
data class AppResult(
val exitCode: Int,
val stdOut: String,
val stdErr: String
) {
fun isSuccessful(): Boolean = exitCode == 0
fun getStdOutLines(): List<String> = stdOut.split("\n")
fun getStdErrLines(): List<String> = stdOut.split("\n")
}
}
像这样:
val args = arrayOf(
audioFile.absolutePath,
"-r",
getRecognizer(language),
"-f",
"json",
"-q"
)
val result = appRunner.run(rhubarbBinary.absolutePath, args)
对于像 ffmpeg
这样的一些程序,它可以工作,但上面的例子没有。
«Raw» 命令是 "/Users/user/<path>/rhubarb" "/var/folders/g6/bmyctvjn7fl3m8kdr0cs1hk80000gn/T/lipsync_audio_14979831388784829375.wav" "-r" "phonetic" "-f" "json" "-q"
,如果我手动 运行 它,它工作正常。
但是如果我用上面的代码 运行 它,它就不会启动并冻结。
我确定它没有启动,因为此命令大约需要 30 秒才能完成并在 运行ning 时消耗 100% CPU,并且在 运行ning 时使用此命令它根本不加载 CPU 的代码。
我在 JVM 8、macOS 10.15.4 上使用 Kotlin 1.3.71。
怎么了?
你在读取管道输出之前等待程序结束,但是管道只有有限的缓冲区,所以当缓冲区已满时,程序将等待你使用缓冲输出,但是你'正在等待程序结束。 死锁!
总是在调用 waitFor()
之前消耗输出。
更新
建议您修改代码如下:
val process = ProcessBuilder(command)
.redirectErrorStream(true)
.start()
val stdOut = processResult.inputStream.bufferedReader().readText()
if (process.waitFor(timeoutAmount, timeoutUnit)) {
val exitCode = processResult.exitValue()
return AppResult(exitCode, stdOut, "")
}
// timeout: decide what to do here, since command hasn't terminated yet
无需指定 Redirect.PIPE
,因为这是默认值。如果你不像这里显示的那样加入 stderr 和 stdout,你需要创建线程来单独使用它们,因为它们都有缓冲区已满的问题,所以你不能先读取其中一个。