Issue
How can I obtain a process' output while setting a timeout value?
I am currently using apache commons io utils to create a String from the process' standard and error outputs.
The code below, as is (with the comments), works fine for processes that terminate. However, if the process doesn't terminate, the main thread doesn't terminate either!
If I uncomment out the commented code and instead comment out process.waitfor(), the method will properly destroy non terminating processes. However, for terminating processes, the output isn't properly obtained. It appears that once waitfor() is completed, I cannot get the process' input and error streams?
Finally, if I attempt to move the commented section to where process.waitfor() currently is, remove process.waitfor() and uncomment the commented section, then for non terminating processes, the main thread also won't stop. This is because the process.waitfor(15, ...) will never be reached.
private static Outputs runProcess(String command) throws Exception {
Process process = Runtime.getRuntime().exec(command);
// if (!process.waitFor(15, TimeUnit.SECONDS)) {
// System.out.println("Destroy");
// process.destroy();
// }
// Run and collect the results from the standard output and error output
String stdStr = IOUtils.toString(process.getInputStream());
String errStr = IOUtils.toString(process.getErrorStream());
process.waitFor();
return new Outputs(stdStr, errStr);
}
Solution
As @EJP suggested, You can use different threads to capture the streams or use ProcessBuilder
or redirect to a file from your command.
Here are 3 approaches that I feel you can use.
Using different threads for Streams.
Process process = Runtime.getRuntime().exec("cat "); ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2); Future<String> output = newFixedThreadPool.submit(() -> { return IOUtils.toString(process.getInputStream()); }); Future<String> error = newFixedThreadPool.submit(() -> { return IOUtils.toString(process.getErrorStream()); }); newFixedThreadPool.shutdown(); // process.waitFor(); if (!process.waitFor(3, TimeUnit.SECONDS)) { System.out.println("Destroy"); process.destroy(); } System.out.println(output.get()); System.out.println(error.get());
Using
ProcessBuilder
ProcessBuilder processBuilder = new ProcessBuilder("cat") .redirectError(new File("error")) .redirectOutput(new File("output")); Process process = processBuilder.start(); // process.waitFor(); if (!process.waitFor(3, TimeUnit.SECONDS)) { System.out.println("Destroy"); process.destroy(); } System.out.println(FileUtils.readFileToString(new File("output"))); System.out.println(FileUtils.readFileToString(new File("error")));
Use a redirection operator in your command to redirect Output & Error to a file and then Read from File.
Here is very good blog which explains different ways of handling Runtime.Exec
Answered By - Kishore Bandi
Answer Checked By - Candace Johnson (JavaFixing Volunteer)