10

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.

aschipfl
  • 33,626
  • 12
  • 54
  • 99
Ganesh S
  • 307
  • 2
  • 3
  • 9
  • This has nothing to do with batch-file. It's a Java question (and a very basic programming question at that). –  May 05 '16 at 05:48

2 Answers2

22

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;
} 
dimo414
  • 47,227
  • 18
  • 148
  • 244
  • This works for java 8 but what about java 7 ? THere is no method waitFor with timeout parameter. – Ganesh S May 10 '16 at 08:36
  • @GaneshS you'll need to replicate the timeout behavior with a polling loop using `Thread.sleep()` and `Process.isAlive()`. – dimo414 May 10 '16 at 12:31
  • 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 May 11 '16 at 06:34
  • There is no Process.isAlive() for java 7. So can not go with Thread.sleep() and Process.isALive() combination. – Ganesh S May 11 '16 at 06:37
  • @GaneshS apologies, I was commenting from my phone. You can replicate `isAlive()` with [`exitValue()`](https://docs.oracle.com/javase/7/docs/api/java/lang/Process.html#exitValue()) (`IllegalThreadStateException` means still-alive). – dimo414 May 11 '16 at 07:21
  • You certainly can use the [`timeout`](http://linux.die.net/man/1/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`. – dimo414 May 11 '16 at 07:25
  • Is there a way to combine this solution with getting the process output as a string? Normally you can get the process output like this: `new String(new ProcessBuilder("mvn.cmd").start().getInputStream().readAllBytes())`. But I think `readAllBytes` will block until the process exits, at which point it will be to late to use `waitFor`. – Lii Aug 25 '22 at 09:21
  • @Lii you'll likely want to read the output in a separate thread. – dimo414 Aug 25 '22 at 23:53
1

The method provided by Process and documented in the JavaDoc is Process#destroyForcibly(). However, it is not always possible to forcibly destroy a process and whether the process actually gets terminated is highly dependent on the operating system and JRE implementation.

Refer to the JavaDoc for more details.

Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
  • Thank You, I think this will help. – Ganesh S May 05 '16 at 06:29
  • 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. – dimo414 May 06 '16 at 05:50