Wie füge ich einen Timeout-Wert hinzu, wenn ich Javas Runtime.exec () verwende?

76

Ich habe eine Methode, mit der ich einen Befehl auf dem lokalen Host ausführe. Ich möchte der Methode einen Timeout-Parameter hinzufügen, damit die Methode mit einem Fehlercode zurückgegeben wird, wenn der aufgerufene Befehl nicht in angemessener Zeit beendet wird. So sieht es bisher aus, ohne dass eine Zeitüberschreitung möglich ist:

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

Kann mir jemand eine gute Möglichkeit vorschlagen, einen Timeout-Parameter zu implementieren?

James Adams
quelle
Ich nehme an, dies ist ein vereinfachtes Beispiel. Sie wissen, dass dies blockiert wird, wenn die Puffer für die Standardausgabe oder den Standardfehler voll sind. Sie haben sie asynchron gelesen, beide parallel.
Michael Piefel
@ MichaelPiefel Das stimmt. Also starte ich einen neuen Thread für diese, aber die Frage ist, wann der Prozess stirbt, muss man auch die Threads töten?
mmm

Antworten:

55
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;
    }
  }  
}
erickson
quelle
Ich habe es versucht, aber ich erhalte den Wert worker.exit immer null.
Vishal
10
Stellen Sie nicht nur einen Codeblock auf. Bitte geben Sie Erklärungen.
Stealth Rabbi
Vielleicht verstehe ich nicht Thread.join(long time)oder Thread.wait(long time), was Thread.join(long time)nennen kann. Wie stoppt dies die Ausführung des Prozesses, wenn das Zeitlimit abgelaufen ist? Ich habe einen Test mit den oben genannten Methoden ausgeführt, bei dem ich die aktuelle Uhrzeit drucke stdout, den Prozess ausführe und dann die Uhrzeit erneut drucke. Die Ausführung des Prozesses dauert ungefähr 3 Sekunden. Der Vorgang wird immer nach Ablauf der Zeitüberschreitungsperiode abgeschlossen, obwohl ich die Zeitüberschreitung als 1s angegeben habe und eine a erwarte TimeoutException.
Agi Hammerthief
@AgiHammerthief Ich verlasse mich Process.destroy()darauf, unvollendete Prozesse nach dem Timeout zu beenden . Ich werde einige Tests machen. Welches Betriebssystem verwenden Sie? Haben Sie Ihren Test in einer IDE oder in einer Shell ausgeführt?
Erickson
82

Wenn Sie Java 8 oder höher verwenden, können Sie einfach das neue waitFor mit Timeout verwenden :

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

Nach der Antwort von erickson habe ich eine allgemeinere Methode entwickelt, um dasselbe zu tun.

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

Jetzt müssen Sie nur noch Folgendes tun:

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 !
}
Muzikant
quelle
Sehr hilfreich. Ich habe diese Form verwendet (weil ich dadurch meine Stream-verbrauchenden Threads unverändert lassen kann). Ich habe jedoch die aktualisiert waitForProcess, um falsche Interrupts zu berücksichtigen ...: paste.ubuntu.com/9898052
sehe
1
Ich würde p_timeoutMillisecondszu lange wechseln . zBpublic int waitForProcess(long timeoutMilliseconds)
Jossef Harush
Ich denke nicht, dass es gut ist, Interger.MIN_VALUE zu verwenden, um Timeout zu signalisieren
Zoltán Haindrich
11

Ich habe dies mithilfe der drei vorgeschlagenen Ansätze implementiert, die mit einem detaillierten Codebeispiel geliefert wurden (ich bin ein Anfänger in der Thread-Programmierung und diese Beispielcodes waren von unschätzbarem Wert - ich würde mir immer noch den Kopf kratzen, wie dies zu tun ist, wenn es nur auf Englisch erklärt würde ohne Code).

Ich habe die Dienstprogrammklasse, die ich dafür verwende, mit den drei Methoden zum Ausführen eines Befehls mit einer Zeitüberschreitung wie folgt implementiert:

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

Ich habe eine Klasse erstellt, um die Ausgabe- und Fehlerströme eines Befehls zu verwenden und anzuzeigen (entnommen aus 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();
        }
    }
}

Ich habe einen Testbefehl erstellt, der ungefähr 10 Sekunden dauert:

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

Dann habe ich ein Testprogramm erstellt, um die drei verschiedenen Methoden zu testen, wobei jede mit einem Timeout-Wert von 5 Sekunden (Befehl sollte fehlschlagen) und einem Timeout-Wert von 15 Sekunden (Befehl sollte erfolgreich sein) aufgerufen wird:

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

}

Folgendes sehe ich, wenn ich das Testprogramm ausführe:

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

Soweit ich das beurteilen kann, funktioniert der Ansatz mit einer Worker-Thread-Klasse am besten, da er in beiden Fällen die erwarteten Ergebnisse liefert. Der Ansatz mit Executors funktioniert ebenfalls wie erwartet, mit der Einschränkung, dass der Befehl im Fall einer Zeitüberschreitung von 15000 ms anscheinend zweimal ausgeführt wird (dh ich sehe die Ausgabe für den Befehl zweimal). Bei der Verwendung der sleep () -Methode wird der Befehl nicht wie im Zeitlimit von 5000 ms erwartet zeitlich begrenzt und die Ausgabe zweimal angezeigt, sondern der Befehl wird im Zeitlimit von 15000 ms wie erwartet ausgeführt.

James Adams
quelle
Ich denke nicht, dass Sie vom Warten auf den Prozess-Exit-Code bei Interrupt zurückkehren sollten. Dies sollte eine Schleife sein, bis Sie den Exit-Code erfolgreich erhalten haben.
Pawel Veselov
Darf ich fragen, warum Sie cmd.exe verwenden, anstatt den Prozess 'exe' direkt aufzurufen?
PhilW
Ich habe es versucht, aber es wird immer eine Timeout-Ausnahme ausgelöst. Stackoverflow.com/questions/23756326/…
vishal
@ JamesAdams Ich musste Windows 7 zur Liste der Betriebssysteme hinzufügen, damit es ausgeführt werden konnte. Seltsamerweise rennt der Arbeiter zweimal für mich. Das Ausführen externer Befehle von Java aus erscheint mir übermäßig verwirrend.
localhost
5

Für alle, die das Executor-Framework verwenden: Sie alle vergessen, den Executor herunterzufahren. Ändern Sie es also wie folgt:

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

Wenn Sie dies nicht tun, behält Ihr Programm einen aktiven Nicht-Daemon-Thread bei und stellt sicher, dass Ihr Programm erst beendet wird, wenn Sie System.exit aufrufen


quelle
5

Für diejenigen, die die neue Java 8-Methode nicht verwenden können waitFor(long timeout, TimeUnit unit)(weil sie auf Android laufen oder einfach nicht aktualisieren können), können Sie sie einfach aus dem JDK-Quellcode rippen und irgendwo in Ihrer Utils-Datei hinzufügen:

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

Die einzige Änderung, die ich am Original aus dem JDK8-Quellcode vorgenommen habe, ist das Hinzufügen des ProcessParameters, damit wir die exitValueMethode aus dem Prozess aufrufen können.

Die exitValueMethode versucht direkt, einen zurückzugeben oder auszulösen, IllegalThreadStateExceptionwenn der Prozess noch nicht beendet wurde. In diesem Fall warten wir auf das empfangene Timeout und beenden es.

Die Methode gibt einen Booleschen Wert zurück. Wenn also false zurückgegeben wird, müssen Sie den Prozess manuell beenden.

Dieser Weg scheint einfacher zu sein als alles, was oben veröffentlicht wurde (erwarten Sie, dass der direkte Anruf sicher auf WaitFor wartet).

Jean-François Savard
quelle
3

Eine leichte Lösung für kleine Apps:

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);
    }
}
Janus Troelsen
quelle
2

Implementieren Sie als Delegat und schlagen Sie den Anruf fehl, wenn er über Ihrem Schwellenwert liegt.

Chris Ballance
quelle
2

Versuchen Sie es mit einem Timer (oder Sleep ()), in einem separaten Thread oder in Ihrer Ereigniswarteschlange, falls vorhanden.

Macke
quelle
2

Es gibt verschiedene Möglichkeiten, dies zu tun, aber ich würde die Verwendung eines Executors in Betracht ziehen. Dies hilft Ihnen nur dabei, den Exit-Wert oder die Ausnahme vom Thread an den ursprünglichen Aufrufer zurückzugeben.

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

Ich denke, Sie können die Rennbedingung nicht umgehen, bei der die Wartezeit abgelaufen ist und der Prozess kurz vor dem Aufruf von destroy () beendet wird.

Neil Coffey
quelle
2

Ich habe auch die Worker-Implementierung getestet und arbeite wie ein Zauber. Während des Bearbeitungsprozesses io habe ich Threads hinzugefügt, um stde und stdo zu behandeln. Wenn der Worker-Thread abläuft, beende ich auch die Io-Threads.

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();
            }
Peco
quelle
Oh, ich sehe jetzt deine andere Antwort. Wäre besser gewesen, dies alles in einer einzigen Antwort zusammenzufassen, würde ich denken.
localhost
2

Als erstes einige Hintergrundinformationen, stieß ich auf das Problem, dass beim Ausführen eines Befehls eine Zeitüberschreitung auftrat, da das Programm, das ich ausführen wollte, niemals Fehler- oder Fehlerinformationen druckte und es einfach intern erneut versuchte, was dazu führte, dass der Prozess stecken blieb weil es beim erneuten Versuch nie einen Fehler oder einen Ausgabestream gab.

Also nach process.exec()oderprocess.start() ,

Es würde für immer an dieser Linie hängen bleiben,

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

Gemäß Java 1.8 mit public boolean waitFor(long timeout,TimeUnit unit) Methode sollte das Zeitlimit nach dem angegebenen Zeitlimit "idealerweise" abgelaufen sein. In meinem Fall kann es jedoch aus irgendeinem Grund zu einem Zeitlimit kommen, weil ich die Anwendung als Windows-Dienst ausgeführt habe (ich habe die Benutzerberechtigungen überprüft und alles aktiviert das Konto hat aber nicht geholfen).

Also habe ich versucht, es mit der folgenden Logik zu implementieren, mit der wir den Eingabestream weiter überprüfen würden input.ready() und ein Timeout-Flag Diese einfache Lösung funktionierte im Vergleich zu allen anderen existierenden wie ein Zauber.

Code:

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 (!process.isAlive() || 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));
            }
        }
    }
}
xxnations
quelle
Ich hatte das gleiche Problem in groovy und diese Antwort löste mein Problem endgültig. Es hat so viel Zeit gekostet, dass ich meine letzte Drop-In-Builder-Klasse teile : gist.github.com/meonlol/df1174d2c1d84e7fe8db8710cd7beff1 Ich hoffe, es hilft jemandem.
Leondepeon
0

Sie können einen Thread starten, der für die gewünschte Zeit in den Ruhezustand versetzt wird, und nach dem Ruhezustand einen Booleschen Wert ändern, den Sie in Ihrer executeCommandLine-Methode durchlaufen.

So etwas in der Art (weder getestet noch kompiliert, diese Lösung ist ein Prototyp, den Sie überarbeiten sollten, wenn es Ihren Anforderungen entspricht):

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;
}
Valentin Jacquemin
quelle
Was ist der Grund dafür, den Booleschen Wert in ein endgültiges Array zu setzen? Gibt es etwas zu vermeiden?
rndmcnlly
2
Der gestartete Thread ist eine interne Klasse. Um auf eine äußere Variable zugreifen zu können, müssen diese endgültig sein. Da Boolescher Wert ein nativer Typ und kein Objekt ist, wird durch das Festlegen in einem Array ein Objekt daraus. In der Prozess-Requisite können Sie sehen, dass der Prozess p aus demselben Grund auch endgültig ist.
Valentin Jacquemin
0

und hier ist der 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;
        }


}
Peco
quelle
0

Apache Commons Exec kann Ihnen dabei helfen.

Siehe 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());
xxg
quelle
0

Wenn ich Java 8 benutze, würde ich mit Aleksander Blomskøld antworten iepwaitFor (1, TimeUnit.MINUTE)

Wenn Sie Java 6/7 verwenden und Swing verwenden, können Sie einen SwingWorker verwenden:

   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
   }
sproger
quelle
0

Ich weiß, dass dies ein wirklich alter Beitrag ist. Ich brauchte Hilfe bei einem ähnlichen Projekt, also dachte ich, ich könnte einen Teil meines Codes geben, den ich bearbeitet habe, und einen, der funktioniert.

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;

Hoffe das hilft der Zukunft: D.

sabbibJAVA
quelle
Ist die read () -Methode nicht ein blockierender Aufruf, dh ein Aufruf, der wartet, bis Daten verfügbar sind? - Wenn dies der Fall ist, wartet dies nur ewig, bis Daten geschrieben wurden, und das Zeitlimit wird erst nach dem Empfang der Daten ausgewertet.
bvdb