使用Java的Runtime.exec()时如何添加超时值?

问题描述 投票:73回答:17

我有一种正在使用的方法来在本地主机上执行命令。我想向该方法添加一个超时参数,以便如果被调用的命令没有在合理的时间内完成,该方法将返回错误代码。这是到目前为止的样子,无法超时:

public static int executeCommandLine(final String commandLine,
                                     final boolean printOutput,
                                     final boolean printError)
    throws IOException, InterruptedException
{
    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(commandLine);

    if (printOutput)
    {
        BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        System.out.println("Output:  " + outputReader.readLine());
    }

    if (printError)
    {
        BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        System.out.println("Error:  " + errorReader.readLine());
    }

    return process.waitFor();
}

有人可以为我提出一个实现超时参数的好方法吗?

java process timeout runtime exec
17个回答
53
投票
public static int executeCommandLine(final String commandLine,
                                     final boolean printOutput,
                                     final boolean printError,
                                     final long timeout)
      throws IOException, InterruptedException, TimeoutException {
  Runtime runtime = Runtime.getRuntime();
  Process process = runtime.exec(commandLine);
  /* Set up process I/O. */
  ... 
  Worker worker = new Worker(process);
  worker.start();
  try {
    worker.join(timeout);
    if (worker.exit != null)
      return worker.exit;
    else
      throw new TimeoutException();
  } catch(InterruptedException ex) {
    worker.interrupt();
    Thread.currentThread().interrupt();
    throw ex;
  } finally {
    process.destroyForcibly();
  }
}

private static class Worker extends Thread {
  private final Process process;
  private Integer exit;
  private Worker(Process process) {
    this.process = process;
  }
  public void run() {
    try { 
      exit = process.waitFor();
    } catch (InterruptedException ignore) {
      return;
    }
  }  
}

2
投票

[有多种方法可以执行此操作,但是我考虑使用Executor -它仅可以帮助您封装将退出值或异常从线程传递回原始调用者的方法。


2
投票

我还测试了worker的实现,并且很有吸引力。在处理过程io下,我添加了线程来处理stde和stdo。如果辅助线程超时,我也将退出io线程。


2
投票

[首先提供一些背景信息,我在运行命令时遇到了超时问题,因为我尝试执行的程序永远不会在出现错误的情况下打印任何调试或错误信息,而只会继续在内部自行重试,从而导致进程卡住,因为重试时永远不会出现错误或输出流。


0
投票

您可以启动一个线程,该线程在您想要的时间进入休眠状态,并在休眠之后更改您在executeCommandLine方法中循环的布尔值。


0
投票

这里是StreamThread


0
投票

Apache Commons Exec可以帮助您做到这一点。


0
投票

如果使用Java 8,我会选择AleksanderBlomskøld的答案,即p.waitFor(1,TimeUnit.MINUTE)


0
投票

我知道这确实是老文章;我需要一个类似项目的帮助,所以我想我可能会提供一些我曾经工作过的代码以及能工作的代码。


79
投票

如果您使用的是Java 8或更高版本,则只需使用新的waitFor with timeout

Process p = ...
if(!p.waitFor(1, TimeUnit.MINUTES)) {
    //timeout - kill the process. 
    p.destroy(); // consider using destroyForcibly instead
}

15
投票

[C0之后,我创建了一种更通用的方法来执行相同的操作。

answer by erickson

现在,您所要做的如下:

public class ProcessWithTimeout extends Thread
{
    private Process m_process;
    private int m_exitCode = Integer.MIN_VALUE;

    public ProcessWithTimeout(Process p_process)
    {
        m_process = p_process;
    }

    public int waitForProcess(int p_timeoutMilliseconds)
    {
        this.start();

        try
        {
            this.join(p_timeoutMilliseconds);
        }
        catch (InterruptedException e)
        {
            this.interrupt();
        }

        return m_exitCode;
    }

    @Override
    public void run()
    {
        try
        { 
            m_exitCode = m_process.waitFor();
        }
        catch (InterruptedException ignore)
        {
            // Do nothing
        }
        catch (Exception ex)
        {
            // Unexpected exception
        }
    }
}

11
投票

我使用详细的代码示例附带的三种建议方法来实现此目的(我是线程编程的新手,并且这些示例代码非常宝贵-如果只是解释了一下,我仍然会scratch之以鼻。英文无代码)。

我使用三种方法来实现我用于此目的的实用程序类,该方法用于执行具有超时的命令,如下所示:

Process process = Runtime.getRuntime().exec("<your command goes here>");
ProcessWithTimeout processWithTimeout = new ProcessWithTimeout(process);
int exitCode = processWithTimeout.waitForProcess(5000);

if (exitCode == Integer.MIN_VALUE)
{
    // Timeout
}
else
{
    // No timeout !
}

我创建了一个类来使用和显示命令的输出和错误流(取自package com.abc.network.lifecycle.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Utility class for performing process related functions such as command line processing. */ public class ProcessUtility { static Log log = LogFactory.getLog(ProcessUtility.class); /** * Thread class to be used as a worker */ private static class Worker extends Thread { private final Process process; private Integer exitValue; Worker(final Process process) { this.process = process; } public Integer getExitValue() { return exitValue; } @Override public void run() { try { exitValue = process.waitFor(); } catch (InterruptedException ignore) { return; } } } /** * Executes a command. * * @param command * @param printOutput * @param printError * @param timeOut * @return * @throws java.io.IOException * @throws java.lang.InterruptedException */ public static int executeCommandWithExecutors(final String command, final boolean printOutput, final boolean printError, final long timeOut) { // validate the system and command line and get a system-appropriate command line String massagedCommand = validateSystemAndMassageCommand(command); try { // create the process which will run the command Runtime runtime = Runtime.getRuntime(); final Process process = runtime.exec(massagedCommand); // consume and display the error and output streams StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput); StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError); outputGobbler.start(); errorGobbler.start(); // create a Callable for the command's Process which can be called by an Executor Callable<Integer> call = new Callable<Integer>() { public Integer call() throws Exception { process.waitFor(); return process.exitValue(); } }; // submit the command's call and get the result from a Future<Integer> futureResultOfCall = Executors.newSingleThreadExecutor().submit(call); try { int exitValue = futureResultOfCall.get(timeOut, TimeUnit.MILLISECONDS); return exitValue; } catch (TimeoutException ex) { String errorMessage = "The command [" + command + "] timed out."; log.error(errorMessage, ex); throw new RuntimeException(errorMessage, ex); } catch (ExecutionException ex) { String errorMessage = "The command [" + command + "] did not complete due to an execution error."; log.error(errorMessage, ex); throw new RuntimeException(errorMessage, ex); } } catch (InterruptedException ex) { String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption."; log.error(errorMessage, ex); throw new RuntimeException(errorMessage, ex); } catch (IOException ex) { String errorMessage = "The command [" + command + "] did not complete due to an IO error."; log.error(errorMessage, ex); throw new RuntimeException(errorMessage, ex); } } /** * Executes a command. * * @param command * @param printOutput * @param printError * @param timeOut * @return * @throws java.io.IOException * @throws java.lang.InterruptedException */ public static int executeCommandWithSleep(final String command, final boolean printOutput, final boolean printError, final long timeOut) { // validate the system and command line and get a system-appropriate command line String massagedCommand = validateSystemAndMassageCommand(command); try { // create the process which will run the command Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec(massagedCommand); // consume and display the error and output streams StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput); StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError); outputGobbler.start(); errorGobbler.start(); // run a thread which will set a flag once it has slept for the timeout period final boolean[] flags = { true }; new Thread() { @Override public void run() { try { Thread.sleep(timeOut); } catch (InterruptedException ex) { String errorMessage = "Timeout loop thread unexpectedly interrupted."; log.error(errorMessage, ex); throw new RuntimeException(errorMessage, ex); } flags[0] = false; } }.start(); // execute the command and wait int returnValue = -1; while (flags[0] && (returnValue < 0)) { returnValue = process.waitFor(); } // if the command timed out then log it if (returnValue < 0) { log.warn("The command [" + command + "] did not complete before the timeout period expired (timeout: " + timeOut + " ms)"); } return returnValue; } catch (InterruptedException ex) { String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption."; log.error(errorMessage, ex); throw new RuntimeException(errorMessage, ex); } catch (IOException ex) { String errorMessage = "The command [" + command + "] did not complete due to an IO error."; log.error(errorMessage, ex); throw new RuntimeException(errorMessage, ex); } } /** * Executes a command. * * @param command * @param printOutput * @param printError * @param timeOut * @return * @throws java.io.IOException * @throws java.lang.InterruptedException */ public static int executeCommandWithWorker(final String command, final boolean printOutput, final boolean printError, final long timeOut) { // validate the system and command line and get a system-appropriate command line String massagedCommand = validateSystemAndMassageCommand(command); try { // create the process which will run the command Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec(massagedCommand); // consume and display the error and output streams StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput); StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError); outputGobbler.start(); errorGobbler.start(); // create and start a Worker thread which this thread will join for the timeout period Worker worker = new Worker(process); worker.start(); try { worker.join(timeOut); Integer exitValue = worker.getExitValue(); if (exitValue != null) { // the worker thread completed within the timeout period return exitValue; } // if we get this far then we never got an exit value from the worker thread as a result of a timeout String errorMessage = "The command [" + command + "] timed out."; log.error(errorMessage); throw new RuntimeException(errorMessage); } catch (InterruptedException ex) { worker.interrupt(); Thread.currentThread().interrupt(); throw ex; } } catch (InterruptedException ex) { String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption."; log.error(errorMessage, ex); throw new RuntimeException(errorMessage, ex); } catch (IOException ex) { String errorMessage = "The command [" + command + "] did not complete due to an IO error."; log.error(errorMessage, ex); throw new RuntimeException(errorMessage, ex); } } /** * Validates that the system is running a supported OS and returns a system-appropriate command line. * * @param originalCommand * @return */ private static String validateSystemAndMassageCommand(final String originalCommand) { // make sure that we have a command if (originalCommand.isEmpty() || (originalCommand.length() < 1)) { String errorMessage = "Missing or empty command line parameter."; log.error(errorMessage); throw new RuntimeException(errorMessage); } // make sure that we are running on a supported system, and if so set the command line appropriately String massagedCommand; String osName = System.getProperty("os.name"); if (osName.equals("Windows XP")) { massagedCommand = "cmd.exe /C " + originalCommand; } else if (osName.equals("Solaris") || osName.equals("SunOS") || osName.equals("Linux")) { massagedCommand = originalCommand; } else { String errorMessage = "Unable to run on this system which is not Solaris, Linux, or Windows XP (actual OS type: \'" + osName + "\')."; log.error(errorMessage); throw new RuntimeException(errorMessage); } return massagedCommand; } } :]

http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4

我创建了一个测试命令,大约需要10秒钟才能完成:

package com.abc.network.lifecycle.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Utility thread class which consumes and displays stream input.
 * 
 * Original code taken from http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4
 */
class StreamGobbler
    extends Thread
{
    static private Log log = LogFactory.getLog(StreamGobbler.class);
    private InputStream inputStream;
    private String streamType;
    private boolean displayStreamOutput;

    /**
     * Constructor.
     * 
     * @param inputStream the InputStream to be consumed
     * @param streamType the stream type (should be OUTPUT or ERROR)
     * @param displayStreamOutput whether or not to display the output of the stream being consumed
     */
    StreamGobbler(final InputStream inputStream,
                  final String streamType,
                  final boolean displayStreamOutput)
    {
        this.inputStream = inputStream;
        this.streamType = streamType;
        this.displayStreamOutput = displayStreamOutput;
    }

    /**
     * Consumes the output from the input stream and displays the lines consumed if configured to do so.
     */
    @Override
    public void run()
    {
        try
        {
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String line = null;
            while ((line = bufferedReader.readLine()) != null)
            {
                if (displayStreamOutput)
                {
                    System.out.println(streamType + ">" + line);
                }
            }
        }
        catch (IOException ex)
        {
            log.error("Failed to successfully consume and display the input stream of type " + streamType + ".", ex);
            ex.printStackTrace();
        }
    }
}

然后,我创建了一个测试程序来测试这三种不同的方法,分别以5秒的超时值(命令应该失败)和15秒的超时值(命令应该成功)调用每个方法:

#!/bin/bash
sleep 10
echo 'TEST COMMAND RAN OK'

这是运行测试程序时看到的内容:

package com.abc.network.lifecycle.util;

public class ProcessUtilityTester
{

    /**
     * @param args
     */
    public static void main(final String[] args)
    {
        try
        {
            String command = args[0];
            int exitValue = -1;
            System.out.println("\n\n5000ms timeout With Executors:");
            try
            {
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithExecutors(command, true, true, 5000);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
            finally
            {
                System.out.println("\nExit value:" + exitValue);
            }
            System.out.println("\n\n5000ms timeout With Sleep:");
            try
            {
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithSleep(command, true, true, 5000);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
            finally
            {
                System.out.println("\nExit value:" + exitValue);
            }
            System.out.println("\n\n5000ms timeout With Worker:");
            try
            {
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithWorker(command, true, true, 5000);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
            finally
            {
                System.out.println("\nExit value:" + exitValue);
            }
            System.out.println("\n\n15000ms timeout With Executors:");
            try
            {
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithExecutors(command, true, true, 15000);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
            finally
            {
                System.out.println("\nExit value:" + exitValue);
            }
            System.out.println("\n\n15000ms timeout With Sleep:");
            try
            {
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithSleep(command, true, true, 15000);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
            finally
            {
                System.out.println("\nExit value:" + exitValue);
            }
            System.out.println("\n\n15000ms timeout With Worker:");
            try
            {
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithWorker(command, true, true, 15000);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
            finally
            {
                System.out.println("\nExit value:" + exitValue);
            }
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        finally
        {
            System.exit(0);
        }
    }

}

因此,我可以看出使用Worker线程类的方法效果最佳,因为在两种情况下都能获得预期的结果。使用Executors的方法也按预期方式工作,但需要注意的是,在15000ms timout情况下,它似乎两次运行了命令(即,我两次看到了命令的输出)。使用sleep()方法的方法不会在5000ms超时的情况下使命令超时,而是显示两次输出,但是在15000ms超时的情况下按预期运行命令。


5
投票

对于使用执行程序框架的每个人:你们都忘记了关闭执行程序。因此,将其更改为以下内容:

5000ms timeout With Executors:
May 1, 2009 1:55:19 AM com.abc.network.lifecycle.util.ProcessUtility executeCommandWithExecutors
SEVERE: The command [/tmp/testcmd.sh] timed out.
java.util.concurrent.TimeoutException
        at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:228)
        at java.util.concurrent.FutureTask.get(FutureTask.java:91)
        at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithExecutors(ProcessUtility.java:179)
        at com.abc.network.lifecycle.util.ProcessUtilityTester.main(ProcessUtilityTester.java:19)
java.lang.RuntimeException: The command [/tmp/testcmd.sh] timed out.
        at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithExecutors(ProcessUtility.java:186)
        at com.abc.network.lifecycle.util.ProcessUtilityTester.main(ProcessUtilityTester.java:19)
Caused by: java.util.concurrent.TimeoutException
        at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:228)
        at java.util.concurrent.FutureTask.get(FutureTask.java:91)
        at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithExecutors(ProcessUtility.java:179)
        ... 1 more

Exit value:-1


5000ms timeout With Sleep:
OUTPUT>TEST COMMAND RAN OK
OUTPUT>TEST COMMAND RAN OK

Exit value:0


5000ms timeout With Worker:
May 1, 2009 1:55:34 AM com.abc.network.lifecycle.util.ProcessUtility executeCommandWithWorker
SEVERE: The command [/tmp/testcmd.sh] timed out.
java.lang.RuntimeException: The command [/tmp/testcmd.sh] timed out.
        at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithWorker(ProcessUtility.java:338)
        at com.abc.network.lifecycle.util.ProcessUtilityTester.main(ProcessUtilityTester.java:47)

Exit value:-1


15000ms timeout With Executors:
OUTPUT>TEST COMMAND RAN OK
OUTPUT>TEST COMMAND RAN OK

Exit value:0


15000ms timeout With Sleep:
OUTPUT>TEST COMMAND RAN OK

Exit value:0


15000ms timeout With Worker:
OUTPUT>TEST COMMAND RAN OK

Exit value:0

如果您不这样做,则程序将保留一个活动的非守护程序线程,确保您的程序在调用System.exit之前永远不会退出


5
投票

对于那些无法使用新的Java 8方法ExecutorService service = Executors.newSingleThreadExecutor(); try { Future<Integer> ft = service.submit(call); try { int exitVal = ft.get(2000L, TimeUnit.MILLISECONDS); return exitVal; } catch (TimeoutException to) { p.destroy(); throw to; } } finally { service.shutdown(); } (因为它们在Android上或根本无法升级)的人,您可以简单地从JDK源代码中提取它并将其添加到您的utils文件中的某个位置:] >

waitFor(long timeout, TimeUnit unit)

我对JDK8源代码中的原始代码所做的唯一更改是添加了waitFor(long timeout, TimeUnit unit)参数,因此我们可以从该过程中调用public boolean waitFor(long timeout, TimeUnit unit, final Process process) throws InterruptedException { long startTime = System.nanoTime(); long rem = unit.toNanos(timeout); do { try { process.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; } 方法。

Process方法将直接尝试返回或抛出exitValue,如果该过程尚未终止。在这种情况下,我们等待收到的超时并终止。

该方法返回一个布尔值,因此,如果它返回false,则您需要手动终止该进程。

这种方法似乎比上面发布的任何方法都要简单(肯定要直接调用waitFor)。


3
投票

用于小型应用程序的轻量级解决方案:


2
投票

以代理身份执行,如果通话超出您的门槛,则通话失败。


2
投票

[尝试在单独的线程中或事件队列中使用计时器(或Sleep()),如果有可用的话。

© www.soinside.com 2019 - 2024. All rights reserved.