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

Reply via email to