views:

2673

answers:

4

I want to launch a process from Java, read its output, and get its return code. But while it's executing, I want to be able to cancel it. I start out by launching the process:

ProcessBuilder pb = new ProcessBuilder(args);
pb.redirectErrorStream(true);
Process proc = pb.start();

If I call proc.waitFor(), I can't do anything until the process exits. So I'm assuming I need to something like this:

while (true) {
  see if process has exited
  capture some output from the process
  decide if I want to cancel it, and if so, cancel it
  sleep for a while
}

Is this right? Can someone give me an example of how to do this in Java?

+2  A: 

Here's an example of what I think you want to do:

ProcessBuilder pb = new ProcessBuilder(args);
pb.redirectErrorStream(true);
Process proc = pb.start();

InputStream is = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);

String line;
int exit = -1;

while ((line = br.readLine()) != null) {
    // Outputs your process execution
    System.out.println(line);
    try {
        exit = proc.exitValue();
        if (exit == 0)  {
            // Process finished
        }
    } catch (IllegalThreadStateException t) {
        // The process has not yet finished. 
        // Should we stop it?
        if (processMustStop())
            // processMustStop can return true 
            // after time out, for example.
            proc.destroy();
    }
}

You can improve it :-) I don't have a real environment to test it now, but you can find some more information here.

Paulo Guedes
What if there's no output for a while? Won't this hang on br.readLine() until there's a line ready to read? I'm not sure how much or little output my process will send.
JW
Go point. You can create a separated thread to analyze if the process must stop. It could run parallel to the while loop and destroy the process if it hangs for some time (or any other condition).
Paulo Guedes
(i meant "good point")
Paulo Guedes
+2  A: 

A helper class like this would do the trick:

public class ProcessWatcher implements Runnable {

    private Process p;
    private volatile boolean finished = false;

    public ProcessWatcher(Process p) {
        this.p = p;
        new Thread(this).start();
    }

    public boolean isFinished() {
        return finished;
    }

    public void run() {
        try {
            p.waitFor();
        } catch (Exception e) {}
        finished = true;
    }

}

You would then implement your loop exactly as you describe:

Process p = Runtime.getRuntime().exec("whatever command");
ProcessWatcher pw = new ProcessWatcher(p);
InputStream output = p.getInputStream();
while(!pw.isFinished()) {
    processOutput(output);
    if(shouldCancel()) p.destroy();
    Thread.sleep(500);
}

Depending upon what conditions would make you want to destroy the process, you might want to do that in a separate thread. Otherwise, you may block while waiting for more program output to process and never really get the option to destroy it.

EDIT: McDowell is 100% right in his comment below, so I've made the finished variable volatile.

Marty Lamb
The "finished" variable should at least be declared as "volatile".
McDowell
That's my concern; what if there's no output for a while? If I call something like BufferedReader.readLine(), I don't want it to hang forever until there's output; I want the chance to cancel.
JW
You could create a new Runnable with: public void run() { while (!pw.isFinished()) { if (shouldCancel()) p.destroy(); Thread.sleep(500); } }then new Thread(myrunnable).start().
Marty Lamb
Or your processOutput() method could check output.available() and only read if it won't block. If you go that route then you can't use readLine() - you would have to handle end-of-line on your own.
Marty Lamb
Or, if the cancellation is upon user request from a GUI, then you can just call process.destroy() from the thread that is processing the user event (such as a button press).
Marty Lamb
A: 

What would make you decide to kill the process -- an asynchronous event (such as input from the user), or a synchronous event (e.g., the process has done what you wanted it to do)? I'm guessing it's the former -- input from the user makes you decide to cancel the subprocess.

Also, how much output do you expect the subprocess to produce? If it's a lot, then the subprocess may block if you don't read from its output stream quickly enough.

Your situation may vary, but it seems that you're likely going to need at least two different threads -- one to decide whether to cancel the process, and one that handles the output from the subprocess.

Have a look here for a bit more detail: http://java.sun.com/developer/JDCTechTips/2005/tt0727.html#2

Dan Breslau
I'm already running in a thread, and someone outside of that thread will set one of my fields when it's time to cancel.
JW
+1  A: 

I recommend checking out Apache Commons Exec to avoid recreating the wheel. It has some nice features like choosing between synchronous vs. asynchronous execution, as well as a standard solution to spawning a watchdog process that can help in timing out the execution in case it gets stuck.

AWhitford