process.waitFor () nunca regresa

95
Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();
usuario590444
fuente
Tenga en cuenta que en JAVA 8 hay una sobrecarga de waitFor que le permite especificar el tiempo de espera. Esta podría ser una mejor opción para evitar un caso en el que waitFor nunca regrese.
Ikaso

Respuestas:

145

Hay muchas razones por las que waitFor()no vuelve.

Pero generalmente se reduce al hecho de que el comando ejecutado no se cierra.

Esto, nuevamente, puede tener muchas razones.

Una razón común es que el proceso produce algunos resultados y no lee las secuencias adecuadas. Esto significa que el proceso se bloquea tan pronto como el búfer esté lleno y espera a que su proceso continúe leyendo. Su proceso, a su vez, espera a que termine el otro proceso (que no lo hará porque espera su proceso, ...). Ésta es una situación clásica de estancamiento.

Debe leer continuamente el flujo de entrada de los procesos para asegurarse de que no se bloquee.

Hay un buen artículo que explica todas las trampas Runtime.exec()y muestra formas de evitarlas llamado "Cuando Runtime.exec () no lo hará" (sí, el artículo es de 2000, ¡pero el contenido aún se aplica!)

Joachim Sauer
fuente
7
Esta respuesta es correcta, pero falta una muestra de código para solucionar el problema. Eche un vistazo a la respuesta de Peter Lawrey para obtener un código útil para descubrir por qué waitFor()no regresa.
ForguesR
83

Parece que no está leyendo el resultado antes de esperar a que termine. Esto está bien solo si la salida no llena el búfer. Si lo hace, esperará hasta que lea el resultado, catch-22.

Quizás tenga algunos errores que no está leyendo. Esto haría que la aplicación se detenga y espere a que espere eternamente. Una forma sencilla de evitar esto es redirigir los errores a la salida normal.

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
fuente
4
Para obtener información: ProcessBuilder es un constructor real, puede escribir directamente ProcessBuilder pb = new ProcessBuilder ("lista de tareas"). RedirectErrorStream (true);
Jean-François Savard
3
Prefiero usarpb.redirectError(new File("/dev/null"));
Toochka
@Toochka Solo para información, redirectErrorsolo está disponible desde Java 1.7
ZhekaKozlov
3
Creo que debería ser la respuesta aceptada, reemplacé mi código con esto y funcionó de inmediato.
Gerben Rampaart
43

También de Java doc:

java.lang

Proceso de clase

Debido a que algunas plataformas nativas solo proporcionan un tamaño de búfer limitado para flujos de entrada y salida estándar, si no se escribe rápidamente el flujo de entrada o se lee el flujo de salida del subproceso, el subproceso se puede bloquear e incluso bloquear.

Si no se borra el búfer del flujo de entrada (que conduce al flujo de salida del subproceso) del proceso, se puede producir un bloqueo del subproceso.

Prueba esto:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();
RollingBoy
fuente
17
Dos advertencias: (1) Use ProcessBuilder + redirectErrorStream (verdadero), entonces está seguro. De lo contrario, (2) necesita un hilo para leer de Process.getInputStream () y otro para leer de Process.getErrorStream (). Pasé unas cuatro horas resolviendo esto (!), También conocido como "The Hard Way".
kevinarpe
1
Puede usar la funcionalidad de Apache Commons Exec para consumir los flujos stdout y stderr simultáneamente:, DefaultExecutor executor = new DefaultExecutor(); PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdoutOS, stderrOS); executor.setStreamHandler(pumpStreamHandler); executor.execute(cmdLine);donde stoutOS y stderrOS son BufferedOutputStreamlos que he creado para escribir en los archivos apropiados.
Matthew Wise
En mi caso, estaba llamando a un archivo por lotes de Spring que abre internamente un editor. Mi código se colgaba incluso después de aplicar el código de process.close(). Pero cuando abro el flujo de entrada como se sugirió anteriormente y lo cierro inmediatamente, el problema desaparece. Entonces, en mi caso, Spring estaba esperando la señal de cierre de flujo. Aunque estoy usando Java 8 que se puede cerrar automáticamente.
shaILU
10

Me gustaría agregar algo a las respuestas anteriores, pero como no tengo el representante para comentar, solo agregaré una respuesta. Esto está dirigido a usuarios de Android que están programando en Java.

Según la publicación de RollingBoy, este código casi funcionó para mí:

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

En mi caso, waitFor () no se estaba publicando porque estaba ejecutando una declaración sin retorno ("ip adddr flush eth0"). Una forma sencilla de solucionar este problema es simplemente asegurarse de que siempre devuelve algo en su estado de cuenta. Para mí, eso significó ejecutar lo siguiente: "ip adddr flush eth0 && echo done". Puede leer el búfer todo el día, pero si no se devuelve nada, su hilo nunca liberará su espera.

¡Espero que ayude a alguien!

Pendientes
fuente
2
Cuando no tenga el representante para comentar, no lo evite y comente de todos modos . ¡Haga de esto una respuesta por sí sola y obtenga reputación!
Financia la demanda de Monica
No creo que sea el process.waitFor()que se cuelga, es el reader.readLine()que se cuelga si no tienes salida. Intenté usar el waitFor(long,TimeUnit)tiempo de espera hasta si algo sale mal y descubrí que era la lectura el colgado. Lo que hace que la versión temporizada requiera otro hilo para hacer la lectura ...
osundblad
5

Hay varias posibilidades:

  1. No ha consumido toda la salida del proceso stdout.
  2. No ha consumido toda la salida del proceso stderr.
  3. El proceso está esperando su entrada y no la ha proporcionado, o no ha cerrado el proceso stdin.
  4. El proceso está girando en un bucle duro.
Marqués de Lorne
fuente
5

Como han mencionado otros, debes consumir stderr y stdout .

En comparación con las otras respuestas, desde Java 1.7 es aún más fácil. Ya no tiene que crear subprocesos usted mismo para leer stderr y stdout .

Simplemente use los ProcessBuildermétodos y use redirectOutputen combinación con redirectErroro 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
fuente
2

Por la misma razón, también puede usar inheritIO()para mapear la consola Java con la consola de la aplicación externa como:

ProcessBuilder pb = new ProcessBuilder(appPath, arguments);

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

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

Debería intentar consumir salida y error al mismo tiempo

    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
fuente
1

Creo que observé un problema similar: algunos procesos se iniciaron, parecían ejecutarse correctamente pero nunca se completaron. La función waitFor () estaba esperando para siempre, excepto si maté el proceso en el Administrador de tareas.
Sin embargo, todo funcionó bien en los casos en que la longitud de la línea de comando fuera de 127 caracteres o menos. Si los nombres de archivo largos son inevitables, es posible que desee utilizar variables ambientales, lo que puede permitirle mantener corta la cadena de línea de comandos. Puede generar un archivo por lotes (usando FileWriter) en el que establece sus variables ambientales antes de llamar al programa que realmente desea ejecutar. El contenido de dicho lote podría verse así:

    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%

El último paso es ejecutar este archivo por lotes utilizando Runtime.

Kauz_at_Vancouver
fuente
1

Aquí hay un método que me funciona. NOTA: Hay algún código dentro de este método que puede no aplicarse a usted, así que intente ignorarlo. Por ejemplo, "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
fuente