16

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?

5 Answers 5

11

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.

Sign up to request clarification or add additional context in comments.

3 Comments

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.
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).
When the process has finished then you will get null back from the readLine() call (just confirmed). It'll only hang if the process doesn't exit.
8

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.

1 Comment

I fought this because I wanted to have a "pure" program that only used the core JDK. But you're right, this ways is much better and solves a lot of problems I didn't know I was signing up for when I tried to create my own.
5

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.

5 Comments

The "finished" variable should at least be declared as "volatile".
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.
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().
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.
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).
2

How about this (see how it works in jcabi-heroku-maven-plugin):

/**
 * Wait for the process to stop, logging its output in parallel.
 * @param process The process to wait for
 * @return Stdout produced by the process
 * @throws InterruptedException If interrupted in between
 */
private String waitFor(final Process process) throws InterruptedException {
    final BufferedReader reader = new BufferedReader(
        new InputStreamReader(process.getInputStream())
    );
    final CountDownLatch done = new CountDownLatch(1);
    final StringBuffer stdout = new StringBuffer();
    new Thread(
        new VerboseRunnable(
            new Callable<Void>() {
                @Override
                public Void call() throws Exception {
                    while (true) {
                        final String line = reader.readLine();
                        if (line == null) {
                            break;
                        }
                        System.out.println(">> " + line);
                        stdout.append(line);
                    }
                    done.countDown();
                    return null;
                }
            },
            false
        )
    ).start();
    try {
        process.waitFor();
    } finally {
        done.await();
        IOUtils.closeQuietly(reader);
    }
    return stdout.toString();
}

ps. Now this implementation is available as com.jcabi.log.VerboseProcess class from jcabi-log artifact.

Comments

0

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

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.