"No value present" 在调用 `ProcessHandle` 的 `parent().get()` 时

"No value present" when calling `ProcessHandle`'s `parent().get()`

import java.lang.ProcessBuilder.Redirect;

public class Main {    
  public static void main(String args[]){    
ProcessBuilder pb = new ProcessBuilder(args); 

try{
Process p = pb.start();
   System.out.println("child process is alive: " + p.isAlive());   
   System.out.println("child process pid: " + p.pid());
   System.out.println("parent of child process: " + p.toHandle().parent().get().pid());
   System.out.println("child process is alive: " + p.isAlive());      
   p.waitFor();

} catch (Exception e) {
    System.out.println("some exception with start process.");
    e.printStackTrace();    
}

  }
}

在 Ubuntu 18.04 上,我 运行 程序到 运行 外部程序 sleep 10 没有问题:

$ java Main  sleep 10
child process is alive: true
child process pid: 20559
parent of child process: 20539
child process is alive: true
$

但是当我运行程序到运行一个外部程序时echo hello

$ java Main  echo hello
child process is alive: false
child process pid: 18534
some exception with start process.
java.util.NoSuchElementException: No value present
    at java.base/java.util.Optional.get(Optional.java:148)
    at Main.main(Main.java:11)

为什么第11行p.toHandle().parent().get().pid()get()处报错?

如果我注释掉第 11 行,它将 运行 没有问题。这是为什么?

如果是因为child在第11行调用p.toHandle().parent().get().pid()之前退出,

我该怎么做才能使问题不发生?例如,我怎样才能使 child 持续更长时间,以便 p.toHandle().parent().get().pid() 可以工作,即使我 运行 在 child 进程中运行了一个短暂的程序?

为什么使用 echo hello 调用时从 parent() 返回的可选值是空的?

因为child进程很短,当你到达要求parent的命令时,child进程已经不存在了。因此,无法再向操作系统查询该进程。

为什么调用 getHandle()pid() 时不会发生这种情况?

请注意,这些都不是 Optional。这意味着 API 保证将在其中返回值。事实上,实现是这样的,只要操作系统创建进程并在 pb.start() 中返回,就会创建句柄(包括进程 ID)。所以你有一个句柄,你有一个进程 ID - 但是当你使用它时,不能保证具有该 ID 的进程是活着的。

documentation 明确告诉您不要对底层进程的活跃度或身份做出假设。

parent() 调用目前是通过使用 child 的 pid 查询操作系统来实现的。所以 parent 在创建流程时不是流程记录的一部分,并且在您调用它时可能不再可用。

如何避免这种情况

无论何时使用 Optional,都不要使用 get。尤其是事先检查它是否存在(并且使用 isPresent ... get 被认为是 "antipattern")。当你看到一个 API 给你一个 Optional 时,想一想如果那个 Optional 恰好是空的你想做什么。不要做出文档不合理的假设。

在这种情况下,如果找不到 parent,您可能希望显示默认消息。例如:

System.out.println("parent of child process: "
    + p.toHandle().parent().map(ProcessHandle::pid)
                           .map(Object::toString)
                           .orElse("not available"));

或者您可以使用当前进程的 pid 作为默认值,或者您可以想出的其他任何东西。或者,如果 parent 的身份对于您的工作绝对必要,您可以抛出另一种异常。请参阅 Optional 的文档,了解使用它的各种优雅方式。

无法扩展底层进程 - 您可以使用 shell 脚本,该脚本会在执行您传递的命令后休眠,但我发现此解决方案充其量是笨拙的。