Skip to content

Commit

Permalink
Merge pull request #33438 from iocanel/container-invoker-fixes
Browse files Browse the repository at this point in the history
Read process output from a new Thread
  • Loading branch information
geoand authored May 18, 2023
2 parents 93770e6 + 5c237be commit f2a18a8
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 68 deletions.
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")

0 comments on commit f2a18a8

Please sign in to comment.