This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new 33ac7a87ba9 camel-jbang - Run background should wait (#16418) 33ac7a87ba9 is described below commit 33ac7a87ba9a9889fec130c67bbaa302de252fb3 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Mon Dec 2 13:15:05 2024 +0100 camel-jbang - Run background should wait (#16418) * CAMEL-21463: Camel-jbang while running the process in background unable to access log if the process start fails with errors --- .../modules/ROOT/pages/camel-jbang.adoc | 7 + .../dsl/jbang/core/commands/CamelCommand.java | 4 + .../apache/camel/dsl/jbang/core/commands/Run.java | 167 +++++++++++++++------ 3 files changed, 128 insertions(+), 50 deletions(-) diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc index 741af075c3e..2337bb64633 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc @@ -1775,6 +1775,8 @@ The `run` command allows running Camel in the background with the `--background` Therefore, to see and understand what happens then the management commands cane be used, such as `camel ps`, `camel get`, and `camel log`. +NOTE: Only Camel Main is supported to run in background + [source,bash] ---- $ camel run chuck.yaml --background @@ -1802,6 +1804,11 @@ $ camel stop chuck Shutting down Camel integration (pid: 80093) ---- +When running in background, then Camel JBang (**4.10 onwards**) will now automatic wait for the integration +to startup before returning from the CLI command. This ensures that if there are any startup +errors such as compilation errors or DSL errors etc. then these are captured and printed in the shell. +You can use the option `--background-wait=false` to turn this off. + ==== Starting and Stopping routes The `camel cmd` is intended for executing miscellaneous commands in the running Camel integrations. diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java index de8247f53f4..7667dbefc4b 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java @@ -117,6 +117,10 @@ public abstract class CamelCommand implements Callable<Integer> { return new File(CommandLineHelper.getCamelDir(), pid + "-debug.json"); } + public File getRunBackgroundLogFile(String uuid) { + return new File(CommandLineHelper.getCamelDir(), uuid + "-run.log"); + } + protected Printer printer() { var out = getMain().getOut(); CommandHelper.SetPrinter(out); diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java index 95a5f19d4d1..d14a6e35056 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java @@ -22,6 +22,7 @@ import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -59,7 +60,10 @@ import org.apache.camel.util.CamelCaseOrderedProperties; import org.apache.camel.util.FileUtil; import org.apache.camel.util.IOHelper; import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.StopWatch; import org.apache.camel.util.StringHelper; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; import org.apache.camel.xml.io.util.XmlStreamDetector; import org.apache.camel.xml.io.util.XmlStreamInfo; import picocli.CommandLine; @@ -68,6 +72,7 @@ import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; import static org.apache.camel.dsl.jbang.core.common.CamelCommandHelper.CAMEL_INSTANCE_TYPE; +import static org.apache.camel.dsl.jbang.core.common.CamelCommandHelper.extractState; import static org.apache.camel.dsl.jbang.core.common.GistHelper.asGistSingleUrl; import static org.apache.camel.dsl.jbang.core.common.GistHelper.fetchGistUrls; import static org.apache.camel.dsl.jbang.core.common.GitHubHelper.asGithubSingleUrl; @@ -129,6 +134,10 @@ public class Run extends CamelCommand { @Option(names = { "--background" }, defaultValue = "false", description = "Run in the background") public boolean background; + @Option(names = { "--background-wait" }, defaultValue = "true", + description = "To wait for run in background to startup successfully, before returning") + public boolean backgroundWait = true; + @Option(names = { "--empty" }, defaultValue = "false", description = "Run an empty Camel without loading source files") public boolean empty; @@ -896,6 +905,11 @@ public class Run extends CamelCommand { } protected int runQuarkus() throws Exception { + if (background) { + printer().println("Run Camel Quarkus with --background is not supported"); + return 1; + } + // create temp run dir File runDir = new File(RUN_PLATFORM_DIR, Long.toString(System.currentTimeMillis())); if (!this.background) { @@ -934,14 +948,14 @@ public class Run extends CamelCommand { eq.ignoreLoadingError = this.ignoreLoadingError; eq.lazyBean = this.lazyBean; + printer().println("Running using Quarkus v" + eq.quarkusVersion + " (preparing and downloading files)"); + // run export int exit = eq.export(); if (exit != 0 || this.exportRun) { return exit; } - System.out.println("Running using Quarkus v" + eq.quarkusVersion + " (preparing and downloading files)"); - // run quarkus via maven String mvnw = "/mvnw"; if (FileUtil.isWindows()) { @@ -950,24 +964,19 @@ public class Run extends CamelCommand { ProcessBuilder pb = new ProcessBuilder(); pb.command(runDir + mvnw, "--quiet", "--file", runDir.toString(), "package", "quarkus:" + (dev ? "dev" : "run")); - if (background) { - Process p = pb.start(); - this.spawnPid = p.pid(); - if (!exportRun && !transformRun && !transformMessageRun) { - printer().println("Running Camel Quarkus integration: " + name + " (version: " + eq.quarkusVersion - + ") in background"); - } - return 0; - } else { - pb.inheritIO(); // run in foreground (with IO so logs are visible) - Process p = pb.start(); - this.spawnPid = p.pid(); - // wait for that process to exit as we run in foreground - return p.waitFor(); - } + pb.inheritIO(); // run in foreground (with IO so logs are visible) + Process p = pb.start(); + this.spawnPid = p.pid(); + // wait for that process to exit as we run in foreground + return p.waitFor(); } protected int runSpringBoot() throws Exception { + if (background) { + printer().println("Run Camel Spring Boot with --background is not supported"); + return 1; + } + // create temp run dir File runDir = new File(RUN_PLATFORM_DIR, Long.toString(System.currentTimeMillis())); if (!this.background) { @@ -1010,14 +1019,14 @@ public class Run extends CamelCommand { eq.ignoreLoadingError = this.ignoreLoadingError; eq.lazyBean = this.lazyBean; + printer().println("Running using Spring Boot v" + eq.springBootVersion + " (preparing and downloading files)"); + // run export int exit = eq.export(); if (exit != 0 || exportRun) { return exit; } - System.out.println("Running using Spring Boot v" + eq.springBootVersion + " (preparing and downloading files)"); - // prepare spring-boot for logging to file InputStream is = Run.class.getClassLoader().getResourceAsStream("spring-boot-logback.xml"); eq.safeCopy(is, new File(eq.exportDir + "/src/main/resources/logback.xml")); @@ -1030,21 +1039,11 @@ public class Run extends CamelCommand { } pb.command(runDir + mvnw, "--quiet", "--file", runDir.toString(), "spring-boot:run"); - if (background) { - Process p = pb.start(); - this.spawnPid = p.pid(); - if (!exportRun && !transformRun && !transformMessageRun) { - printer().println("Running Camel Spring Boot integration: " + name + " (version: " + camelVersion - + ") in background"); - } - return 0; - } else { - pb.inheritIO(); // run in foreground (with IO so logs are visible) - Process p = pb.start(); - this.spawnPid = p.pid(); - // wait for that process to exit as we run in foreground - return p.waitFor(); - } + pb.inheritIO(); // run in foreground (with IO so logs are visible) + Process p = pb.start(); + this.spawnPid = p.pid(); + // wait for that process to exit as we run in foreground + return p.waitFor(); } private boolean acceptPropertiesFile(String file) { @@ -1150,6 +1149,7 @@ public class Run extends CamelCommand { openapi = answer.getProperty("camel.jbang.open-api", openapi); download = "true".equals(answer.getProperty("camel.jbang.download", download ? "true" : "false")); background = "true".equals(answer.getProperty("camel.jbang.background", background ? "true" : "false")); + backgroundWait = "true".equals(answer.getProperty("camel.jbang.backgroundWait", backgroundWait ? "true" : "false")); jvmDebugPort = parseJvmDebugPort(answer.getProperty("camel.jbang.jvmDebug", Integer.toString(jvmDebugPort))); camelVersion = answer.getProperty("camel.jbang.camel-version", camelVersion); kameletsVersion = answer.getProperty("camel.jbang.kameletsVersion", kameletsVersion); @@ -1201,6 +1201,9 @@ public class Run extends CamelCommand { if (background) { cmds.remove("--background=true"); cmds.remove("--background"); + cmds.remove("--background-wait"); + cmds.remove("--background-wait=false"); + cmds.remove("--background-wait=true"); } if (camelVersion != null) { cmds.remove("--camel-version=" + camelVersion); @@ -1236,13 +1239,7 @@ public class Run extends CamelCommand { pb.command(jbangArgs); if (background) { - Process p = pb.start(); - this.spawnPid = p.pid(); - if (!exportRun && !transformRun && !transformMessageRun) { - printer().println("Running Camel integration: " + name + " (version: " + camelVersion - + ") in background with PID: " + p.pid()); - } - return 0; + return runBackgroundProcess(pb, "Camel Main"); } else { pb.inheritIO(); // run in foreground (with IO so logs are visible) Process p = pb.start(); @@ -1266,17 +1263,74 @@ public class Run extends CamelCommand { cmds.remove("--background=true"); cmds.remove("--background"); + cmds.remove("--background-wait=false"); + cmds.remove("--background-wait=true"); + cmds.remove("--background-wait"); addCamelCommand(cmds); ProcessBuilder pb = new ProcessBuilder(); pb.command(cmds); + + return runBackgroundProcess(pb, "Camel Main"); + } + + protected int runBackgroundProcess(ProcessBuilder pb, String kind) throws Exception { + File log = null; + if (backgroundWait) { + // store background output in a log file to capture any error on startup + log = getRunBackgroundLogFile("" + new Random().nextLong()); + log.deleteOnExit(); + pb.redirectErrorStream(true); + pb.redirectOutput(log); + } + Process p = pb.start(); this.spawnPid = p.pid(); if (!exportRun && !transformRun && !transformMessageRun) { - printer().println("Running Camel integration: " + name + " in background with PID: " + p.pid()); + printer().println( + "Running " + kind + ": " + name + " in background with PID: " + p.pid() + + (backgroundWait ? " (waiting to startup)" : "")); + } + + int ec = 0; + if (log != null) { + StopWatch watch = new StopWatch(); + int state = 0; // state 5 is running + while (p.isAlive() && watch.taken() < 20000 && state < 5) { + JsonObject root = loadStatus(p.pid()); + if (root != null) { + JsonObject context = (JsonObject) root.get("context"); + if (context != null) { + state = context.getInteger("phase"); + } + } + if (state < 5) { + try { + Thread.sleep(500); + } catch (Exception e) { + // we want to exit + break; + } + } + } + if (!p.isAlive()) { + ec = p.exitValue(); + if (ec != 0) { + printer().println(kind + ": " + name + " startup failure"); + printer().println(""); + String text = IOHelper.loadText(new FileInputStream(log)); + printer().print(text); + } + } else { + printer().println(kind + ": " + name + " (state: " + extractState(state) + ")"); + } } - return 0; + if (log != null) { + log.delete(); + } + + return ec; } protected int runDebug(KameletMain main) throws Exception { @@ -1335,6 +1389,9 @@ public class Run extends CamelCommand { if (background) { cmds.remove("--background=true"); cmds.remove("--background"); + cmds.remove("--background-wait=true"); + cmds.remove("--background-wait=false"); + cmds.remove("--background-wait"); } if (repositories != null) { if (!VersionHelper.isGE(v, "3.18.1")) { @@ -1354,13 +1411,7 @@ public class Run extends CamelCommand { ProcessBuilder pb = new ProcessBuilder(); pb.command(jbangArgs); if (background) { - Process p = pb.start(); - this.spawnPid = p.pid(); - if (!exportRun && !transformRun && !transformMessageRun) { - printer().println("Running Camel integration: " + name + " (version: " + camelVersion - + ") in background with PID: " + p.pid()); - } - return 0; + return runBackgroundProcess(pb, "Camel Main"); } else { pb.inheritIO(); // run in foreground (with IO so logs are visible) Process p = pb.start(); @@ -1873,4 +1924,20 @@ public class Run extends CamelCommand { cmds.add(0, "camel"); } } + + private JsonObject loadStatus(long pid) { + try { + File f = getStatusFile(Long.toString(pid)); + if (f != null) { + FileInputStream fis = new FileInputStream(f); + String text = IOHelper.loadText(fis); + IOHelper.close(fis); + return (JsonObject) Jsoner.deserialize(text); + } + } catch (Exception e) { + // ignore + } + return null; + } + }