java: closing subprocess std streams?

2019-07-07 07:59发布

问题:

from the javadoc for java.lang.Process:

The methods that create processes may not work well for special processes on certain native platforms, such as native windowing processes, daemon processes, Win16/DOS processes on Microsoft Windows, or shell scripts. The created subprocess does not have its own terminal or console. All its standard io (i.e. stdin, stdout, stderr) operations will be redirected to the parent process through three streams (getOutputStream(), getInputStream(), getErrorStream()). The parent process uses these streams to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.

I know about this issue, and of course it makes sense to communicate in a timely fashion with a subprocess if you are wanting to communicate with it.

But what if you just want to launch a process and don't care about the I/O? In Java is there a way to release the resources in the parent process that are dedicated towards managing the subprocess? (e.g. I/O pipes and waiting for the subprocess exit status)

If I execute the following in the parent process:

  Process.getOutputStream().close();
  Process.getInputStream().close();
  Process.getErrorStream().close();

do I still have to worry about deadlock? (e.g. if the subprocess continuously sends out data to its own stdout)

回答1:

You have two things to worry about here:

  1. Will the process block, trying to write to stdout as you fail to consume it, and
  2. How will the process interpret the streams being closed?

Closing the output stream clearly won't cause a deadlock unless the process requires input. However, the process will detect that the end of stream has been reached, and may terminate accordingly. Take grep for example. This program will terminate when you close its input stream.

The easiest and safest way to ignore the input would be to create a library that consumes and discards the input on a separate thread. You've probably seen the class StreamGobbler floating around which does just this.

Edit

So I tried the following with two different subprocess programs:

Process proc = new ProcessBuilder(appStr).start();
proc.getInputStream().close();

InputStream eos = proc.getErrorStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(eos));
for ( String line = null; (line = reader.readLine()) != null; ) {
   System.err.println(line);
}

System.out.println(proc.waitFor());

The first time, I invoked a simple Java app:

public class App {
   public static void main(String[] args) throws Exception {
      for ( int i = 0; i < 1000; i++ ) {
         System.out.println(i);
      }
   }
}

The process ended normally and the parent process terminated displaying a 0 exit code.

But then I tried invoking it against a native program, in this case a cat variation, echoing a file. In this case, the child process failed and wrote a garbled message to the error stream complaining that the standard output stream had been closed. It terminated abnormally with a non-zero exit code.

So really what you're suggesting in closing the streams will not be successful in the general case. You must know that the child process can gracefully handle losing its output stream, and I suspect most applications won't. My intuition is that Java can do some graceful failing when the output stream is closed, such that from the program's point of view the stream is still open with nobody listening.