process.waitFor () nunca retorna

95
Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();
user590444
fonte
Observe que em JAVA 8 há uma sobrecarga de waitFor que permite que você especifique um tempo limite. Essa pode ser uma escolha melhor para evitar um caso em que waitFor nunca retorne.
Ikaso 01 de

Respostas:

145

Existem muitos motivos que waitFor()não voltam.

Mas geralmente se resume ao fato de que o comando executado não é encerrado.

Isso, novamente, pode ter muitas razões.

Um motivo comum é que o processo produz alguma saída e você não lê os fluxos apropriados. Isso significa que o processo é bloqueado assim que o buffer está cheio e espera que seu processo continue lendo. O seu processo, por sua vez, espera que o outro processo termine (o que não acontecerá porque espera pelo seu processo, ...). Esta é uma situação de impasse clássica.

Você precisa ler continuamente o fluxo de entrada dos processos para garantir que ele não bloqueie.

Há um bom artigo que explica todas as armadilhas Runtime.exec()e mostra maneiras de contorná-las, chamado "Quando Runtime.exec () não vai" (sim, o artigo é de 2000, mas o conteúdo ainda se aplica!)

Joachim Sauer
fonte
7
Esta resposta está correta, mas falta um exemplo de código para solucionar o problema. Dê uma olhada na resposta de Peter Lawrey para códigos úteis para descobrir por waitFor()que não retorna.
ForguesR
83

Parece que você não está lendo a saída antes de aguardar sua conclusão. Isso só funciona se a saída não preencher o buffer. Em caso afirmativo, ele esperará até que você leia a saída, catch-22.

Talvez você tenha alguns erros que não está lendo. Isso faria com que o aplicativo parasse e esperasse para sempre. Uma maneira simples de contornar isso é redirecionar os erros para a saída regular.

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
fonte
4
Para informações: ProcessBuilder sendo um construtor real, você pode escrever diretamente ProcessBuilder pb = new ProcessBuilder ("lista de tarefas"). RedirectErrorStream (true);
Jean-François Savard
3
Prefiro usarpb.redirectError(new File("/dev/null"));
Toochka
@Toochka Apenas para informação, redirectErrorestá disponível apenas a partir do Java 1.7
ZhekaKozlov
3
Acredito que deva ser a resposta aceita, troquei meu código por este e funcionou imediatamente.
Gerben Rampaart
43

Também do documento Java:

java.lang

Processo de aula

Como algumas plataformas nativas fornecem apenas um tamanho de buffer limitado para fluxos de entrada e saída padrão, a falha em escrever prontamente o fluxo de entrada ou ler o fluxo de saída do subprocesso pode causar o bloqueio do subprocesso e até mesmo um impasse.

Falha ao limpar o buffer do fluxo de entrada (que canaliza para o fluxo de saída do subprocesso) do Processo pode levar a um bloqueio do subprocesso.

Experimente isto:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();
RollingBoy
fonte
17
Duas advertências: (1) Use ProcessBuilder + redirectErrorStream (true), então você está seguro. Caso contrário, (2) você precisa de um thread para ler Process.getInputStream () e outro para ler Process.getErrorStream (). Acabei de passar cerca de quatro horas descobrindo isso (!), Também conhecido como "The Hard Way".
kevinarpe
1
Você pode usar a funcionalidade Apache Commons Exec para consumir os streams stdout e stderr simultaneamente DefaultExecutor executor = new DefaultExecutor(); PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdoutOS, stderrOS); executor.setStreamHandler(pumpStreamHandler); executor.execute(cmdLine);:, onde stoutOS e stderrOS são BufferedOutputStreams que criei para gravar nos arquivos apropriados.
Matthew Wise
No meu caso, estava chamando um arquivo em lote do Spring que abre internamente um editor. Meu código estava travado mesmo depois de aplicar o código de process.close(). Mas quando eu abro o fluxo de entrada conforme sugerido acima e fecho imediatamente - o problema desaparece. Portanto, no meu caso, a Spring estava esperando pelo sinal de fechamento do stream. Embora eu esteja usando o Java 8 que pode ser fechado automaticamente.
shaILU de
10

Gostaria de acrescentar algo às respostas anteriores, mas como não tenho o representante para comentar, apenas acrescentarei uma resposta. Isso é direcionado para usuários de Android que estão programando em Java.

De acordo com a postagem de RollingBoy, este código quase funcionou para mim:

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

No meu caso, o waitFor () não estava liberando porque eu estava executando uma instrução sem retorno ("ip adddr flush eth0"). Uma maneira fácil de corrigir isso é simplesmente garantir que você sempre retorne algo em seu extrato. Para mim, isso significava executar o seguinte: "ip adddr flush eth0 && echo done". Você pode ler o buffer o dia todo, mas se nada for retornado, sua thread nunca irá liberar sua espera.

Espero que ajude alguém!

Encostas
fonte
2
Quando você não tem um representante para comentar, não contorne isso e comente mesmo assim . Faça desta uma resposta por si só e obtenha a reputação dela!
Processo de Fundo Monica de
Não acho que seja o process.waitFor()que trava, é o reader.readLine()que trava se você não tiver saída. Tentei usar o waitFor(long,TimeUnit)para timeout se algo desse errado e descobri que era o read que travava. O que faz com que a versão expirada exija outro thread para fazer a leitura ...
osundblad
5

Existem várias possibilidades:

  1. Você não consumiu toda a saída do processo stdout .
  2. Você não consumiu toda a saída do processo stderr .
  3. O processo está aguardando uma entrada sua e você não a forneceu ou não fechou o do processo stdin.
  4. O processo está girando em um loop rígido.
Marquês de Lorne
fonte
5

Como outros mencionaram, você deve consumir stderr e stdout .

Em comparação com as outras respostas, desde o Java 1.7 é ainda mais fácil. Você não precisa mais criar threads para ler stderr e stdout .

Basta usar ProcessBuildere usar os métodos redirectOutputem combinação com 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
fonte
2

Pelo mesmo motivo, você também pode usar inheritIO()para mapear o console Java com console de aplicativo externo 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
fonte
2

Você deve tentar consumir a saída e o erro ao mesmo tempo

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

Acho que observei um problema semelhante: alguns processos foram iniciados, pareciam funcionar com sucesso, mas nunca foram concluídos. A função waitFor () estava esperando uma eternidade, exceto se eu encerrasse o processo no Gerenciador de Tarefas.
No entanto, tudo funcionou bem nos casos em que o comprimento da linha de comando era de 127 caracteres ou menos. Se nomes de arquivo longos forem inevitáveis, você pode querer usar variáveis ​​ambientais, o que pode permitir que você mantenha a string da linha de comando curta. Você pode gerar um arquivo em lote (usando FileWriter) no qual define suas variáveis ​​de ambiente antes de chamar o programa que realmente deseja executar. O conteúdo desse lote pode ser semelhante a:

    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%

A última etapa é executar este arquivo em lote usando o Runtime.

Kauz_at_Vancouver
fonte
1

Aqui está um método que funciona para mim. NOTA: Há algum código neste método que pode não se aplicar a você, portanto, tente ignorá-lo. Por exemplo, "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
fonte