0
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();    
}

  }
}

on Ubuntu 18.04, I run the program to run an external program sleep 10 without problme:

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

But when I run the program to run an external program 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)

Why does it report error at get() in line 11 p.toHandle().parent().get().pid()?

If I comment out line 11, it will run without problem. Why is that?

If it is because the child exits before calling p.toHandle().parent().get().pid() at line 11,

  • why does NoSuchElementException happen at get() instead of earlier at toHandle() or at parent() in p.toHandle().parent().get().pid() at line 11?
  • Why is there no such problem when calling p.pid() at line 10?

What can I do to make the problem not happen? For example, how can I make the child last longer, so that p.toHandle().parent().get().pid() can work, even when I run a short lived program in the child process?

halfer
  • 19,824
  • 17
  • 99
  • 186
Tim
  • 1
  • 141
  • 372
  • 590
  • I guess that your process has no parent, thats why when you call get on Optional and the value underneath is null - you get `NoSuchElementException`. – Michał Krzywański Apr 14 '19 at 11:45
  • The parent is still running when the `NoSuchElementException` message is printed. It is the process that I started with `java Main echo hello` – Tim Apr 14 '19 at 11:46
  • ` an Optional of the parent process; the Optional is empty if the child process does not have a parent or if the parent is not available, possibly due to operating system limitations` You might be running into one such limitation. – rdas Apr 14 '19 at 11:48
  • @DroidX86 Can you verify that on your machine? I am on Ubuntu 18.04 – Tim Apr 14 '19 at 11:52
  • 2
    Generally, never `get` from an `Optional`. Especially not without checking if it's present first. Use `orElse`etc. – RealSkeptic Apr 14 '19 at 11:52
  • @Tim What OS are you on? – rdas Apr 14 '19 at 11:52
  • @RealSkeptic Thanks. What is the line of code if using `orElse`? – Tim Apr 14 '19 at 11:53
  • @Droid Ubuntu 18.04 – Tim Apr 14 '19 at 11:53
  • @Tim I'm on the same. Let me try it – rdas Apr 14 '19 at 11:54
  • The use of `orElse` depends on what you want to use as default. What do you want to do if the process doesn't have a parent? – RealSkeptic Apr 14 '19 at 11:58
  • @Real I use `waitFor()`, so that is unlikely. – Tim Apr 14 '19 at 12:01
  • Why? What does `waitFor` have to do with the process's parent or lack of it? It is a fact that you don't get a parent process - that's why you got the `NoSuchElementException`. In fact, you can see that the process isn't running. Try the same program with `java Main /bin/sleep 10` and see if you get different results. – RealSkeptic Apr 14 '19 at 12:06
  • @RealSkeptic (1) The parent should remain existence when calling `p.waitFor()`. The `NoSuchElementException` happens at an earlier line, so the parent should still exists there. My apology that you have difficulty understand `waifFor`. (2) `java Main /bin/sleep 10` doesn't expose the problem, so I am not asking about it but about `java Main echo hello`. My apology that you don't know what my post is asking about. – Tim Apr 14 '19 at 12:10
  • 3
    That means that your process either failed or finished too soon, so when you are checking for its parent, it's no longer there. That's why it's working with a long process. `waitFor` has nothing to do with this. You are making an assumption that the parent process is available when the process itself no longer is. – RealSkeptic Apr 14 '19 at 12:12
  • @RealSkeptic if `waitFor` has nothing to do with this and if parent exits before calling `waitFor`, why does `java Main /bin/sleep 10` work without the `NoSuchElementException` problem ? My apology your comments don't make any sense. – Tim Apr 14 '19 at 12:18
  • 3
    @Tim read the javadoc: *Causes the current thread to wait, **if necessary**, until the process represented by this Process object has terminated. **This method returns immediately if the subprocess has already terminated**.* (emphasis mine). The child process is terminated when you try to get its parent, so you can't get it. In the case of a long running process, it's not terminated, so you can. waitFor doesn't prevent the child process to terminate as you seem to think it does. – JB Nizet Apr 14 '19 at 12:20
  • @JBNizet Thanks. I see. The parent still exists at line 11 but the child doesn't. (1) But why `NoSuchElementException` at `get()` instead of earlier at `toHandle()` or at `parent()` in `p.toHandle().parent().get().pid()`? (2) How can I make the child last longer , so that `p.toHandle().parent().get().pid()` at line 11 can work, even when I run a short lived program in the child process? – Tim Apr 14 '19 at 12:27
  • I'm pretty sure you can't. A process does what it's supposed to do, and I don't think you can prevent it from exiting from the outside. – JB Nizet Apr 14 '19 at 12:35
  • @JBNizet Thanks. why does `NoSuchElementException` happen at `get()` but not happen earlier at `toHandle()` or at `parent()` in `p.toHandle().parent().get().pid()`, or at `p.pid()` in the line before? – Tim Apr 14 '19 at 12:39
  • Read the javadoc of Optional. It's precisely designed to **force** you to realize that the returned value is... optional, i.e. might be absent. `get()` should have been named `getBecauseImSureThereIsSomethingInThisOptionalOtherwiseThrowAnExceptionToProveIWasWrong()`. You should only call get() if you'r **sure** the optional isn't empty. And you're not. – JB Nizet Apr 14 '19 at 12:45
  • @JBNizet Thanks. How shall I "only call get() if you'r sure the optional isn't empty"? How would you rewrite `p.toHandle().parent().get().pid()`? – Tim Apr 14 '19 at 12:58
  • RealSkeptic already suggested orElse(). But of course, it depends on what you want to do. Have you read the javadoc? Have you googled for "how to use Optional"? – JB Nizet Apr 14 '19 at 13:00

1 Answers1

2

Why is the optional returned from parent() empty when called using echo hello?

Because the child process is very short, and when you get to the command that asks for the parent, the child process no longer exists. Therefore, the operating system cannot be queried about that process anymore.

Why doesn't this happen when calling getHandle() or pid()?

Note that neither of these is an Optional. This means that the API guarantees that values will be returned in them. In fact, the implementation is such that the handle is created - including the process ID - as soon as the process is created by the operating system and returned in pb.start(). So you have a handle and you have a process ID - but the process with that ID is not guaranteed to be alive when you use it.

The documentation specifically tells you not to make assumptions on the liveness or identity of the underlying process.

The parent() call is currently implemented by querying the operating system using the child's pid. So the parent is not part of the process record when the process is created, and by the time you call it, may no longer be available.

How to avoid this situation

Whenever you use an Optional, do not use get. Especially not without checking if it's present or not beforehand (and using isPresent ... get is considered an "antipattern"). When you see an API gives you an Optional, think what you want to do if that Optional happens to be empty. Don't make assumptions that are not justified by the documentation.

In this case you may want to display a default message in case the parent is not found. For example:

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

Or you could use the current process's pid as the default, or whatever else that you can come up with. Or you may throw a different kind of exception if the parent's identity is absolutely necessary for your work. See the documentation for Optional for various ways of elegantly working with it.

There is no way to extend the underlying process - you could use a shell script that sleeps after it performs the command you have passed, but I find this solution to be clunky at best.

RealSkeptic
  • 33,993
  • 7
  • 53
  • 79