process.waitFor () nunca retorna


95
Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();

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


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

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

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!


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.

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

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

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

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.


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

}


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.