17

I am executing a command which returns me the Revision number of a file; 'fileName'. But if there is some problem executing the command, then the application hangs up. What can I do to avoid that condition? Please find below my code.

String cmd= "cmd /C si viewhistory --fields=revision --project="+fileName; 
Process p = Runtime.getRuntime().exec(cmd) ;  
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));  
String line = null; 
while ((line = in.readLine()) != null) {  
System.out.println(line);  
} 

} catch (Exception e) {  
e.printStackTrace();  
 }
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
user1688404
  • 729
  • 3
  • 9
  • 19
  • 4
    Take a look at [ProcessBuilder](http://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html). This is a simpler API for doing this sort of thing – RNJ Oct 22 '12 at 09:39
  • 1
    If there is no output, `readLine` will block forever. – assylias Oct 22 '12 at 09:41
  • 1
    @assylias: how do i check if there is no output? – user1688404 Oct 22 '12 at 09:50
  • 1
    Read the article linked in the [tag Wiki for `exec`](http://stackoverflow.com/tags/runtime.exec/info) & follow the recommendations. Also act on the advice of @MyNameIsTooCommon & use [`ProcessBuilder`](http://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html). – Andrew Thompson Oct 22 '12 at 09:55

2 Answers2

38

I guess the issue is that you are only reading InputStream and not reading ErrorStream. You also have to take care that both the streams are read in parallel. It may so happen that currently the data piped from the output stream fills up the OS buffer, your exec command will be automatically be suspended to give your reader a chance to empty the buffer. But the program will still be waiting for the output to process. Hence, the hang occurs.

You can create a separate class to handle both the Input and Error Stream as follows,

public class ReadStream implements Runnable {
    String name;
    InputStream is;
    Thread thread;      
    public ReadStream(String name, InputStream is) {
        this.name = name;
        this.is = is;
    }       
    public void start () {
        thread = new Thread (this);
        thread.start ();
    }       
    public void run () {
        try {
            InputStreamReader isr = new InputStreamReader (is);
            BufferedReader br = new BufferedReader (isr);   
            while (true) {
                String s = br.readLine ();
                if (s == null) break;
                System.out.println ("[" + name + "] " + s);
            }
            is.close ();    
        } catch (Exception ex) {
            System.out.println ("Problem reading stream " + name + "... :" + ex);
            ex.printStackTrace ();
        }
    }
}

The way you use it is as follows,

String cmd= "cmd /C si viewhistory --fields=revision --project="+fileName; 
Process p = Runtime.getRuntime().exec(cmd) ;  
s1 = new ReadStream("stdin", p.getInputStream ());
s2 = new ReadStream("stderr", p.getErrorStream ());
s1.start ();
s2.start ();
p.waitFor();        
} catch (Exception e) {  
e.printStackTrace();  
} finally {
    if(p != null)
        p.destroy();
}
Arham
  • 2,072
  • 2
  • 18
  • 30
  • 1
    Yep that block of code also save me from being ignorant hehe :) – Akyo Sep 26 '16 at 08:45
  • +1 for the perfect example, /C is an important argument which we should pass. Without passing this argument the code won't run. URL for cmd arguments: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd – Abhishek K Oct 23 '18 at 04:15
  • I would like to use this, however, I need to return a value from `ReadStream`, rather than just dumping the stream to `System.out`. I know this can be done with `Callable` rather than `Runnable`, but it's a bit more complicated. Is there a practical example I can follow? Preferably following the same sort of structure shown in these examples. – Sturm May 31 '19 at 17:27
11

This code is based on the same idea Arham's answer, but is implemented using a java 8 parallel stream, which makes it a little more concise.

public static String getOutputFromProgram(String program) throws IOException {
    Process proc = Runtime.getRuntime().exec(program);
    return Stream.of(proc.getErrorStream(), proc.getInputStream()).parallel().map((InputStream isForOutput) -> {
        StringBuilder output = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new InputStreamReader(isForOutput))) {
            String line;
            while ((line = br.readLine()) != null) {
                output.append(line);
                output.append("\n");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return output;
    }).collect(Collectors.joining());
}

You can call the method like this

getOutputFromProgram("cmd /C si viewhistory --fields=revision --project="+fileName);

Note that this method will hang if the program you are calling hangs, which will happen if it requires input.

mikeyreilly
  • 6,523
  • 1
  • 26
  • 21
  • Works great running `mvn verify` and I'm not wrapping it with `cmd /C` -- do you know what is the benefit of wrapping a second shell? – John Mar 07 '18 at 18:40
  • 1
    I copied the OP's command string exactly and it started with "cmd /C". I don't think there is any benefit to wrapping it in this case. – mikeyreilly Mar 08 '18 at 16:47
  • This is a fantastic code block and works as is but theres one issue I have noticed. While this works to prevent hanging in a process that used to hang for me, in another process that is very fast and that creates files to be used quickly after by other things, the files are not getting written fast enough before an access is attempted. I assumed a proc.waitfor would fix it but no luck so far, you may want to edit the code block to address this issue that can arise in not having sufficient waiting – chilleo Jun 19 '18 at 20:11