Search code examples
javaprocessoutputstreamshutdown-hook

How to correctly read shutdown hook using Process in Java


I am facing an issue with writing a Java code that would launch another Unix process and print its outcome. I have the positive route all done and working, but the issue happens when I terminate the Process, I do not get the output from the shutdown hook. I know for fact that the shutdown hook gets entered into, because of the side effect the hook has, such as creating a proof file.

Example:

class JavaProgram1 {
  public static void main(String[] args) {
    registerShutdownHook(() -> {
      writeTextFile("./shutdown", "ShutdownHook ran");
      System.out.println("Hook shutdown");
    });

    System.out.println("Hello");
    try { Thread.sleep(3600); } catch (InterruptedException e){}
    System.out.println("Bye");
  }
}

the snippet above (with properly implemented methods, of course) works exactly as intended if I run it from cmd line using Java (Or IDEA IDE for that matter).

I am working with Java 11 on RedHat Linux.

The issue begins when I want to launch this program from another Java program similar to this:

class JavaLauncher {
  public static void main(String[] args){
    Process process = startProcessThroughProcessBuilder("JavaProgram1");
    Thread readingThread = readOutput(process);
    process.destroy();
    process.waitFor();
    readingThread.join()
  }

  public void readOutput(Process process){
    BufferedReader processOutput = new BufferedReader(new InputStreamReader(process.getInputStream()));
    Thread readingThread = new Thread(() -> {
      try{
        String line;
        while (line = processOutput.readLine() != null) {
          logger.debug(line)
        }
        logger.info("IO finished");
      } catch (IOException e) {
        logger.warn("IO Interrupted: "+e)
      }
    })
  }
}

I am not seeing the results I expect.

Basically what it boils down to is when I call a Destroy() on the Process, the reading thread gets IOException thrown, but I would expect that since the Process terminates through shutdown hook and is not killed, it will close its streams, signalling EOF, and the reading thread should cleanly exit the while loop.

Instead, I get a IOException thrown with "Stream closed" message.

The reading thread will not even print the "Hook Shutdown" message, indicating that the streams close before I attempt to write that message in the Hook.

If I modify the code in a way that the shutdown hook does not contain any output, the IOException is NOT thrown, which leads me to the thought that the streams are getting closed prematurely.

Can you give me any pointers as to what might be happening and how do I solve the issue?


Solution

  • There is more to the Java class Process than the actual process itself. The Java class maintains a number of IO streams as fields. If you single-step through the Process.destroy() method, you'll see that it calls a private destroy() function that has these statements after killing the process:

                try { stdin.close();  } catch (IOException ignored) {}
                try { stdout.close(); } catch (IOException ignored) {}
                try { stderr.close(); } catch (IOException ignored) {}
    

    So, once you've called destroy(), even if the process itself outputs something before it dies, don't expect to be able to read from its input stream, because destroy() closes all streams before it returns.

    (Although you've created a reading thread before destroying the process, that's in a separate thread. By the time the thread is started, you may have already destroyed the process and closed its streams.)

    See comments for proposed solutions; in particular Holger's suggestion sounds good: use process.toHandle().destroy(). That will destroy the actual process but not the Java Process instance. Therefore, make sure you destroy that too (to release resources) once you've processed it.