Java pitfall: How to prevent Runtime.getRuntime().exec() from hanging

Runtime.getRuntime().exec() is used to execute a command line program from within the Java program as below.

[java]
import java.io.File;
import java.io.IOException;

public class ProcessExecutor {

public static void main(String[] args) throws IOException, InterruptedException {

String command = "c:my.exe";
String workingDir = "c:myworkingdir";

// start execution
Process process = Runtime.getRuntime().exec(command, null, new File(workingDir));

// wait for completion
process.waitFor();

}

}
[/java]

However the command line program being run above may block/deadlock as it did for me on Windows 7. I was trying to run a program that produced a lot of output. I could run the program standalone but through Java it hung indefinitely. Thread dumps showed nothing.

After being quite puzzled for a while as to why this was happening finally I found the answer in Java 7 api docs for Process.

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, or even deadlock.

So, in fact, the fix for the above program is as follows.

[java]
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;

public class ProcessExecutor {

public static void main(String[] args) throws IOException, InterruptedException {

String command = "c:my.exe";
String workingDir = "c:myworkingdir";

// start execution
Process process = Runtime.getRuntime().exec(command, null, new File(workingDir));

// exhaust input stream
BufferedInputStream in = new BufferedInputStream(process.getInputStream());
byte[] bytes = new byte[4096];
while (in.read(bytes) != -1) {}

// wait for completion
process.waitFor();

}

}
[/java]

This is so bad. Not only is this unexpected but it is also undocumented in the exec call. Also another problem is that if you are timing the total execution time for a given command and don’t care about the output you need to read the output anyway and probably subtract the reading time from the total execution time. I’m not sure how accurate that will be.

Surely there could have been a better way to handle this for the user in the api internals. So windows 7 must be one of those OSs with small buffer sizes then. Anyway, at least you know now. Obviously you don’t have to read it into nothing as I’m doing above. You can write it to stdout or a file.

Update: A commenter made a good point that I’d forgotten to read the error stream above. Don’t forget to do so in your own code!

7 thoughts on “Java pitfall: How to prevent Runtime.getRuntime().exec() from hanging

  1. Exec command like this:

    command > /dev/null 2>&1

    Of course you have to change /dev/null to something meaningful for windows 7

    And by the way, you need to read stderr too. And it’s not the OS’s fault, every OS will say “broken pipe” if the buffer ends – linux too. Try it. Someone (app) have to do something with bytes generated from the command, system is not a miracle maker which makes data just disappear.

  2. Good points. But, firstly, will such unix style redirection work on Windows? Secondly, yes, you’re right although I knew about error stream I forgot to read it in the example above. And, lastly, I’m not necessarily blaming the OS but I do strongly feel that there has to be a way for the Java SDK API should handle this pitfall better. If you’ve called waitFor() without reading the streams then surely it can exhaust the stream on your behalf.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s