Merci beaucoup pour toutes les réponses utiles!
J'ai mis en œuvre au moyen des trois approches proposées qui sont venus détaillées avec des exemples de code (je suis un novice avec le fil de la programmation et de ces codes ont été précieux -- je serais encore à me gratter la tête à la façon de le faire que si c'était juste expliqué en anglais sans code). Je vais vous expliquer ce que j'ai fait ci-dessous et donner mes premières impressions avec quelques autres questions, dont les réponses peuvent m'aider à comprendre ce problème mieux (et j'espère qu'il va profiter de la prochaine personne qui cherche une réponse à ce problème).
J'ai mis en place la classe utilitaire que j'utilise avec les trois méthodes pour l'exécution d'une commande avec un délai d'attente de la sorte:
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;
}
}
J'ai créé une classe de consommer et d'afficher la sortie d'erreur et de flux à partir d'une commande (prises à partir de 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();
}
}
}
J'ai créé une commande test qui prend à peu près 10 secondes:
#!/bin/bash
sleep 10
echo 'TEST COMMAND RAN OK'
Puis j'ai créé un programme pour tester les trois méthodes différentes, appelant chacune avec une valeur de temporisation de 5 secondes (commande d'échec) et avec une valeur de temporisation de 15 secondes (commande doit réussir):
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);
}
}
}
Voici ce que je vois quand je lance le programme de 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
Donc, à partir de ce que je peux dire la démarche à l'aide d'un Travailleur de la classe thread qui fonctionne le mieux, en ce qu'elle donne les résultats escomptés dans les deux cas. L'approche à l'aide d'Exécuteurs fonctionne comme prévu, avec la mise en garde qu'il semble être l'exécution de la commande deux fois dans la 15000ms timout cas (c'est à dire je vois la sortie de la commande deux fois). L'approche à l'aide de la méthode de sommeil() n'a pas de délai d'attente de la commande comme prévu dans le 5000ms délai de cas, et affiche la sortie deux fois, mais exécute la commande comme prévu dans le 15000ms délai de cas.
Donc, deux autres questions:
Pourquoi la commande apparemment en train d'être exécuté deux fois dans le cas de l'interprète et du sommeil() approches?
Comment puis-je mettre fin à la executeCommand*() méthodes de sorte qu'ils ne laissent pas la commande du thread en cours d'exécution? Quand je n'ai pas de conclure mon programme de test d'un Système.exit(0) il n'est jamais retourné, et quand j'ai couru dans mon débogueur il fait la même chose et a montré les fils encore en vie dans la JVM.
Encore un immense merci à tous les premiers répondants et merci d'avance à toute personne d'autre qui peut clarifier la confusion que j'ai encore sur ce sujet.
--James
0 votes
Je suppose que c'est un exemple simplifié. Vous savez que cela bloque si les tampons de sortie standard ou d'erreur standard sont pleins. Vous les avez lus de manière asynchrone, à la fois en parallèle.
0 votes
@MichaelPiefel C'est vrai. Alors je commence un nouveau fil de discussion pour cela, mais la question est lorsque le processus meurt, faut-il aussi tuer les threads ?