如何检测子进程是否因为其输出缓冲区已满而挂起?
How can I detect if a subprocess is hung because its output buffer is full?
考虑以下 Bash 脚本和 Java 程序:
$ cat kb.sh
#!/bin/bash
# Prints KB - fold adds a \n
tr '[=10=]' '=' < /dev/zero | fold -w 1023 | head -n ${1:-10}
$ cat Demo.java
import java.util.concurrent.TimeUnit;
class Demo {
public static void main(String[] args) throws Exception {
Process p = Runtime.getRuntime()
.exec("/tmp/kb.sh " + (args.length > 0 ? args[0] : ""));
if (p.waitFor(10, TimeUnit.SECONDS)) {
System.out.println("Process terminated");
} else {
System.err.println("Process did not terminate");
p.destroy();
System.exit(1);
}
}
}
Demo
class 作为子进程启动 kb.sh
,并期望它快速终止。 kb.sh
,就其本身而言,输出(大概很快)一些 KB 的数据。我们可以在实践中快速验证它 运行s:
$ time /tmp/kb.sh 10000 | wc
10000 10000 10240000
real 0m0.398s
user 0m0.178s
sys 0m0.030s
当我们 运行 Demo
class 但是我们看到不同的行为:
$ java -cp . Demo 64
Process terminated
$ java -cp . Demo 65
Process did not terminate
如果我们尝试打印 ~65KB 它会挂起。我知道为什么 - Process
正在缓冲子进程的输出,当它的缓冲区变满时,子进程会阻塞,直到通过 Process.getInputStream()
从缓冲区中读出一些数据。如果您在 p.waitFor()
之前添加了对 ByteStreams.exhaust(p.getInputStream());
的调用,该过程将始终成功终止。
我的问题是,in Java 是否有任何方法可以检测子进程何时像这样被阻塞?我担心答案可能是 "not without reflection",因为我在任何相关 API 中都没有看到任何此类机制,但我可能遗漏了一些东西。
为了防止不可避免的 "Why do you want to do this?",我正在编写一个诊断实用程序来检测现有 Process
实例中的此问题,因为它是一个持续的(和邪恶的)错误来源。我不想操纵 Process
或做任何破坏性的事情,我只是想检测进程何时因缓冲区已满而停止,以便我可以提醒调用者。
注意:依赖 OS 的解决方案,例如检查 ps
的输出,是可以接受的,但显然不如仅依赖 Java 的解决方案理想。
您不必检测它。您必须消耗 它的所有输出,包括标准输出和标准错误。如果您的代码不这样做,修复它。
简短回答:无法检测缓冲区已满是否是挂起子进程的原因。
更长的答案:Java I/O 流 API 不提供任何方法来确定
缓冲流的状态。您无法确定缓冲区是否已满。更糟糕的是,您甚至不知道缓冲区中有多少 space 可用,因此即使不是不可能,也很难确定下一个 write() 操作是否会阻塞。当然,一旦被阻止,它就不会响应任何内容。
没有要求子进程产生或响应 "heartbeat" ping 来证明它是活动的并且没有挂起——一旦它挂起就没有办法知道为什么——你无能为力主动或被动地处理完整的缓冲流而不是读取它们。
考虑以下 Bash 脚本和 Java 程序:
$ cat kb.sh
#!/bin/bash
# Prints KB - fold adds a \n
tr '[=10=]' '=' < /dev/zero | fold -w 1023 | head -n ${1:-10}
$ cat Demo.java
import java.util.concurrent.TimeUnit;
class Demo {
public static void main(String[] args) throws Exception {
Process p = Runtime.getRuntime()
.exec("/tmp/kb.sh " + (args.length > 0 ? args[0] : ""));
if (p.waitFor(10, TimeUnit.SECONDS)) {
System.out.println("Process terminated");
} else {
System.err.println("Process did not terminate");
p.destroy();
System.exit(1);
}
}
}
Demo
class 作为子进程启动 kb.sh
,并期望它快速终止。 kb.sh
,就其本身而言,输出(大概很快)一些 KB 的数据。我们可以在实践中快速验证它 运行s:
$ time /tmp/kb.sh 10000 | wc
10000 10000 10240000
real 0m0.398s
user 0m0.178s
sys 0m0.030s
当我们 运行 Demo
class 但是我们看到不同的行为:
$ java -cp . Demo 64
Process terminated
$ java -cp . Demo 65
Process did not terminate
如果我们尝试打印 ~65KB 它会挂起。我知道为什么 - Process
正在缓冲子进程的输出,当它的缓冲区变满时,子进程会阻塞,直到通过 Process.getInputStream()
从缓冲区中读出一些数据。如果您在 p.waitFor()
之前添加了对 ByteStreams.exhaust(p.getInputStream());
的调用,该过程将始终成功终止。
我的问题是,in Java 是否有任何方法可以检测子进程何时像这样被阻塞?我担心答案可能是 "not without reflection",因为我在任何相关 API 中都没有看到任何此类机制,但我可能遗漏了一些东西。
为了防止不可避免的 "Why do you want to do this?",我正在编写一个诊断实用程序来检测现有 Process
实例中的此问题,因为它是一个持续的(和邪恶的)错误来源。我不想操纵 Process
或做任何破坏性的事情,我只是想检测进程何时因缓冲区已满而停止,以便我可以提醒调用者。
注意:依赖 OS 的解决方案,例如检查 ps
的输出,是可以接受的,但显然不如仅依赖 Java 的解决方案理想。
您不必检测它。您必须消耗 它的所有输出,包括标准输出和标准错误。如果您的代码不这样做,修复它。
简短回答:无法检测缓冲区已满是否是挂起子进程的原因。
更长的答案:Java I/O 流 API 不提供任何方法来确定 缓冲流的状态。您无法确定缓冲区是否已满。更糟糕的是,您甚至不知道缓冲区中有多少 space 可用,因此即使不是不可能,也很难确定下一个 write() 操作是否会阻塞。当然,一旦被阻止,它就不会响应任何内容。
没有要求子进程产生或响应 "heartbeat" ping 来证明它是活动的并且没有挂起——一旦它挂起就没有办法知道为什么——你无能为力主动或被动地处理完整的缓冲流而不是读取它们。