Posts Tagged Process

Proper handling of the ProcessBuilder

This post is outdated! A newer Version can bei found here: How to handle the output of the ProcessBuilder!

This article describes the proper handling of the streams which are provided by the ProccessBuilder. If you are interested in the common usage and the difference between Runtime and Processbuilder, refere from Runtime.exec to ProcessBuilder.

I had good experiences with the ProcessBuilder to call native commands. That’s why I would like to use it for another project. But testing it in the new project environment, it often hung.

But why and what’s the difference to the other project? It was the quantity of output.

In the JDK documentation of the ProcessBuilder I’ve found right hint:

The parent process uses these streams (#getInputStream(), #getErrorStream()) 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.

That means: The streams must be read!!! Depending on the native platform, the quantity of output (info or error, what ever) can cause a buffer overflow and therefore an hanging Process.

The code above shows is a wrapper to the ProcessBuilder which boozes the streams:

/**
 * LGPL
 */
public class ProcessBuilderWrapper {
	private StringWriter infos;
	private StringWriter errors;
	private int status;

	public ProcessBuilderWrapper(File directory, List command) throws Exception {
		infos = new StringWriter();
		errors = new StringWriter();
		ProcessBuilder pb = new ProcessBuilder(command);
		if(directory != null)
			pb.directory(directory);
		Process process = pb.start();
		StreamBoozer seInfo = new StreamBoozer(process.getInputStream(), new PrintWriter(infos, true));
		StreamBoozer seError = new StreamBoozer(process.getErrorStream(), new PrintWriter(errors, true));
		seInfo.start();
		seError.start();
		status = process.waitFor();
		seInfo.join();
		seError.join();
	}

	public ProcessBuilderWrapper(List command) throws Exception {
		this(null, command);
	}

	public String getErrors() {
		return errors.toString();
	}

	public String getInfos() {
		return infos.toString();
	}

	public int getStatus() {
		return status;
	}

	class StreamBoozer extends Thread {
		private InputStream in;
		private PrintWriter pw;

		StreamBoozer(InputStream in, PrintWriter pw) {
			this.in = in;
			this.pw = pw;
		}

		@Override
		public void run() {
			BufferedReader br = null;
			try {
				br = new BufferedReader(new InputStreamReader(in));
				String line = null;
	            while ( (line = br.readLine()) != null) {
	            	pw.println(line);
	            }
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

Example:

List cmd = new ArrayList();
cmd.add("ls");
cmd.add("-al");
ProcessBuilderWrapper pbd = new ProcessBuilderWrapper(new File("/tmp"), cmd);
System.out.println("Command has terminated with status: " + pbd.getStatus());
System.out.println("Output:\n" + pbd.getInfos());
System.out.println("Error: " + pbd.getErrors());

, ,

18 Comments