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 506c6a6ac90b CAMEL-25561: camel-jbang-mcp - after a failed restart,
camel_run starts the integration again under the name it had (#27700)
506c6a6ac90b is described below
commit 506c6a6ac90b1e4af3de8ec866b204fda5e64ddc
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Oct 12 09:08:43 2026 +0200
CAMEL-25561: camel-jbang-mcp - after a failed restart, camel_run starts the
integration again under the name it had (#27700)
A failed restart leaves the integration stopped, and its answer said
camel_run starts it again; camel_run with the
directory only then started it under the default name (from the first route
file) instead of its own (--name), so
what found it by its name no longer did. The name of an integration whose
restart failed is now kept by its directory,
camel_run without a name uses it (and says so), and the answer of the
failed restart names it.
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../core/commands/ai/IntegrationLauncher.java | 46 +++++++++++++++++++---
.../core/commands/ai/IntegrationLauncherTest.java | 23 +++++++++++
2 files changed, 64 insertions(+), 5 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
index 6a3e98982f63..75d8046ccd8f 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
@@ -58,6 +58,13 @@ public final class IntegrationLauncher {
*/
private static final Map<Path, ProcessHandle> LAUNCHED = new
ConcurrentHashMap<>();
+ /**
+ * CAMEL-25561: the name of an integration whose restart failed, by its
directory. The failed restart leaves it
+ * stopped, and camel_run without a name would start it again under the
default name (from the first file), which
+ * the tools and scripts that know it by its own name do not find.
+ */
+ private static final Map<Path, String> STOPPED_BY_RESTART = new
ConcurrentHashMap<>();
+
private IntegrationLauncher() {
}
@@ -66,19 +73,23 @@ public final class IntegrationLauncher {
*
* @param directory the project directory (the working directory of the
process)
* @param files the source files to run, relative to the directory;
empty runs every route file in it
- * @param name the integration name (the {@code --name} option);
null keeps the default
+ * @param name the integration name (the {@code --name} option);
null keeps the name it had before a failed
+ * restart, else the default
* @param dev dev mode (reload on file changes)
* @param extraArgs further {@code camel run} arguments
* @return status started (with pid, name, log), failed (with
the output) or starting
*/
public static JsonObject run(Path directory, List<String> files, String
name, boolean dev, List<String> extraArgs) {
+ String runName = runName(directory, name);
+ boolean before = runName != null && !runName.equals(name);
List<String> cmd = new ArrayList<>(LauncherHelper.getCamelCommand());
// no files given: the whole directory is the app (camel run
--source-dir), so a file the agent adds later,
// a bean file, a Java class under src/main/java, is part of it and
reloaded in dev mode (CAMEL-24861);
// with files given only those run, for a directory that holds several
apps
boolean sourceDir = files == null || files.isEmpty();
List<String> sources = sourceDir ? sourceFiles(directory) : files;
- cmd.addAll(sourceDir ? sourceDirArguments(name, dev, extraArgs) :
runArguments(sources, name, dev, extraArgs));
+ cmd.addAll(sourceDir
+ ? sourceDirArguments(runName, dev, extraArgs) :
runArguments(sources, runName, dev, extraArgs));
JsonObject result = new JsonObject();
// with --source-dir the guard looks at the top level only; a class
under src/main/java is an app too
if (!sourceDir && sources.isEmpty()) {
@@ -131,8 +142,10 @@ public final class IntegrationLauncher {
if (info != null) {
// the name the other tools find the process by: the one the
caller gave, else the Camel context name
// (the source file name without its extensions); the pid is
in the result as well
- String started = name != null && !name.isBlank()
- ? name : info.contextName() != null &&
!info.contextName().isBlank() ? info.contextName() : info.name();
+ String started = runName != null && !runName.isBlank()
+ ? runName
+ : info.contextName() != null &&
!info.contextName().isBlank() ? info.contextName() : info.name();
+ STOPPED_BY_RESTART.remove(key(directory));
result.put("status", "started");
result.put("pid", pid);
result.put("name", started);
@@ -140,6 +153,7 @@ public final class IntegrationLauncher {
result.put("devMode", dev);
result.put("sourceDir", sourceDir);
result.put("message", "Started " + started + " (pid " + pid +
")"
+ + (before ? ", under the name it had
before its restart failed" : "")
+ (dev
? sourceDir
? "; dev mode watches
the directory: a changed or added file is"
@@ -390,6 +404,26 @@ public final class IntegrationLauncher {
}
}
+ /**
+ * The name camel_run starts a directory under: the one given, else the
one the integration had before its restart
+ * failed (CAMEL-25561), else null for the default name.
+ */
+ static String runName(Path directory, String name) {
+ return name != null && !name.isBlank() ? name :
STOPPED_BY_RESTART.get(key(directory));
+ }
+
+ /**
+ * Keeps the name of an integration whose restart failed and left it
stopped, so camel_run without a name starts it
+ * again under that name; false when the directory or the name is not
known.
+ */
+ static boolean stoppedByRestart(Path directory, String name) {
+ if (directory == null || name == null || name.isBlank()) {
+ return false;
+ }
+ STOPPED_BY_RESTART.put(key(directory), name);
+ return true;
+ }
+
/** The key of a directory in {@link #LAUNCHED}: its real path, the same
through any symlink. */
private static Path key(Path directory) {
try {
@@ -509,8 +543,10 @@ public final class IntegrationLauncher {
Thread.sleep(250);
}
if (!p.isAlive()) {
+ boolean kept = stoppedByRestart(directory, name);
return "Restart of " + (name != null ? name : "pid " + pid) +
" failed with exit code "
- + p.exitValue() + ", so it is stopped now: fix the
cause below, then camel_run starts it again.\n"
+ + p.exitValue() + ", so it is stopped now: fix the
cause below, then camel_run starts it again"
+ + (kept ? " (directory " + directory + ") under the
same name, " + name : "") + ".\n"
+ failureOutput(output);
}
return "Restarting " + (name != null ? name : "pid " + pid) + " as
pid " + p.pid()
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
index b263e6d876d9..daf494767ceb 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
@@ -156,4 +156,27 @@ class IntegrationLauncherTest {
Files.writeString(output, "Cannot find dependency
org.example:missing:1.0\n");
assertThat(IntegrationLauncher.failureOutput(output)).isEqualTo("Cannot find
dependency org.example:missing:1.0");
}
+
+ /**
+ * CAMEL-25561: a failed restart leaves the integration stopped; camel_run
without a name starts it again under the
+ * name it had, not the default one from the first file, so what finds it
by its name still does.
+ */
+ @Test
+ void camelRunKeepsTheNameOfAnIntegrationWhoseRestartFailed(@TempDir Path
dir) throws Exception {
+ Path other = Files.createDirectory(dir.resolve("other"));
+ Path app = Files.createDirectory(dir.resolve("app"));
+ assertThat(IntegrationLauncher.runName(app, null)).isNull();
+ assertThat(IntegrationLauncher.stoppedByRestart(app,
"run-order-generator")).isTrue();
+ assertThat(IntegrationLauncher.runName(app,
null)).isEqualTo("run-order-generator");
+ assertThat(IntegrationLauncher.runName(app, "
")).isEqualTo("run-order-generator");
+ // the same directory written another way
+ assertThat(IntegrationLauncher.runName(other.resolve("../app"),
null)).isEqualTo("run-order-generator");
+ // a name given is the name
+ assertThat(IntegrationLauncher.runName(app,
"order-generator")).isEqualTo("order-generator");
+ assertThat(IntegrationLauncher.runName(other, null)).isNull();
+ // without the directory or the name there is nothing to keep
+ assertThat(IntegrationLauncher.stoppedByRestart(null,
"run-order-generator")).isFalse();
+ assertThat(IntegrationLauncher.stoppedByRestart(other,
null)).isFalse();
+ assertThat(IntegrationLauncher.runName(other, null)).isNull();
+ }
}