Runtime.getRuntime().exec(cmd) hanging
Asked Answered
I

3

18

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();  
 }
Indetermination answered 22/10, 2012 at 9:34 Comment(4)
Take a look at ProcessBuilder. This is a simpler API for doing this sort of thingLyon
If there is no output, readLine will block forever.Banka
@assylias: how do i check if there is no output?Indetermination
Read the article linked in the tag Wiki for exec & follow the recommendations. Also act on the advice of @MyNameIsTooCommon & use ProcessBuilder.Twine
M
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();
}
Mcknight answered 22/10, 2012 at 9:52 Comment(3)
Yep that block of code also save me from being ignorant hehe :)Caveator
+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: learn.microsoft.com/en-us/windows-server/administration/…Betroth
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.Marlee
C
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.

Cardamom answered 8/8, 2014 at 12:15 Comment(3)
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?Jacintha
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.Cardamom
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 waitingBawdry
A
0

As stated in other answers, the IO buffers can fill up without proper handling and in that case the exec will hang as it can no longer write to the buffers.

If it is only required to print the output of the process, this can also be achieved by inheriting the IO of the JVM process using a ProcessBuilder.

ProcessBuilder processBuilder = new ProcessBuilder(cmd.split(" "));

processBuilder.inheritIO();
processBuilder.start();

Here it is assumed that the cmd string does not have consequent whitespace characters.

Apivorous answered 26/10, 2023 at 8:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.