How to stop a command being executed after 4-5 seconds through process builder?

Ganesh S picture Ganesh S · May 5, 2016 · Viewed 10.2k times · Source

Reference code :

ProcessBuilder ps4;
Process pr4 = null;

String batchFile3 = new File(path + "/src/example.sh");

ps4 = new ProcessBuilder(batchFile3.getAbsolutePath());

ps4.redirectErrorStream(true);
ps4.directory(new File(path + "/src/"));

pr4 = ps4.start();

BufferedReade readRun = new BufferedReader(new InputStreamReader(pr4.getInputStream()));



if(pr4.waitFor()==0)
{

}

 String line,stre;   

while ((line = readRun.readLine()) != null) {

     System.out.print("-----" + line);

     if (line != null) {

           stre += line;

    }

}
  • Here I have result in stre string it might be error or output generated by batch file which I am executing.

  • I want to stop execution of batch file if it is taking more that 4-5 seconds to exectute and kill that batch file execution process.

  • also in that case I should be able to return back to program to process a block which will execute only if this delay in processing of batch file occurs other wise that block should not be processed.

Answer

dimo414 picture dimo414 · May 6, 2016

As I understand it you want to stop a subprocess if it runs longer than four or five seconds. This cannot be done directly with ProcessBuilder (you can see that no relevant method exists in the class), but you can implement this behavior easily enough once the subprocess has begun.

Calling Process.waitFor() as you do in your sample code is problematic because it will block your current thread indefinitely - if your process takes longer than five seconds .waitFor() will not stop it. However .waitFor() is overloaded and its sibling takes a timeout argument.

public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException

Causes the current thread to wait, if necessary, until the subprocess represented by this Process object has terminated, or the specified waiting time elapses.

You can use this in tandem with Process.destroy() to stop the process if it takes too long. For example:

Process process = new ProcessBuilder(command, and, arguments)
    .redirectErrorStream(true)
    .directory(workingDir)
    .start();

process.waitFor(5, TimeUnit.SECONDS);
process.destroy();
process.waitFor(); // wait for the process to terminate

This relies on the fact that Process.destroy() is a no-op when called on an already-finished subprocess. Before Java 9 this behavior was not documented, but in practice has always been the case. The alternative would be to inspect the return value of .waitFor(), but this would introduce a TOCTTOU race.

What about Process.destroyForcibly()? Generally speaking you should not call this method (another thing the JDK could be clearer about), however if a process is truly hung it may become necessary. Ideally you should ensure your subprocesses are well-behaved, but if you must use .destroyForcibly() this is how I would recommend doing so:

// Option 2
process.waitFor(5, TimeUnit.SECONDS);  // let the process run for 5 seconds
process.destroy();                     // tell the process to stop
process.waitFor(10, TimeUnit.SECONDS); // give it a chance to stop
process.destroyForcibly();             // tell the OS to kill the process
process.waitFor();                     // the process is now dead

This ensures that misbehaving processes will be killed promptly, while still giving properly implemented programs time to exit upon being instructed. The exact behavior of .destroy() and .destroyForcibly() is OS-specific, but on Linux we can see that they correspond to SIGTERM and SIGKILL:

int sig = (force == JNI_TRUE) ? SIGKILL : SIGTERM;
kill(pid, sig);

You should rarely have a need to call .destroyForcibly(), and I would suggest only adding it if you discover it is necessary.

Option 2 is conceptually similar to using the timeout command like so:

$ timeout --kill-after=10 5 your_command

It's easy enough to replicate Process.waitFor(long, TimeUnit) in Java 7, there's nothing magic about the default Java 8 implementation:

public boolean waitFor(long timeout, TimeUnit unit)
    throws InterruptedException
{
    long startTime = System.nanoTime();
    long rem = unit.toNanos(timeout);

    do {
        try {
            exitValue();
            return true;
        } catch(IllegalThreadStateException ex) {
            if (rem > 0)
                Thread.sleep(
                    Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100));
        }
        rem = unit.toNanos(timeout) - (System.nanoTime() - startTime);
    } while (rem > 0);
    return false;
}