"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()
之前退出,
- 为什么
NoSuchElementException
发生在 get()
而不是更早的 toHandle()
或第 11 行的 p.toHandle().parent().get().pid()
中的 parent()
?
- 为什么在第10行调用
p.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 脚本,该脚本会在执行您传递的命令后休眠,但我发现此解决方案充其量是笨拙的。
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()
之前退出,
- 为什么
NoSuchElementException
发生在get()
而不是更早的toHandle()
或第 11 行的p.toHandle().parent().get().pid()
中的parent()
? - 为什么在第10行调用
p.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 脚本,该脚本会在执行您传递的命令后休眠,但我发现此解决方案充其量是笨拙的。