2

While trying the new ProcessHandle API in Java 9 on a simple "Dockerized" Java program, I found a difference in terms of behavior when it comes to retrieving process IDs of running processes. Specifically when calling the method ProcessHandle.pid(), the resulting PID on Docker is different than the native ID shown on the host, although the docs say that the method "returns the native process ID of the process". In addition, there was a difference between the results of ProcessHandle.allProcesses().

To demonstrate, the below program does the following:

  1. prints the PID of the current process,
  2. spawns a child process that sleeps for a few seconds (to allow time to print its info),
  3. and finally prints all visible processes.

public static void main(String[] args) {
    System.out.println("### Current process info ###");
    ProcessHandle currentProcess = ProcessHandle.current();
    printInfo(currentProcess);

    System.out.println();

    // Fork a child process that lasts for a few seconds
    spawnProcess("jshell --startup ./sleep.txt");

    printAllVisibleProcesses();
}

private static void printAllVisibleProcesses() {
    System.out.println("### Visible processes info ###");
    ProcessHandle.allProcesses().forEach(ProcessHandleExamples::printInfo);
    System.out.println();
}

private static void spawnProcess(String command) {
    System.out.println("Spawning: " + command);
    try {
        Runtime.getRuntime().exec(command);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static void printInfo(ProcessHandle processHandle) {
    ProcessHandle.Info processInfo = processHandle.info();
    System.out.println("Process ID: " + processHandle.pid());

    System.out.println("Process arguments: " + Arrays.toString(processInfo.arguments().orElse(new String[0])));
    System.out.println("Process executable: " + processInfo.command().orElse(""));
    System.out.println("Process command line: " + processInfo.commandLine().orElse(""));
    System.out.println("Process start time: " + processInfo.startInstant().orElse(null));
    System.out.println("Process total cputime accumulated: " + processInfo.totalCpuDuration().orElse(null));
    System.out.println("Process user: " + processInfo.user().orElse(""));
}

When running the application normally (without Docker), the output is as expected; it includes the native PID of the current process, its child process and lots of other visible processes.

### Current process info ###
Process ID: 7756
Process arguments: []
Process executable: D:\Dev\Java\jdk-9\bin\java.exe
Process command line: 
Process start time: 2017-10-08T12:23:46.474Z
Process total cputime accumulated: PT0.4368028S
Process user: manouti

Spawning: jshell --startup ./sleep.txt
### Visible processes info ###
... skipping some output
Process ID: 8060
Process arguments: []
Process executable: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
Process command line: 
Process start time: 2017-10-08T12:20:04.758Z
Process total cputime accumulated: PT10.4676671S
Process user: manouti

Process ID: 7756
Process arguments: []
Process executable: D:\Dev\Java\jdk-9\bin\java.exe
Process command line: 
Process start time: 2017-10-08T12:23:46.474Z
Process total cputime accumulated: PT0.8268053S
Process user: manouti

Process ID: 8080
Process arguments: []
Process executable: D:\Dev\Java\jdk-9\bin\jshell.exe
Process command line: 
Process start time: 2017-10-08T12:23:46.992Z
Process total cputime accumulated: PT0.0780005S
Process user: manouti

When I run on Docker (Windows 7 with Docker running on a boot2docker Linux), a much smaller subset of processes are visible, and the PIDs do not match the ones on the host.

$ docker run test/java9-processhandle-example:1.0

After running the above command, the host shows the following processes:

enter image description here

However, the resulting program output below shows PIDs 1 and 16, instead of 4291 and 4333. And the visible processes include the container process and the spawned process.

I'm wondering if this is expected. Since I am relatively new to Docker, if this is a limitation caused by the container, I would be glad if someone could explain it (I'm also not sure if this is reproducible on a different Docker setup, e.g. Docker on Linux or Windows Server). Otherwise, is this a limitation by the API itself when applied in containers (which does not seem to be mentioned anywere in the Javadocs)?

### Current process info ###
Process ID: 1
Process arguments: [ProcessHandleExamples]
Process executable: /usr/lib/jvm/java-9-openjdk-amd64/bin/java
Process command line: /usr/lib/jvm/java-9-openjdk-amd64/bin/java ProcessHandleExamples
Process start time: 2017-10-08T14:17:48.420Z
Process total cputime accumulated: PT0.35S
Process user: root

Spawning: jshell --startup ./sleep.txt
### Visible processes info ###
Process ID: 1
Process arguments: [ProcessHandleExamples]
Process executable: /usr/lib/jvm/java-9-openjdk-amd64/bin/java
Process command line: /usr/lib/jvm/java-9-openjdk-amd64/bin/java ProcessHandleExamples
Process start time: 2017-10-08T14:17:48.420Z
Process total cputime accumulated: PT0.6S
Process user: root

Process ID: 16
Process arguments: [--startup, ./sleep.txt]
Process executable: /usr/lib/jvm/java-9-openjdk-amd64/bin/jshell
Process command line: /usr/lib/jvm/java-9-openjdk-amd64/bin/jshell --startup ./sleep.txt
Process start time: 2017-10-08T14:17:49.070Z
Process total cputime accumulated: PT0.03S
Process user: root
M A
  • 71,713
  • 13
  • 134
  • 174
  • 4
    I would almost expect this. The container shouldn't have any knowledge of the host's PID sequence. – Makoto Oct 08 '17 at 15:36
  • 2
    Possible duplicate of [PID mapping between docker and host](https://stackoverflow.com/questions/33328841/pid-mapping-between-docker-and-host).. This doesn't have much to do with Java or its version IMO. You can follow the links by VonC in the answer to realize the details. – Naman Oct 08 '17 at 16:06
  • Moving here and just to clarify further, is your question how to use ProcessHandler in Docker(which I doubt by the content) OR getting different PIDs in docker and host(in which case I believe its a duplicate) ? – Naman Oct 08 '17 at 18:15

2 Answers2

7

This is nothing specific to Java or Java 9, it's a docker theme.

Each container has its own PID namespace and the first process that is run in the container has a PID of 1.

You can read more about this in the docker documentation, especially:

By default, all containers have the PID namespace enabled.

PID namespace provides separation of processes. The PID Namespace removes the view of the system processes, and allows process ids to be reused including pid 1.

TT.
  • 15,774
  • 6
  • 47
  • 88
P.J.Meisch
  • 18,013
  • 6
  • 50
  • 66
  • It's not even specific to docker, anything that uses the mentioned pid namespaces will experience this. You could do this at the lowest level via `unshare`, independent from all the other namespacing axes – the8472 Oct 10 '17 at 20:52
0

So running a container with --pid=host is needed to allow the ProcessHandle.pid() to return the expected value as implied in the documentation (native PID assigned by operating system).

It also makes the ProcessHandle.allProcesses() return system visible processes, as opposed to just processes bound to the container.

M A
  • 71,713
  • 13
  • 134
  • 174
  • _native_ pid in Java is what you normally see with a `ps` command. This is in difference to process ids which for example `jps` shows. These are Java internal process ids. _native_ within Docker containers are always namespaced ids, That's the sense of putting stuff in containers not to expose host data (and data from other containers) – P.J.Meisch Oct 09 '17 at 06:54
  • @P.J.Meisch Makes sense, although the documentation says "the native process ID is an identification number that the operating system assigns to the process". Maybe the docs can take this difference into account in the future. – M A Oct 09 '17 at 07:08
  • so the question is, what is the operating system in a containerized context? I understand that it's the container and so you get the containered ids. – P.J.Meisch Oct 09 '17 at 08:00
  • @P.J.Meisch I find it a little difficult for me to associate the concept of an operating system with a container (especially that a container is usually lightweight and differentiated from a VM by _not_ implementing an OS). Maybe the operating system could be thought of as the Docker engine itself. But again, the behavior is much more clearer now. I'm just being picky about the documentation :) – M A Oct 09 '17 at 08:06
  • 1
    a container is running in your hosts, but it has its own IPs, filesystem, pids etc. Take for example a container based on ubuntu running in a docker host on Mac OSX. The processor is the same, but the files that the container process sees are ubuntu files and libraries, and so for the container it is running in an ubuntu os. So what makes up the os? basically the set of files, libraries etc. And these are different in different containers. You'll get used to that after working with docker for a while. – P.J.Meisch Oct 09 '17 at 08:43