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