Как добавить значение тайм-аута при использовании Java Runtime.exec()?

StackOverflow https://stackoverflow.com/questions/808276

Вопрос

У меня есть метод, который я использую для выполнения команды на локальном хосте.Я хотел бы добавить в метод параметр тайм-аута, чтобы, если вызываемая команда не завершится в течение разумного периода времени, метод вернется с кодом ошибки.Вот как это выглядит на данный момент, без возможности тайм-аута:

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();
}

Может ли кто-нибудь предложить мне хороший способ реализовать параметр тайм-аута?

Это было полезно?

Решение

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;
    }
  }  
}

Другие советы

Если вы используете Java 8 или более позднюю версию, вы можете просто использовать новый waitFor с таймаутом:

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

После ответ от Эриксона, Я создал более общий способ сделать то же самое.

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
        }
    }
}

Теперь все, что вам нужно сделать, это следующее:

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):

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();
        }
    }
}

Я создал тестовую команду, выполнение которой занимает примерно 10 секунд:

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

Затем я создал тестовую программу для тестирования трех разных методов, вызывая каждый со значением таймаута 5 секунд (команда должна завершиться неудачно) и со значением таймаута 15 секунд (команда должна завершиться успешно):

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);
        }
    }

}

Вот что я вижу, когда запускаю тестовую программу:

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

Итак, насколько я могу судить, подход с использованием класса потока Worker работает лучше всего, поскольку в обоих случаях он дает ожидаемые результаты.Подход с использованием исполнителей также работает так, как и ожидалось, с оговоркой, что в случае таймаута 15000 мс команда выполняется дважды (т.Я вижу вывод команды дважды).Подход с использованием метода Sleep() не ограничивает время ожидания команды, как ожидалось, в случае таймаута 5000 мс и отображает выходные данные дважды, но запускает команду, как ожидалось, в случае таймаута 15000 мс.

Для всех, кто использует структуру исполнителя:вы все забываете завершить работу исполнителя.Поэтому измените его на следующее:

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();
}

Если вы этого не сделаете, ваша программа сохранит активный поток, не являющийся демоном, гарантируя, что ваша программа никогда не завершит работу, пока вы не вызовете System.exit.

Для тех, кто не может использовать новый метод Java 8 waitFor(long timeout, TimeUnit unit) (потому что они работают на Android или просто не могут быть обновлены), вы можете просто скопировать их из исходного кода JDK и добавить куда-нибудь в свой файл utils:

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;
    }

Единственное изменение, которое я внес в исходный код JDK8, — это добавление Process параметр, чтобы мы могли вызвать exitValue метод из процесса.

А exitValue метод напрямую попытается вернуть или выдать IllegalThreadStateException если процесс еще не завершился.В этом случае мы ждем полученного таймаута и завершаем работу.

Метод возвращает логическое значение, поэтому, если он возвращает false, значит, вам нужно вручную завершить процесс.

Этот способ кажется проще, чем все, что было описано выше (наверняка ожидайте прямого вызова waitFor).

Легкое решение для небольших приложений:

public class Test {
    public static void main(String[] args) throws java.io.IOException, InterruptedException {   
        Process process = new ProcessBuilder().command("sleep", "10").start();

        int i=0;
        boolean deadYet = false;
        do {
            Thread.sleep(1000);
            try {
                process.exitValue();
                deadYet = true;
            } catch (IllegalThreadStateException e) {
                System.out.println("Not done yet...");
                if (++i >= 5) throw new RuntimeException("timeout");
            }
        } while (!deadYet);
    }
}

Реализуйте в качестве делегата и откажитесь от вызова, если для его завершения требуется превышение порогового значения.

Попробуйте использовать таймер (или Sleep()) в отдельном потоке или в очереди событий, если она у вас есть.

Есть разные способы сделать это, но я бы рассмотрел возможность использования Executor - он просто помогает вам инкапсулировать передачу значения выхода или исключения из потока обратно исходному вызывающему объекту.

    final Process p = ...        
    Callable<Integer> call = new Callable<Integer>() {
    public Integer call() throws Exception {
        p.waitFor();
        return p.exitValue();
      }
    };
    Future<Integer> ft = Executors.newSingleThreadExecutor().submit(call);
    try {
      int exitVal = ft.get(2000L, TimeUnit.MILLISECONDS);
      return exitVal;
    } catch (TimeoutException to) {
      p.destroy();
      throw to;
    }

Я думаю, что вы не можете обойти состояние гонки, когда время ожидания истекает, а затем процесс завершается непосредственно перед вызовом метода Destroy().

Я также протестировал реализацию работника, и она работает как шарм.При обработке процесса io я добавил потоки для обработки stde и stdo.Если время рабочего потока истекает, я также выхожу из потоков io.

Process p = Runtime.getRuntime().exec(cmd.trim());

            //setup error and output stream threads
            CommandStreamThread eStream = new CommandStreamThread(p.getErrorStream(), "STDE");            
            CommandStreamThread oStream = new CommandStreamThread(p.getInputStream(), "STDO");

            // kick them off
            eStream.start();
            oStream.start();

            //setup a worker thread so we can time it out when we need
            CommandWorkerThread worker=new CommandWorkerThread(p);
            worker.start();

            try {
                worker.join(this.getTimeout());
                if (worker.getExit() != null)
                    return worker.getExit();
                else
                    throw new TimeoutException("Timeout reached:"+this.getTimeout()+" ms");
            } catch(InterruptedException ex) {
                eStream.interrupt();
                oStream.interrupt();
                worker.interrupt();
                Thread.currentThread().interrupt();
                throw ex;
            } finally {
                p.destroy();
            }

Сначала немного справочной информации: я столкнулся с проблемой тайм-аута при выполнении команды, потому что программа, которую я пытался выполнить, никогда не печатала какую-либо информацию об отладке или ошибке в случае ошибки и просто продолжала повторять внутренние попытки сама по себе, что приводило к зависанию процесса. потому что при повторной попытке никогда не было ошибок или выходного потока.

Так после process.exec() или process.start() ,

Он бы застрял навечно на этой линии,

BufferedReader input = new BufferedReader(newInputStreamReader(process.getInputStream()));

Согласно Java 1.8 с public boolean waitFor(long timeout,TimeUnit unit) метод, в идеале он должен иметь тайм-аут после указанного тайм-аута, но в моем случае по какой-то причине тайм-аут никогда не истекал, возможно, потому, что я запускал приложение как службу Windows (я проверил права пользователя и все в учетной записи, но это не помогло не поможет).

Поэтому я попытался реализовать это с помощью приведенной ниже логики, где мы будем продолжать проверять входной поток с помощью input.ready() и флаг тайм-аута. Это простое решение работало как шарм по сравнению со всеми другими, которые существовали.

Код:

public boolean runCommand() throws IOException, InterruptedException, Exception {
    StringBuilder rawResponse = new StringBuilder();
    System.out.println("Running Command " + Arrays.toString(command));
    ProcessBuilder processBuilder = new ProcessBuilder(Arrays.asList(command));
    processBuilder.redirectErrorStream(true);
    Process process = processBuilder.start(); //Executing the process
    BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
    waitForTimeout(input, process); //Waiting for Timout
    String line;
    while ((line = input.readLine()) != null) {
        rawResponse.append(line).append("\n");
    }
    return true;
}


//Timeout method 
private void waitForTimeout(BufferedReader input, Process process) throws InterruptedException, Exception {
    int timeout = 5;
    while (timeout > 0) {
        if (input.ready()) {
            break;
        } else {
            timeout--;
            Thread.sleep(1000);
            if (timeout == 0 && !input.ready()) {
                destroyProcess(process);
                throw new Exception("Timeout in executing the command "+Arrays.toString(command));
            }
        }
    }
}

Вы можете запустить поток, который находится в режиме ожидания на нужное вам время, и после сна изменить логическое значение, которое вы зацикливаете в своем методе выполненияCommandLine.

Что-то вроде этого (не тестировалось и не компилировалось, это решение является прототипом, вы должны его реорганизовать, если оно вам нужно):

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());
    }

    ret = -1;
    final[] b = {true};
    new Thread(){
       public void run(){
           Thread.sleep(2000); //to adapt
           b[0] = false;
       }
    }.start();
    while(b[0])
    {
          ret = process.waitFor();
    }

    return ret;
}

и вот StreamThread

public class CommandStreamThread extends Thread{
        private InputStream iStream;
        private String cPrompt;

        CommandStreamThread (InputStream is, String cPrompt)
        {
            this.iStream = is;
            this.cPrompt = cPrompt;
        }

        public void run()
        {
            try
            {
                InputStreamReader streamReader= new InputStreamReader(this.iStream);
                BufferedReader reader = new BufferedReader(streamReader);


                String linesep=System.getProperty("line.separator");
                String line=null;
                while ((line=reader.readLine())!=null){
                    System.out.println(line);
                    //Process the next line seperately in case this is EOF is not preceded by EOL
                    int in;
                    char[] buffer=new char[linesep.length()];
                    while ( (in = reader.read(buffer)) != -1){
                        String bufferValue=String.valueOf(buffer, 0, in);
                        System.out.print(bufferValue);
                        if (bufferValue.equalsIgnoreCase(linesep))
                            break;
                    }
                }

                //Or the easy way out with commons utils!
                //IOUtils.copy(this.iStream, System.out);


              } catch (Exception e){
                    e.printStackTrace();  
              }
        }

        public InputStream getIStream() {
            return iStream;
        }

        public void setIStream(InputStream stream) {
            iStream = stream;
        }

        public String getCPrompt() {
            return cPrompt;
        }

        public void setCPrompt(String prompt) {
            cPrompt = prompt;
        }


}

Apache Commons Exec может помочь вам в этом.

Видеть http://commons.apache.org/proper/commons-exec/tutorial.html

String line = "your command line";
CommandLine cmdLine = CommandLine.parse(line);
DefaultExecutor executor = new DefaultExecutor();
ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
executor.setWatchdog(watchdog);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
int exitValue = executor.execute(cmdLine);
System.out.println(exitValue);
System.out.println(outputStream.toString());

Если вы используете Java 8, я бы выбрал ответ Александра Бломскёльда, т.е.p.waitFor(1, TimeUnit.MINUTE)

иначе, если Java 6/7 и используется Swing, вы можете использовать SwingWorker:

   final Process process = ...
   SwingWorker<Integer, Integer> sw = new SwingWorker<>() {
       @Override
       protected Integer doInBackground() throws Exception {
          process.waitFor();
          return process.exitValue();
       }
   };
   sw.execute();                
   int exitValue = sw.get(1, TimeUnit.SECONDS);
   if (exitValue == 0) {
       //everything was fine
   } else {
       //process exited with issues
   }

Я знаю, что это действительно старый пост;мне нужна была помощь с похожим проектом, поэтому я подумал, что могу поделиться частью своего кода, над которым я работал, и кода, который работает.

long current = System.currentTimeMillis();

ProcessBuilder pb  = new ProcessBuilder(arguments);
try{
    pb.redirectErrorStream(true);
    process = pb.start();
    int c ;
    while((c = process.getInputStream().read()) != -1 )
        if(System.currentTimeMillis() - current < timeOutMilli) 
            result += (char)c;
        else throw new Exception();
    return result.trim();
    }catch(Exception e){
        e.printStackTrace();
    }
    return result;

Надеюсь, это поможет в будущем :D

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top