public static CommandOutput executeCommand()

in apm-agent-common/src/main/java/co/elastic/apm/agent/common/util/ProcessExecutionUtil.java [42:127]


    public static CommandOutput executeCommand(List<String> command, long timeoutMillis) {
        ProcessBuilder buildTheProcess = new ProcessBuilder(command);
        // merge stdout and stderr so we only have to read one stream
        buildTheProcess.redirectErrorStream(true);
        Process spawnedProcess = null;
        int exitValue = -1;
        Throwable exception = null;
        StringBuilder commandOutput = new StringBuilder();
        long duration = 0L;
        try {
            spawnedProcess = buildTheProcess.start();

            long start = System.currentTimeMillis();
            boolean isAlive = true;
            byte[] buffer = new byte[4 * 1000];
            try (InputStream in = spawnedProcess.getInputStream()) {
                // stop trying if the time elapsed exceeds the timeout
                while (isAlive && duration < timeoutMillis) {
                    while (in.available() > 0) {
                        int lengthRead = in.read(buffer, 0, buffer.length);
                        commandOutput.append(new String(buffer, 0, lengthRead));
                    }
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        //no action, just means the next loop iteration checking
                        //for timeout or process dead, is earlier
                    }
                    duration = System.currentTimeMillis() - start;
                    // if it's not alive but there is still readable input, then continue reading
                    isAlive = processIsAlive(spawnedProcess) || in.available() > 0;
                }
                //would like to call waitFor(TIMEOUT) here, but that is 1.8+
                //so pause for a bit, and just ensure that the output buffers are empty
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    //no action, just means the exit is earlier
                }

                if (duration >= timeoutMillis) {
                    spawnedProcess.destroy();
                    throw new TimeoutException(String.format(
                        "Execution of %s exceeded the specified timeout of %sms. Process killed.",
                        cmdAsString(command),
                        timeoutMillis)
                    );
                }

                //handle edge case where process terminated but still has unread IO
                //and in.available() could have returned 0 from IO buffering
                while (in.available() > 0) {
                    int lengthRead = in.read(buffer, 0, buffer.length);
                    commandOutput.append(new String(buffer, 0, lengthRead));
                }
            }

        } catch (Throwable e1) {
            exception = e1;
        } finally {
            // Cleanup as well as we can
            if (spawnedProcess != null && processIsAlive(spawnedProcess)) {
                spawnedProcess.destroy();
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    //no action, just means the next loop iteration is earlier
                }
                // when no longer need 1.7 compatibility, add these lines
                // try{Thread.sleep(50);}catch (InterruptedException e) {}
                // if (p.isAlive()) {
                // p.destroyForcibly();
                // }
            }
            if (spawnedProcess != null) {
                try {
                    exitValue = spawnedProcess.exitValue();
                } catch (IllegalThreadStateException e2) {
                    if (exception == null) {
                        exception = e2;
                    }
                }
            }
        }
        return new CommandOutput(commandOutput, exitValue, exception, duration);
    }