如何检测子进程是否因为其输出缓冲区已满而挂起?

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 来证明它是活动的并且没有挂起——一旦它挂起就没有办法知道为什么——你无能为力主动或被动地处理完整的缓冲流而不是读取它们。