Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read process output from a new Thread #33438

Merged
merged 1 commit into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 86 additions & 65 deletions core/deployment/src/main/java/io/quarkus/deployment/util/ExecUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

Expand All @@ -16,43 +17,58 @@ public class ExecUtil {

private static final Logger LOG = Logger.getLogger(ExecUtil.class);

private static final Function<InputStream, Runnable> PRINT_OUTPUT = i -> new HandleOutput(i);
private static final Function<InputStream, Runnable> SILENT = i -> new HandleOutput(i, Logger.Level.DEBUG);
private static final Function<InputStream, Runnable> INFO_LOGGING = i -> new HandleOutput(i);
private static final Function<InputStream, Runnable> DEBUG_LOGGING = i -> new HandleOutput(i, Logger.Level.DEBUG);
private static final Function<InputStream, Runnable> SYSTEM_LOGGING = i -> new HandleOutput(i);

private static Function<InputStream, Runnable> SELECTED_LOGGING = INFO_LOGGING;

private static final int PROCESS_CHECK_INTERVAL = 500;

private static class HandleOutput implements Runnable {

private final InputStream is;
private final Logger.Level logLevel;
private final Optional<Logger.Level> logLevel;

HandleOutput(InputStream is) {
this(is, Logger.Level.INFO);
this(is, null);
}

HandleOutput(InputStream is, Logger.Level logLevel) {
this.is = is;
this.logLevel = LOG.isEnabled(logLevel) ? logLevel : null;
this.logLevel = Optional.ofNullable(logLevel);
}

@Override
public void run() {
try (InputStreamReader isr = new InputStreamReader(is);
BufferedReader reader = new BufferedReader(isr)) {

try (InputStreamReader isr = new InputStreamReader(is); BufferedReader reader = new BufferedReader(isr)) {
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
if (logLevel != null) {
LOG.log(logLevel, line);
}
final String l = line;
logLevel.ifPresentOrElse(level -> LOG.log(level, l), () -> System.out.println(l));
}
} catch (IOException e) {
if (logLevel != null) {
LOG.log(logLevel, "Failed to handle output", e);
}
logLevel.ifPresentOrElse(level -> LOG.log(level, "Failed to handle output", e), () -> e.printStackTrace());
}
}
}

public static void useInfoLogging() {
ExecUtil.SELECTED_LOGGING = INFO_LOGGING;
}

public static void useDebugLogging() {
ExecUtil.SELECTED_LOGGING = DEBUG_LOGGING;
}

/**
* There are cases where its preferable to just write to System.out.
* For example from maven-invoker verify scripts, logging can trigger Stack Overflow.
* For such cases its preferable to use this method.
*/
public static void useSystemLogging() {
ExecUtil.SELECTED_LOGGING = SYSTEM_LOGGING;
}

/**
* Execute the specified command from within the current directory.
*
Expand All @@ -77,125 +93,131 @@ public static boolean execWithTimeout(Duration timeout, String command, String..
}

/**
* Execute the specified command from within the current directory and hide the output.
* Execute the specified command from within the specified directory.
*
* @param directory The directory
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean execSilent(String command, String... args) {
return execSilent(new File("."), command, args);
public static boolean exec(File directory, String command, String... args) {
return exec(directory, SELECTED_LOGGING, command, args);
}

/**
* Execute the specified command until the given timeout from within the current directory and hide the output.
* Execute the specified command until the given timeout from within the specified directory.
*
* @param directory The directory
* @param timeout The timeout
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean execSilentWithTimeout(Duration timeout, String command, String... args) {
return execSilentWithTimeout(new File("."), timeout, command, args);
public static boolean execWithTimeout(File directory, Duration timeout, String command, String... args) {
return execWithTimeout(directory, SELECTED_LOGGING, timeout, command, args);
}

/**
* Execute the specified command from within the specified directory.
* The method allows specifying an output filter that processes the command output.
*
* @param directory The directory
* @param outputFilterFunction A {@link Function} that gets an {@link InputStream} and returns an outputFilter.
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean exec(File directory, String command, String... args) {
return exec(directory, PRINT_OUTPUT, command, args);
public static boolean exec(File directory, Function<InputStream, Runnable> outputFilterFunction, String command,
String... args) {
try {
Function<InputStream, Runnable> loggingFunction = outputFilterFunction != null ? outputFilterFunction
: INFO_LOGGING;
Process process = startProcess(directory, command, args);
Thread t = new Thread(loggingFunction.apply(process.getInputStream()));
t.setName("Process stdout");
t.setDaemon(true);
t.start();
process.waitFor();
destroyProcess(process);
return process.exitValue() == 0;
} catch (InterruptedException e) {
return false;
}
}

/**
* Execute the specified command until the given timeout from within the specified directory.
* The method allows specifying an output filter that processes the command output.
*
* @param directory The directory
* @param outputFilterFunction A {@link Function} that gets an {@link InputStream} and returns an outputFilter.
* @param timeout The timeout
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean execWithTimeout(File directory, Duration timeout, String command, String... args) {
return execWithTimeout(directory, PRINT_OUTPUT, timeout, command, args);
public static boolean execWithTimeout(File directory, Function<InputStream, Runnable> outputFilterFunction,
Duration timeout, String command, String... args) {
try {
Function<InputStream, Runnable> loggingFunction = outputFilterFunction != null ? outputFilterFunction
: INFO_LOGGING;
Process process = startProcess(directory, command, args);
Thread t = new Thread(loggingFunction.apply(process.getInputStream()));
t.setName("Process stdout");
t.setDaemon(true);
t.start();
process.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS);
destroyProcess(process);
return process.exitValue() == 0;
} catch (InterruptedException e) {
return false;
}
}

/**
* Execute the specified command from within the specified directory and hide the output.
* Execute the specified command from within the current directory using debug logging.
*
* @param directory The directory
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean execSilent(File directory, String command, String... args) {
return exec(directory, SILENT, command, args);
public static boolean execWithDebugLogging(String command, String... args) {
return execWithDebugLogging(new File("."), command, args);
}

/**
* Execute the specified command until the given timeout from within the specified directory and hide the output.
* Execute the specified command from within the specified directory using debug logging.
*
* @param directory The directory
* @param timeout The timeout
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean execSilentWithTimeout(File directory, Duration timeout, String command, String... args) {
return execWithTimeout(directory, SILENT, timeout, command, args);
public static boolean execWithDebugLogging(File directory, String command, String... args) {
return exec(directory, DEBUG_LOGGING, command, args);
}

/**
* Execute the specified command from within the specified directory.
* The method allows specifying an output filter that processes the command output.
* Execute the specified command from within the current directory using system logging.
*
* @param directory The directory
* @param outputFilterFunction A {@link Function} that gets an {@link InputStream} and returns an outputFilter.
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean exec(File directory, Function<InputStream, Runnable> outputFilterFunction, String command,
String... args) {
try {
Process process = startProcess(directory, command, args);
outputFilterFunction.apply(process.getInputStream()).run();
process.waitFor();
return process.exitValue() == 0;
} catch (InterruptedException e) {
return false;
}
public static boolean execWithSystemLogging(String command, String... args) {
return execWithSystemLogging(new File("."), command, args);
}

/**
* Execute the specified command until the given timeout from within the specified directory.
* The method allows specifying an output filter that processes the command output.
* Execute the specified command from within the specified directory using system logging.
*
* @param directory The directory
* @param outputFilterFunction A {@link Function} that gets an {@link InputStream} and returns an outputFilter.
* @param timeout The timeout
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean execWithTimeout(File directory, Function<InputStream, Runnable> outputFilterFunction,
Duration timeout, String command, String... args) {
try {
Process process = startProcess(directory, command, args);
Thread t = new Thread(outputFilterFunction.apply(process.getInputStream()));
t.setName("Process stdout");
t.setDaemon(true);
t.start();
process.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS);
destroyProcess(process);
return process.exitValue() == 0;
} catch (InterruptedException e) {
return false;
}
public static boolean execWithSystemLogging(File directory, String command, String... args) {
return exec(directory, SYSTEM_LOGGING, command, args);
}

/**
Expand Down Expand Up @@ -243,5 +265,4 @@ public static void destroyProcess(Process process) {
process.destroyForcibly();
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import io.quarkus.deployment.util.ExecUtil

ExecUtil.useSystemLogging() //prevents stack overflow issues

try {
ExecUtil.exec("docker", "version", "--format", "'{{.Server.Version}}'")
} catch (Exception ignored) {
Expand All @@ -9,4 +11,4 @@ try {

String group = System.getProperty("user.name")
assert ExecUtil.exec("docker", "images", group + "/container-build-docker")
assert ExecUtil.exec("docker", "rmi", group + "/container-build-docker:0.1-SNAPSHOT")
assert ExecUtil.exec("docker", "rmi", group + "/container-build-docker:0.1-SNAPSHOT")
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import io.quarkus.deployment.util.ExecUtil

import java.util.concurrent.ThreadLocalRandom

ExecUtil.useSystemLogging() //prevents stack overflow issues
try {
ExecUtil.exec("docker", "version", "--format", "'{{.Server.Version}}'")
} catch (Exception ignored) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import io.quarkus.deployment.util.ExecUtil

import java.util.concurrent.ThreadLocalRandom

ExecUtil.useSystemLogging() //prevents stack overflow issues

try {
ExecUtil.exec("docker", "version", "--format", "'{{.Server.Version}}'")
} catch (Exception ignored) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

ExecUtil.useSystemLogging() //prevents stack overflow issues
try {
ExecUtil.exec("docker", "version", "--format", "'{{.Server.Version}}'")
} catch (Exception ignored) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import io.quarkus.deployment.util.ExecUtil

ExecUtil.useSystemLogging() //prevents stack overflow issues
try {
ExecUtil.exec("docker", "version", "--format", "'{{.Server.Version}}'")
} catch (Exception ignored) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import io.quarkus.deployment.util.ExecUtil

ExecUtil.useSystemLogging() //prevents stack overflow issues
try {
ExecUtil.exec("docker", "version", "--format", "'{{.Server.Version}}'")
} catch (Exception ignored) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import io.quarkus.deployment.util.ExecUtil

ExecUtil.useSystemLogging() //prevents stack overflow issues
try {
ExecUtil.exec("docker", "version", "--format", "'{{.Server.Version}}'")
} catch (Exception ignored) {
println "Docker not found"
return
}

assert ExecUtil.exec("docker", "run", "--rm", "-p", "5000:5000", "-d", "--name", "registry" ,"registry:2");
assert ExecUtil.exec("docker", "run", "--rm", "-p", "5000:5000", "-d", "--name", "registry" ,"registry:2");
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import io.quarkus.deployment.util.ExecUtil
import static io.restassured.RestAssured.get
import static org.hamcrest.Matchers.containsString

ExecUtil.useSystemLogging() //prevents stack overflow issues
try {
ExecUtil.exec("docker", "version", "--format", "'{{.Server.Version}}'")
} catch (Exception ignored) {
Expand All @@ -14,4 +15,4 @@ get("http://localhost:5000/v2/_catalog")
.then()
.body(containsString("container-image-push"))

assert ExecUtil.exec("docker", "stop", "registry")
assert ExecUtil.exec("docker", "stop", "registry")