48

At the moment I execute a native process using the following:

java.lang.Process process = Runtime.getRuntime().exec(command); 
int returnCode = process.waitFor();

Suppose instead of waiting for the program to return I wish to terminate if a certain amount of time has elapsed. How do I do this?

informatik01
  • 16,038
  • 10
  • 74
  • 104
deltanovember
  • 42,611
  • 64
  • 162
  • 244

6 Answers6

57

All other responses are correct but it can be made more robust and efficient using FutureTask.

For example,

private static final ExecutorService THREAD_POOL 
    = Executors.newCachedThreadPool();

private static <T> T timedCall(Callable<T> c, long timeout, TimeUnit timeUnit)
    throws InterruptedException, ExecutionException, TimeoutException
{
    FutureTask<T> task = new FutureTask<T>(c);
    THREAD_POOL.execute(task);
    return task.get(timeout, timeUnit);
}

final java.lang.Process[] process = new Process[1];
try {
    int returnCode = timedCall(new Callable<Integer>() {
        public Integer call() throws Exception {
            process[0] = Runtime.getRuntime().exec(command); 
            return process[0].waitFor();
        }
    }, timeout, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    process[0].destroy();
    // Handle timeout here
}

If you do this repeatedly, the thread pool is more efficient because it caches the threads.

dlauzon
  • 1,241
  • 16
  • 23
ZZ Coder
  • 74,484
  • 29
  • 137
  • 169
  • The handle timeout here could be a little more robust for an example. I have a couple of mechanisms I use, but for the simplest case, use something like: `catch (TimeoutException e) { System.exit(-1);}` – John Yeary Nov 10 '12 at 15:49
  • Type argument cannot be of primitive type. Please replace `int` with `Integer`. – naXa stands with Ukraine Sep 22 '14 at 15:15
  • Although the TimeoutException will be called after the specified timeout, the command itself will continue to execute in the background on the other thread unless we call `destroy()` when `TimeoutException` is thrown. (the same is seen even after a `shutdownNow()` call on the executor) – dlauzon Jan 24 '20 at 19:22
  • `THREAD_POOL` should be [shut down](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ExecutorService.html#shutdown()) when the application is stopped, otherwise it might prevent the JVM from terminating for up to 60 seconds (the maximum idle time for worker threads of `newCachedThreadPool()`) because non-daemon worker threads are still alive. – Marcono1234 Dec 11 '20 at 19:11
25

If you're using Java 8 or later (API 26 or later for Android) you could simply use the waitFor with timeout:

Process p = ...
if(!p.waitFor(1, TimeUnit.MINUTE)) {
    //timeout - kill the process. 
    p.destroy(); // consider using destroyForcibly instead
}
Aleksander Blomskøld
  • 18,374
  • 9
  • 76
  • 82
  • In Android that is only available in `API 26` and above though. – Top-Master Aug 13 '20 at 17:59
  • @Bogdan, if (and only if) `waitFor(...)` returned true, you can call [`p.exitValue()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html#exitValue--) to get the exit code. – Marcono1234 Dec 11 '20 at 19:10
19

This is how the Plexus CommandlineUtils does it:

Process p;

p = cl.execute();

...

if ( timeoutInSeconds <= 0 )
{
    returnValue = p.waitFor();
}
else
{
    long now = System.currentTimeMillis();
    long timeoutInMillis = 1000L * timeoutInSeconds;
    long finish = now + timeoutInMillis;
    while ( isAlive( p ) && ( System.currentTimeMillis() < finish ) )
    {
        Thread.sleep( 10 );
    }
    if ( isAlive( p ) )
    {
        throw new InterruptedException( "Process timeout out after " + timeoutInSeconds + " seconds" );
    }
    returnValue = p.exitValue();
}

public static boolean isAlive( Process p ) {
    try
    {
        p.exitValue();
        return false;
    } catch (IllegalThreadStateException e) {
        return true;
    }
}
Rich Seller
  • 83,208
  • 23
  • 172
  • 177
  • 8
    Ewwww...so every 10 milliseconds it relies on p.exitValue() throwing IllegalThreadStateException to indicate "still running"? – Ogre Psalm33 Apr 09 '12 at 22:02
  • 1
    @OgrePsalm33 It's awful, but sadly Java gives no nicer way up to Java 7. Java8 gives a "p.isAlive()" – leonbloy May 16 '14 at 18:17
  • 2
    Why the loop? Why not to run a TimerTask which will check "aliveness" only once upon timeout expiration? – Stan Jun 30 '15 at 22:21
6

What about the Groovy way

public void yourMethod() {
    ...
    Process process = new ProcessBuilder(...).start(); 
    //wait 5 secs or kill the process
    waitForOrKill(process, TimeUnit.SECONDS.toMillis(5));
    ...
}

public static void waitForOrKill(Process self, long numberOfMillis) {
    ProcessRunner runnable = new ProcessRunner(self);
    Thread thread = new Thread(runnable);
    thread.start();
    runnable.waitForOrKill(numberOfMillis);
}

protected static class ProcessRunner implements Runnable {
    Process process;
    private boolean finished;

    public ProcessRunner(Process process) {
        this.process = process;
    }

    public void run() {
        try {
            process.waitFor();
        } catch (InterruptedException e) {
            // Ignore
        }
        synchronized (this) {
            notifyAll();
            finished = true;
        }
    }

    public synchronized void waitForOrKill(long millis) {
        if (!finished) {
            try {
                wait(millis);
            } catch (InterruptedException e) {
                // Ignore
            }
            if (!finished) {
                process.destroy();
            }
        }
    }
}
mickthompson
  • 5,442
  • 11
  • 47
  • 59
4

just modified a bit according to my requirement. time out is 10 seconds here. process is getting destroyed after 10 seconds if it is not exiting.

public static void main(String arg[]) {

    try {
        Process p = Runtime.getRuntime().exec("\"C:/Program Files/VanDyke Software/SecureCRT/SecureCRT.exe\"");
        long now = System.currentTimeMillis(); 
        long timeoutInMillis = 1000L * 10; 
        long finish = now + timeoutInMillis; 
        while ( isAlive( p ) ) { 
            Thread.sleep( 10 ); 
            if ( System.currentTimeMillis() > finish ) {
                p.destroy();
            }
        }
    } catch (Exception err) {
        err.printStackTrace();
    }
}

public static boolean isAlive( Process p ) {  
    try {  
        p.exitValue();  
        return false;  
    } catch (IllegalThreadStateException e) {  
        return true;  
    }  
}  
svarog
  • 9,477
  • 4
  • 61
  • 77
nuwan
  • 41
  • 2
2

You'd need a 2. thread that interrupts the thread that calls .waitFor(); Some non trivial synchronization will be needed to make it robust, but the basics are:

TimeoutThread:

 Thread.sleep(timeout);
 processThread.interrupt();

ProcessThread:

  try {
      proc.waitFor(); 
    } catch (InterruptedException e) {
       proc.destroy();
    }
nos
  • 223,662
  • 58
  • 417
  • 506