I have a Java program which launches a separate subprocess represented by the Process class, and then attaches listeners which look at the stdout/stderr of the Process. In some cases, the Process will hang and stop being able to make progress, at which time the TimeLimiter will throw a TimeoutException, attempt to interrupt the underlying thread which is actually doing the readLine()
call, and then kill the Process using kill -9
and close the stdout and stderr streams from the Process object. The last thing it tries to do is close the BufferedReader, but this call hangs forever. Sample code below:
private static final TimeLimiter timeLimiter = new SimpleTimeLimiter(); // has its own thread pool
public void readStdout(Process process) {
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
try {
String line = null;
while ((line = timeLimiter.callWithTimeout(reader::readLine, 5, TimeUnit.SECONDS, true)) != null) { // this will throw a TimeoutException when the process hangs
System.out.println(line);
}
} finally {
killProcess(process); // this does a "kill -9" on the process
process.getInputStream().close(); // this works fine
process.getErrorStream().close(); // this works fine
reader.close(); // THIS HANGS FOREVER
}
}
Why does the close()
call hang forever, and what can I do about it?
Related question: Program freezes on bufferedreader close
UPDATE:
In case it wasn't clear, TimeLimiter is from the Guava libraries: https://github.com/google/guava/blob/master/guava/src/com/google/common/util/concurrent/SimpleTimeLimiter.java
Also, I've been asked for the code from the killProcess()
method, so here it is (note this only works on Linux/Unix machines):
public void killProcess(Process process) {
// get the process ID (pid)
Field field = process.getClass().getDeclaredField("pid"); // assumes this is a java.lang.UNIXProcess
field.setAccessible(true);
int pid = (Integer)field.get(process);
// populate the list of child processes
List<Integer> processes = new ArrayList<>(Arrays.asList(pid));
for (int i = 0; i < processes.size(); ++i) {
Process findChildren = Runtime.getRuntime().exec(new String[] { "ps", "-o", "pid", "--no-headers", "--ppid", Integer.toString(processes.get(i)) });
findChildren.waitFor(); // this will return a non-zero exit code when no child processes are found
Scanner in = new Scanner(findChildren.getInputStream());
while (in.hasNext()) {
processes.add(in.nextInt());
}
in.close();
}
// kill all the processes, starting with the children, up to the main process
for (int i = processes.size() - 1; i >= 0; --i) {
Process killProcess = Runtime.getRuntime().exec(new String[] { "kill", "-9", Integer.toString(processes.get(i)) });
killProcess.waitFor();
}
}