process.waitFor () ne retourne jamais

95
Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();
user590444
la source
Veuillez noter que sur JAVA 8, il existe une surcharge waitFor qui vous permet de spécifier un délai d'expiration. Cela pourrait être un meilleur choix pour s'abstenir d'un cas où waitFor ne revient jamais.
Ikaso

Réponses:

145

Il y a de nombreuses raisons qui waitFor()ne reviennent pas.

Mais cela se résume généralement au fait que la commande exécutée ne se ferme pas.

Ceci, encore une fois, peut avoir de nombreuses raisons.

Une raison courante est que le processus produit une sortie et que vous ne lisez pas les flux appropriés. Cela signifie que le processus est bloqué dès que la mémoire tampon est pleine et attend que votre processus continue sa lecture. Votre processus attend à son tour que l'autre processus se termine (ce qu'il ne fera pas car il attend votre processus, ...). C'est une situation classique de blocage.

Vous devez continuellement lire le flux d'entrée des processus pour vous assurer qu'il ne se bloque pas.

Il y a un bel article qui explique tous les pièges Runtime.exec()et montre des moyens de les contourner appelé "Quand Runtime.exec () ne le fera pas" (oui, l'article date de 2000, mais le contenu s'applique toujours!)

Joachim Sauer
la source
7
Cette réponse est correcte mais il manque un exemple de code pour résoudre le problème. Jetez un œil à la réponse de Peter Lawrey pour trouver un code utile pour savoir pourquoi waitFor()ne revient pas.
ForguesR
83

Il semble que vous ne lisez pas la sortie avant d'attendre qu'elle se termine. Cela ne fonctionne que si la sortie ne remplit pas le tampon. Si c'est le cas, il attendra que vous lisiez la sortie, catch-22.

Peut-être avez-vous des erreurs que vous ne lisez pas. Cela permettrait à l'application de s'arrêter et d'attendre pour attendre indéfiniment. Un moyen simple de contourner ce problème consiste à rediriger les erreurs vers la sortie normale.

ProcessBuilder pb = new ProcessBuilder("tasklist");
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null)
    System.out.println("tasklist: " + line);
process.waitFor();
Peter Lawrey
la source
4
Pour info: ProcessBuilder étant un vrai constructeur, vous pouvez directement écrire ProcessBuilder pb = new ProcessBuilder ("tasklist"). RedirectErrorStream (true);
Jean-François Savard
3
Je préfère utiliserpb.redirectError(new File("/dev/null"));
Toochka
@Toochka Juste pour information, redirectErrorn'est disponible que depuis Java 1.7
ZhekaKozlov
3
Devrait être la réponse acceptée, je crois, j'ai remplacé mon code par ceci et cela a immédiatement fonctionné.
Gerben Rampaart
43

Également de Java doc:

java.lang

Processus de classe

Étant donné que certaines plates-formes natives ne fournissent qu'une taille de tampon limitée pour les flux d'entrée et de sortie standard, l'échec de l'écriture rapide du flux d'entrée ou de la lecture du flux de sortie du sous-processus peut entraîner le blocage du sous-processus, voire un blocage.

L'échec de l'effacement du tampon du flux d'entrée (qui conduit vers le flux de sortie du sous-processus) de Process peut entraîner un blocage du sous-processus.

Essaye ça:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();
RollingBoy
la source
17
Deux mises en garde: (1) Utilisez ProcessBuilder + redirectErrorStream (true), alors vous êtes en sécurité. Sinon, (2) vous avez besoin d'un thread pour lire depuis Process.getInputStream () et un autre pour lire depuis Process.getErrorStream (). Je viens de passer environ quatre heures à comprendre cela (!), Alias ​​"The Hard Way".
kevinarpe le
1
Vous pouvez utiliser la fonctionnalité Apache Commons Exec pour consommer simultanément les flux stdout et stderr DefaultExecutor executor = new DefaultExecutor(); PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdoutOS, stderrOS); executor.setStreamHandler(pumpStreamHandler); executor.execute(cmdLine);:, où stoutOS et stderrOS sont BufferedOutputStreams que j'ai créés pour écrire dans les fichiers appropriés.
Matthew Wise
Dans mon cas, j'appelais un fichier batch de Spring qui ouvre en interne un éditeur. Mon code se bloquait même après avoir appliqué le code de process.close(). Mais lorsque j'ouvre le flux d'entrée comme suggéré ci-dessus et que je ferme immédiatement, le problème disparaît. Donc, dans mon cas, Spring attendait le signal de fermeture du flux. Même si j'utilise Java 8 fermable automatiquement.
shaILU
10

Je voudrais ajouter quelque chose aux réponses précédentes, mais comme je n'ai pas le représentant à commenter, je vais simplement ajouter une réponse. Ceci est destiné aux utilisateurs d'Android qui programment en Java.

Selon le message de RollingBoy, ce code a presque fonctionné pour moi:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();

Dans mon cas, waitFor () ne se libérait pas car j'exécutais une instruction sans retour ("ip adddr flush eth0"). Un moyen simple de résoudre ce problème consiste simplement à vous assurer de toujours renvoyer quelque chose dans votre relevé. Pour moi, cela signifiait exécuter ce qui suit: "ip adddr flush eth0 && echo done". Vous pouvez lire le tampon toute la journée, mais si rien n'est jamais retourné, votre thread ne relâchera jamais son attente.

J'espère que cela aide quelqu'un!

Les pentes
la source
2
Lorsque vous n'avez pas le représentant à commenter, ne le contournez pas et commentez de toute façon . Faites-en une réponse en soi et obtenez des représentants!
Fund Monica's Lawsuit
Je ne pense pas que ce soit le process.waitFor()qui se bloque, c'est le reader.readLine()qui se bloque si vous n'avez pas de sortie. J'ai essayé d'utiliser le waitFor(long,TimeUnit)délai d'expiration en cas de problème et j'ai découvert que c'était la lecture du blocage. Ce qui rend la version expirée nécessite un autre thread pour faire la lecture ...
osundblad
5

Il existe plusieurs possibilités:

  1. Vous n'avez pas consommé toute la sortie sur le processus stdout.
  2. Vous n'avez pas consommé toute la sortie sur le processus stderr.
  3. Le processus attend votre contribution et vous ne l'avez pas fourni, ou vous n'avez pas fermé le processus stdin.
  4. Le processus tourne dans une boucle dure.
Marquis de Lorne
la source
5

Comme d'autres l'ont mentionné, vous devez consommer stderr et stdout .

Par rapport aux autres réponses, depuis Java 1.7, c'est encore plus facile. Vous n'avez plus besoin de créer vous-même des threads pour lire stderr et stdout .

Utilisez simplement le ProcessBuilderet utilisez les méthodes redirectOutputen combinaison avec redirectErrorou redirectErrorStream.

String directory = "/working/dir";
File out = new File(...); // File to write stdout to
File err = new File(...); // File to write stderr to
ProcessBuilder builder = new ProcessBuilder();
builder.directory(new File(directory));
builder.command(command);
builder.redirectOutput(out); // Redirect stdout to file
if(out == err) { 
  builder.redirectErrorStream(true); // Combine stderr into stdout
} else { 
  builder.redirectError(err); // Redirect stderr to file
}
Process process = builder.start();
Markus Weninger
la source
2

Pour la même raison, vous pouvez également utiliser inheritIO()pour mapper la console Java avec une console d'application externe comme:

ProcessBuilder pb = new ProcessBuilder(appPath, arguments);

pb.directory(new File(appFile.getParent()));
pb.inheritIO();

Process process = pb.start();
int success = process.waitFor();
Vivek Dhiman
la source
2

Vous devriez essayer de consommer la sortie et l'erreur en même temps

    private void runCMD(String CMD) throws IOException, InterruptedException {
    System.out.println("Standard output: " + CMD);
    Process process = Runtime.getRuntime().exec(CMD);

    // Get input streams
    BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
    BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    String line = "";
    String newLineCharacter = System.getProperty("line.separator");

    boolean isOutReady = false;
    boolean isErrorReady = false;
    boolean isProcessAlive = false;

    boolean isErrorOut = true;
    boolean isErrorError = true;


    System.out.println("Read command ");
    while (process.isAlive()) {
        //Read the stdOut

        do {
            isOutReady = stdInput.ready();
            //System.out.println("OUT READY " + isOutReady);
            isErrorOut = true;
            isErrorError = true;

            if (isOutReady) {
                line = stdInput.readLine();
                isErrorOut = false;
                System.out.println("=====================================================================================" + line + newLineCharacter);
            }
            isErrorReady = stdError.ready();
            //System.out.println("ERROR READY " + isErrorReady);
            if (isErrorReady) {
                line = stdError.readLine();
                isErrorError = false;
                System.out.println("ERROR::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::" + line + newLineCharacter);

            }
            isProcessAlive = process.isAlive();
            //System.out.println("Process Alive " + isProcessAlive);
            if (!isProcessAlive) {
                System.out.println(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Process DIE " + line + newLineCharacter);
                line = null;
                isErrorError = false;
                process.waitFor(1000, TimeUnit.MILLISECONDS);
            }

        } while (line != null);

        //Nothing else to read, lets pause for a bit before trying again
        System.out.println("PROCESS WAIT FOR");
        process.waitFor(100, TimeUnit.MILLISECONDS);
    }
    System.out.println("Command finished");
}
Eduardo Reyes
la source
1

Je pense avoir observé un problème similaire: certains processus ont démarré, semblaient fonctionner correctement mais ne se sont jamais terminés. La fonction waitFor () attendait pour toujours, sauf si j'ai tué le processus dans le Gestionnaire des tâches.
Cependant, tout fonctionnait bien dans les cas où la longueur de la ligne de commande était de 127 caractères ou moins. Si les noms de fichiers longs sont inévitables, vous pouvez utiliser des variables d'environnement, ce qui peut vous permettre de garder la chaîne de ligne de commande courte. Vous pouvez générer un fichier de commandes (à l'aide de FileWriter) dans lequel vous définissez vos variables d'environnement avant d'appeler le programme que vous souhaitez réellement exécuter. Le contenu d'un tel lot pourrait ressembler à:

    set INPUTFILE="C:\Directory 0\Subdirectory 1\AnyFileName"
    set OUTPUTFILE="C:\Directory 2\Subdirectory 3\AnotherFileName"
    set MYPROG="C:\Directory 4\Subdirectory 5\ExecutableFileName.exe"
    %MYPROG% %INPUTFILE% %OUTPUTFILE%

La dernière étape consiste à exécuter ce fichier de commandes à l'aide de Runtime.

Kauz_at_Vancouver
la source
1

Voici une méthode qui fonctionne pour moi. REMARQUE: il existe du code dans cette méthode qui peut ne pas s'appliquer à vous, essayez donc de l'ignorer. Par exemple "logStandardOut (...), git-bash, etc".

private String exeShellCommand(String doCommand, String inDir, boolean ignoreErrors) {
logStandardOut("> %s", doCommand);

ProcessBuilder builder = new ProcessBuilder();
StringBuilder stdOut = new StringBuilder();
StringBuilder stdErr = new StringBuilder();

boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
if (isWindows) {
  String gitBashPathForWindows = "C:\\Program Files\\Git\\bin\\bash";
  builder.command(gitBashPathForWindows, "-c", doCommand);
} else {
  builder.command("bash", "-c", doCommand);
}

//Do we need to change dirs?
if (inDir != null) {
  builder.directory(new File(inDir));
}

//Execute it
Process process = null;
BufferedReader brStdOut;
BufferedReader brStdErr;
try {
  //Start the command line process
  process = builder.start();

  //This hangs on a large file
  // /programming/5483830/process-waitfor-never-returns
  //exitCode = process.waitFor();

  //This will have both StdIn and StdErr
  brStdOut = new BufferedReader(new InputStreamReader(process.getInputStream()));
  brStdErr = new BufferedReader(new InputStreamReader(process.getErrorStream()));

  //Get the process output
  String line = null;
  String newLineCharacter = System.getProperty("line.separator");

  while (process.isAlive()) {
    //Read the stdOut
    while ((line = brStdOut.readLine()) != null) {
      stdOut.append(line + newLineCharacter);
    }

    //Read the stdErr
    while ((line = brStdErr.readLine()) != null) {
      stdErr.append(line + newLineCharacter);
    }

    //Nothing else to read, lets pause for a bit before trying again
    process.waitFor(100, TimeUnit.MILLISECONDS);
  }

  //Read anything left, after the process exited
  while ((line = brStdOut.readLine()) != null) {
    stdOut.append(line + newLineCharacter);
  }

  //Read anything left, after the process exited
  while ((line = brStdErr.readLine()) != null) {
    stdErr.append(line + newLineCharacter);
  }

  //cleanup
  if (brStdOut != null) {
    brStdOut.close();
  }

  if (brStdErr != null) {
    brStdOut.close();
  }

  //Log non-zero exit values
  if (!ignoreErrors && process.exitValue() != 0) {
    String exMsg = String.format("%s%nprocess.exitValue=%s", stdErr, process.exitValue());
    throw new ExecuteCommandException(exMsg);
  }

} catch (ExecuteCommandException e) {
  throw e;
} catch (Exception e) {
  throw new ExecuteCommandException(stdErr.toString(), e);
} finally {
  //Log the results
  logStandardOut(stdOut.toString());
  logStandardError(stdErr.toString());
}

return stdOut.toString();

}

Sagan
la source