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

12,185

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;
} 
Share:
12,185
Ganesh S
Author by

Ganesh S

Updated on June 14, 2022

Comments

  • Ganesh S
    Ganesh S almost 2 years

    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.

    • Admin
      Admin about 8 years
      This has nothing to do with batch-file. It's a Java question (and a very basic programming question at that).
  • Ganesh S
    Ganesh S about 8 years
    Thank You, I think this will help.
  • Ar5hv1r
    Ar5hv1r about 8 years
    There's rarely a need to call .destroyForcibly(); under the covers (on Linux) it sends a SIGKILL while .destroy() sends the much safer SIGTERM. You should only use .destroyForcibly() if you've previously tried to .destroy() the process and, after a grace period, the process continues running.
  • Ganesh S
    Ganesh S about 8 years
    This works for java 8 but what about java 7 ? THere is no method waitFor with timeout parameter.
  • Ar5hv1r
    Ar5hv1r about 8 years
    @GaneshS you'll need to replicate the timeout behavior with a polling loop using Thread.sleep() and Process.isAlive().
  • Ganesh S
    Ganesh S about 8 years
    I have came up with better solution to this, I have added "timeout -s SIGKILL -k 20s 10s" as prefix to command which I am executing through batch file. And it is working. It works with both Java 7 as well as Java 8.
  • Ganesh S
    Ganesh S about 8 years
    There is no Process.isAlive() for java 7. So can not go with Thread.sleep() and Process.isALive() combination.
  • Ar5hv1r
    Ar5hv1r about 8 years
    @GaneshS apologies, I was commenting from my phone. You can replicate isAlive() with exitValue() (IllegalThreadStateException means still-alive).
  • Ar5hv1r
    Ar5hv1r about 8 years
    You certainly can use the timeout command if that works for your use-case, though it tightly-couples you to Linux. There are a number of ways the subprocess could be modified to run for no more than n seconds. Note that, as I mention in my answer, you should avoid SIGKILL; use SIGTERM or SIGINT instead. Particularly since you also specify -k there's no reason to initially send a SIGKILL.