Domanda

Ho un metodo che sto usando per eseguire un comando sull'host locale. Vorrei aggiungere un parametro di timeout al metodo in modo che se il comando chiamato non termina in un periodo di tempo ragionevole, il metodo restituirà un codice di errore. Ecco come appare finora, senza la possibilità di timeout:

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

Qualcuno può suggerire un buon modo per implementare un parametro di timeout?

È stato utile?

Soluzione

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

Altri suggerimenti

Se stai usando Java 8 o versioni successive potresti semplicemente usare il nuovo waitFor con timeout :

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

Seguendo la risposta di erickson , ho creato un modo più generico per fare la stessa cosa.

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

Ora, tutto ciò che devi fare è il seguente:

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

L'ho implementato usando i tre approcci suggeriti forniti con un esempio di codice dettagliato (sono un principiante con la programmazione di thread e questi codici di esempio erano inestimabili - continuerei a grattarmi la testa su come farlo se fosse solo spiegato in inglese senza codice).

Ho implementato la classe di utilità che sto usando per questo con i tre metodi per eseguire un comando con un timeout in questo modo:

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

Ho creato una classe per utilizzare e visualizzare i flussi di output e di errore da un comando (tratto da 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();
        }
    }
}

Ho creato un comando di prova che richiede circa 10 secondi per essere completato:

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

Quindi ho creato un programma di test per testare i tre diversi metodi, chiamando ciascuno con un valore di timeout di 5 secondi (il comando dovrebbe fallire) e con un valore di timeout di 15 secondi (il comando dovrebbe avere successo):

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

}

Ecco cosa vedo quando eseguo il programma di test:

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

Quindi, da quello che posso dire, l'approccio che utilizza una classe di thread di lavoro funziona meglio, in quanto fornisce i risultati previsti in entrambi i casi. L'approccio che utilizza Executors funziona anche come previsto, con l'avvertenza che sembra che stia eseguendo il comando due volte nel caso timout 15000ms (ovvero vedo l'output per il comando due volte). L'approccio che utilizza il metodo sleep () non esegue il timeout del comando come previsto nel caso di timeout di 5000ms e visualizza l'output due volte, ma esegue il comando come previsto nel caso di timeout di 15000ms.

Per tutti coloro che usano il framework degli esecutori: tutti dimenticano di chiudere l'esecutore. Quindi modificalo come segue:

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

Se non lo fai, il tuo programma manterrà un thread non daemon attivo, assicurandoti che il tuo programma non si chiuderà mai finché non chiami System.exit

Per coloro che non possono usare il nuovo metodo Java 8 waitFor (timeout lungo, unità TimeUnit) (perché sono su Android o semplicemente non possono eseguire l'aggiornamento) puoi semplicemente strappalo dal codice sorgente JDK e aggiungilo da qualche parte nel tuo file 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;
    }

L'unica modifica che ho apportato a quello originale dal codice sorgente JDK8 è l'aggiunta del parametro Process in modo da poter chiamare exitValue dal processo.

exitValue proverà direttamente a restituire o generare un IllegalThreadStateException se il processo non è ancora terminato. In tal caso, attendiamo il timeout ricevuto e terminiamo.

Il metodo restituisce un valore booleano, quindi se restituisce false allora sai che devi terminare manualmente il processo.

In questo modo sembra più semplice di tutto ciò che è stato pubblicato sopra (aspettati che la chiamata diretta sia in attesa di sicuro).

Una soluzione leggera per piccole app:

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

Implementa come delegato e fallisci la chiamata se il completamento richiede la tua soglia.

Prova a utilizzare un timer (o sospensione ()), in un thread separato o nella coda degli eventi, se ne hai uno disponibile.

Esistono vari modi per farlo, ma prenderei in considerazione l'uso di un Executor: ti aiuta a incapsulare il passaggio del valore di uscita o dell'eccezione dal thread al chiamante originale.

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

Penso che non sia possibile aggirare le condizioni di gara in base alle quali l'attesa scade e quindi il processo termina appena prima di chiamare destro ().

Ho anche testato l'implementazione del lavoratore e funziona come un incantesimo. Sotto il processo di gestione io, ho aggiunto thread per gestire stde e stdo. Se il thread di lavoro scade, anch'io esco dai thread 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();
            }

Prima alcune informazioni di base, mi sono imbattuto nel problema di avere un timeout durante l'esecuzione di un comando perché il programma che ho cercato di eseguire non avrebbe mai stampato alcun debug o informazioni di errore in caso di errore e continuerebbe a riprovare internamente da solo risultando nel processo bloccato perché non si è mai verificato alcun errore o flusso di output durante il tentativo di nuovo tentativo.

Quindi dopo process.exec () o process.start () ,

Sarebbe bloccato per sempre su questa linea,

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

Come da java 1.8 con il metodo public booleano waitFor (timeout lungo, unità TimeUnit) dovrebbe avere " idealmente " scaduto dopo il timeout specificato, ma nel mio caso per qualche motivo non è mai scaduto perché stavo eseguendo un'applicazione come servizio di Windows (ho verificato le autorizzazioni utente e tutto sull'account ma non ha aiutato).

Quindi ho cercato di implementarlo con la logica seguente, dove continuavamo a controllare il flusso di input con input.ready () e un flag di timeout. Questa semplice soluzione ha funzionato come un fascino rispetto a tutti altro che esisteva.

Codice:

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

Puoi lanciare un Thread che dorme per il tempo che vuoi e dopo il sonno cambiando un booleano che esegui in loop nel tuo metodo executeCommandLine.

Qualcosa del genere (non testata né compilata, questa soluzione è un prototipo che dovresti rifattorizzare se ti soddisfa):

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

ed ecco 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 può aiutarti a farlo.

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

Se utilizzassi Java 8 andrei con Aleksander Blomsk & # 248; rispondo io ad esempio p.waitFor (1, TimeUnit.MINUTE)

altrimenti se Java 6/7 e utilizzando Swing, è possibile utilizzare 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
   }

So che questo è un post davvero vecchio; avevo bisogno di aiuto con un progetto simile, quindi ho pensato di poter dare un po 'del mio codice che ho lavorato e quelli che funzionano.

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;

Spero che questo aiuti il ??futuro: D

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top