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 b3d42cb59133 CAMEL-26794: camel-cli-connector - an Error in its
scheduled task no longer stops the status and the CLI actions for good (#29522)
b3d42cb59133 is described below
commit b3d42cb5913331fab0d11608d31504cf2a35da94
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 10 07:46:27 2026 +0100
CAMEL-26794: camel-cli-connector - an Error in its scheduled task no longer
stops the status and the CLI actions for good (#29522)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../apache/camel/cli/connector/ConnectorTasks.java | 60 ++++++++++++++++++++++
.../cli/connector/FileCliConnectorTransport.java | 6 ++-
.../connector/WebSocketCliConnectorTransport.java | 6 +--
.../connector/FileCliConnectorTransportTest.java | 23 ++++++++-
4 files changed, 87 insertions(+), 8 deletions(-)
diff --git
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ConnectorTasks.java
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ConnectorTasks.java
new file mode 100644
index 000000000000..40aa283c3809
--- /dev/null
+++
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ConnectorTasks.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.cli.connector;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The periodic tasks of the connector, which must never end: a task that
throws is not run again by its scheduler, and
+ * these tasks are the only way the Camel CLI reaches the integration (its
status, the actions, stop). An Error too: an
+ * OutOfMemoryError of a moment while the status is collected would otherwise
leave the status as it was then for good,
+ * and the integration out of reach of camel stop.
+ */
+final class ConnectorTasks {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ConnectorTasks.class);
+ // an Error is logged as a warning once: the task runs every second, and
may fail every time
+ private static final AtomicBoolean WARNED = new AtomicBoolean();
+
+ private ConnectorTasks() {
+ }
+
+ /** Runs the task, and ignores what it throws, so the scheduler runs it
again. */
+ static void safely(Runnable task) {
+ try {
+ task.run();
+ } catch (Exception e) {
+ // e and not e.getMessage(): the class name says what it was when
the message is null (a NullPointerException)
+ LOG.debug("Error in Camel CLI connector task due to: {}. This
exception is ignored.", e, e);
+ } catch (Throwable e) {
+ try {
+ if (WARNED.compareAndSet(false, true)) {
+ LOG.warn("Error in Camel CLI connector task due to: {}.
This error is ignored, and the task runs"
+ + " again.",
+ e, e);
+ } else {
+ LOG.debug("Error in Camel CLI connector task due to: {}.
This error is ignored.", e, e);
+ }
+ } catch (Throwable ignored) {
+ // logging fails too when the memory is short; the task runs
again all the same
+ }
+ }
+ }
+}
diff --git
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/FileCliConnectorTransport.java
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/FileCliConnectorTransport.java
index 23499d104a05..3bad67b2eb86 100644
---
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/FileCliConnectorTransport.java
+++
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/FileCliConnectorTransport.java
@@ -133,7 +133,8 @@ public class FileCliConnectorTransport extends
ServiceSupport implements CliConn
debugFile = createLockFile(lockFile.getName() + "-debug.json");
receiveFile = createLockFile(lockFile.getName() + "-receive.json");
timelineFile = createLockFile(lockFile.getName() +
"-timeline.json");
- scheduledFuture = executor.scheduleWithFixedDelay(this::task, 0,
delay, TimeUnit.MILLISECONDS);
+ scheduledFuture = executor.scheduleWithFixedDelay(() ->
ConnectorTasks.safely(this::task), 0, delay,
+ TimeUnit.MILLISECONDS);
LOG.info("Camel CLI connector enabled");
} else {
LOG.warn("Cannot create PID file: {}. This integration cannot be
managed by Camel CLI connector.", getPid());
@@ -155,7 +156,8 @@ public class FileCliConnectorTransport extends
ServiceSupport implements CliConn
boolean done = scheduledFuture == null || scheduledFuture.isDone();
if (done) {
this.delay = delay;
- scheduledFuture = executor.scheduleWithFixedDelay(this::task, 0,
delay, TimeUnit.MILLISECONDS);
+ scheduledFuture = executor.scheduleWithFixedDelay(() ->
ConnectorTasks.safely(this::task), 0, delay,
+ TimeUnit.MILLISECONDS);
}
}
diff --git
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/WebSocketCliConnectorTransport.java
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/WebSocketCliConnectorTransport.java
index 6e1e764f6fdf..1a148b3f2138 100644
---
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/WebSocketCliConnectorTransport.java
+++
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/WebSocketCliConnectorTransport.java
@@ -653,11 +653,7 @@ public class WebSocketCliConnectorTransport extends
ServiceSupport implements Cl
// ---- helpers ----
private static void safely(Runnable task) {
- try {
- task.run();
- } catch (Exception e) {
- LOG.debug("Error in Camel CLI connector task due to: {}. This
exception is ignored.", e.getMessage(), e);
- }
+ ConnectorTasks.safely(task);
}
private void execute(Runnable task) {
diff --git
a/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/FileCliConnectorTransportTest.java
b/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/FileCliConnectorTransportTest.java
index e0cbef902e21..52a953210919 100644
---
a/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/FileCliConnectorTransportTest.java
+++
b/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/FileCliConnectorTransportTest.java
@@ -54,6 +54,7 @@ class FileCliConnectorTransportTest {
private File dir;
private DefaultCamelContext context;
private FileCliConnectorTransport transport;
+ private final EmptySnapshots snapshots = new EmptySnapshots();
@BeforeEach
void start() {
@@ -63,7 +64,7 @@ class FileCliConnectorTransportTest {
context = new DefaultCamelContext();
transport = new FileCliConnectorTransport();
- transport.configure(context, FileCliConnectorTransportTest::dispatch,
new EmptySnapshots(), () -> {
+ transport.configure(context, FileCliConnectorTransportTest::dispatch,
snapshots, () -> {
});
transport.start();
// poll faster than the default second
@@ -89,6 +90,18 @@ class FileCliConnectorTransportTest {
assertThat(file(PID + "-status.json.tmp")).doesNotExist();
}
+ @Test
+ void anErrorWhileCollectingTheStatusDoesNotEndTheTask() {
+ awaitJson(file(PID + "-status.json"));
+ // CAMEL-26794: an OutOfMemoryError of a moment; the scheduler would
never run a task that threw it again, and
+ // the integration would no longer write its status nor answer an
action
+ snapshots.error = new OutOfMemoryError("Java heap space");
+ await().atMost(10, TimeUnit.SECONDS).until(() -> snapshots.error ==
null);
+
+ writeAction(action("result"), "r1");
+ assertThat(awaitJson(file(PID +
"-output-r1.json"))).containsEntry("status", "success");
+ }
+
@Test
void resultIsTheReply() {
writeAction(action("result"), "r1");
@@ -328,8 +341,16 @@ class FileCliConnectorTransportTest {
}
private static final class EmptySnapshots implements CliSnapshotProducer {
+ // thrown once by the next status
+ volatile Error error;
+
@Override
public JsonObject status() {
+ Error e = error;
+ if (e != null) {
+ error = null;
+ throw e;
+ }
return new JsonObject();
}