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 f1192845d52c CAMEL-25502: camel-core - RouteStoppingException for an
exchange cut off by a route or CamelContext stop (#27634)
f1192845d52c is described below
commit f1192845d52c936e91bb05ec4bc08d6ac3181414
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Oct 10 00:09:44 2026 +0200
CAMEL-25502: camel-core - RouteStoppingException for an exchange cut off by
a route or CamelContext stop (#27634)
An exchange cut off because its route or the CamelContext is being stopped
(a route stop, a
dev-mode reload, the graceful shutdown timing out) failed with a plain
RejectedExecutionException,
which could not be told apart from a real rejection (a full thread pool or
queue): CAMEL-25484
recognised it by its message, and the error registry recorded it as an
error.
- org.apache.camel.RouteStoppingException extends
RejectedExecutionException, so onException and
catch blocks for RejectedExecutionException still match
- set where an in-flight exchange is cut off by a stop: the error handler,
a direct producer
interrupted while waiting for a consumer, Delay, Throttle, Threads,
Failover, Resequencer, and the
internal processor on a forced shutdown; real rejections stay
RejectedExecutionException
- the error handler logs any such cut-off as one WARN line by its type
(also a CamelContext stop)
- the error registry leaves it out, unless
camel.errorRegistry.includeRouteStopping=true; the option
is shown in JMX and the errors dev console
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../apache/camel/catalog/dev-consoles-openapi.json | 7 ++-
.../apache/camel/catalog/dev-consoles/errors.json | 7 ++-
.../org/apache/camel/catalog/docs/main.adoc | 3 +-
.../main/camel-main-configuration-metadata.json | 1 +
.../camel/component/direct/DirectProducer.java | 12 ++---
.../org/apache/camel/RouteStoppingException.java | 39 +++++++++++++++
.../java/org/apache/camel/spi/ErrorRegistry.java | 14 ++++++
.../camel/impl/engine/CamelInternalProcessor.java | 4 +-
.../camel/impl/engine/DefaultErrorRegistry.java | 17 +++++++
.../impl/engine/SharedCamelInternalProcessor.java | 4 +-
.../org/apache/camel/dev-console/errors.json | 7 ++-
.../camel/impl/console/ErrorRegistryConsole.java | 4 +-
.../impl/console/ErrorRegistryConsoleTest.java | 2 +
.../apache/camel/processor/AbstractThrottler.java | 4 +-
.../processor/ConcurrentRequestsThrottler.java | 3 +-
.../camel/processor/DelayProcessorSupport.java | 7 +--
.../apache/camel/processor/ThreadsProcessor.java | 5 +-
.../camel/processor/TotalRequestsThrottler.java | 3 +-
.../errorhandler/RedeliveryErrorHandler.java | 19 ++++----
.../loadbalancer/FailOverLoadBalancer.java | 4 +-
.../processor/resequencer/ResequencerEngine.java | 7 +--
.../camel/impl/engine/ShutdownWaitingAtTest.java | 6 +--
.../errorhandler/RouteStopCutOffLogTest.java | 39 ++++++++++++++-
...rRegistryConfigurationPropertiesConfigurer.java | 7 +++
.../camel-main-configuration-metadata.json | 1 +
core/camel-main/src/main/docs/main.adoc | 3 +-
.../org/apache/camel/main/BaseMainSupport.java | 1 +
.../main/ErrorRegistryConfigurationProperties.java | 25 ++++++++++
.../ErrorRegistryConfigurationPropertiesTest.java | 55 ++++++++++++++++++++++
.../mbean/ManagedErrorRegistryMBean.java | 6 +++
.../management/mbean/ManagedErrorRegistry.java | 10 ++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 22 +++++++--
.../modules/ROOT/pages/error-registry.adoc | 1 +
33 files changed, 304 insertions(+), 45 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
index c413cba69c2b..bc39ea57c610 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
@@ -1735,6 +1735,10 @@
"type": "string",
"description": "The time to live for entries"
},
+
"includeRouteStopping": {
+
"type": "boolean",
+
"description": "Whether exchanges cut off by a route stop, a route
reload or a CamelContext stop are also captured"
+
},
"errors": {
"type": "array",
"items": {
@@ -1747,7 +1751,8 @@
"required": [
"enabled",
"size",
-
"maximumEntries"
+
"maximumEntries",
+
"includeRouteStopping"
]
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/errors.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/errors.json
index 8b44a9600807..de288992e4ec 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/errors.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/errors.json
@@ -118,6 +118,10 @@
"type": "string",
"description": "The time to live for entries"
},
+ "includeRouteStopping": {
+ "type": "boolean",
+ "description": "Whether exchanges cut off by a route stop, a route
reload or a CamelContext stop are also captured"
+ },
"errors": {
"type": "array",
"items": {
@@ -130,7 +134,8 @@
"required": [
"enabled",
"size",
- "maximumEntries"
+ "maximumEntries",
+ "includeRouteStopping"
]
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
index 5e3ffea4d8df..4d300fd6eb9c 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
@@ -748,7 +748,7 @@ The camel.lra supports 5 options, which are listed below.
=== Camel Error Registry configurations
-The camel.errorRegistry supports 9 options, which are listed below.
+The camel.errorRegistry supports 10 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -759,6 +759,7 @@ The camel.errorRegistry supports 9 options, which are
listed below.
| `camel.errorRegistry.enabled` | Whether the error registry is enabled to
capture errors during message routing. | false | boolean
| `camel.errorRegistry.includeExchangeProperties` | Whether to include the
exchange properties in the captured error data. | true | boolean
| `camel.errorRegistry.includeExchangeVariables` | Whether to include the
exchange variables in the captured error data. | true | boolean
+| `camel.errorRegistry.includeRouteStopping` | Whether to also capture an
exchange cut off by a route stop, a route reload or a CamelContext stop
(RouteStoppingException). Nothing in the route failed, but a consumer that does
not roll back may lose the message, so enable it to see how often stops cut off
work. | false | boolean
| `camel.errorRegistry.maximumEntries` | The maximum number of error entries
to keep in the registry. When the limit is exceeded, the oldest entries are
evicted. | 100 | int
| `camel.errorRegistry.maximumEntriesPerKind` | The maximum number of error
entries of the same kind (same route, node and exception type) to keep, so a
storm of one failure does not evict all the other errors. The counter of that
kind keeps rising even when its older entries are evicted. | 3 | int
| `camel.errorRegistry.timeToLiveSeconds` | The time-to-live in seconds for
error entries. Entries older than this are evicted. The default value is 0
(disabled). | 0 | int
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index 8d59e0dc9e73..a1e0eca1dfa0 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -266,6 +266,7 @@
{ "name": "camel.errorRegistry.enabled", "required": false, "description":
"Whether the error registry is enabled to capture errors during message
routing.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.errorRegistry.includeExchangeProperties", "required":
false, "description": "Whether to include the exchange properties in the
captured error data.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": true, "secret": false },
{ "name": "camel.errorRegistry.includeExchangeVariables", "required":
false, "description": "Whether to include the exchange variables in the
captured error data.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": true, "secret": false },
+ { "name": "camel.errorRegistry.includeRouteStopping", "required": false,
"description": "Whether to also capture an exchange cut off by a route stop, a
route reload or a CamelContext stop (RouteStoppingException). Nothing in the
route failed, but a consumer that does not roll back may lose the message, so
enable it to see how often stops cut off work.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue" [...]
{ "name": "camel.errorRegistry.maximumEntries", "required": false,
"description": "The maximum number of error entries to keep in the registry.
When the limit is exceeded, the oldest entries are evicted.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"integer", "javaType": "int", "defaultValue": 100, "secret": false },
{ "name": "camel.errorRegistry.maximumEntriesPerKind", "required": false,
"description": "The maximum number of error entries of the same kind (same
route, node and exception type) to keep, so a storm of one failure does not
evict all the other errors. The counter of that kind keeps rising even when its
older entries are evicted.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"integer", "javaType": "int", "defaultValue": 3, "secret": false },
{ "name": "camel.errorRegistry.timeToLiveSeconds", "required": false,
"description": "The time-to-live in seconds for error entries. Entries older
than this are evicted. The default value is 0 (disabled).", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"integer", "javaType": "int", "defaultValue": 0, "secret": false },
diff --git
a/components/camel-direct/src/main/java/org/apache/camel/component/direct/DirectProducer.java
b/components/camel-direct/src/main/java/org/apache/camel/component/direct/DirectProducer.java
index 4f7adf8e5f1c..12be998da2f0 100644
---
a/components/camel-direct/src/main/java/org/apache/camel/component/direct/DirectProducer.java
+++
b/components/camel-direct/src/main/java/org/apache/camel/component/direct/DirectProducer.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.direct;
import org.apache.camel.AsyncCallback;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePropertyKey;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.support.DefaultAsyncProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -104,13 +105,12 @@ public class DirectProducer extends DefaultAsyncProducer {
LOG.info("Interrupted while waiting for a consumer on {}: the
route is being stopped or reloaded",
endpoint.getEndpointUri());
Thread.currentThread().interrupt();
- DirectConsumerNotAvailableException cause = new
DirectConsumerNotAvailableException(
+ // a cut-off by a route stop, not a failure (CAMEL-25502); the
interruption is kept as the cause, so
+ // onException(InterruptedException.class) still matches
+ exchange.setException(new RouteStoppingException(
"No consumers available on endpoint: " + endpoint
-
+ " (interrupted while waiting for one, as the route is being
stopped or reloaded)",
- exchange);
- // keep the interruption as the cause, so
onException(InterruptedException.class) still matches
- cause.initCause(e);
- exchange.setException(cause);
+ + " (interrupted
while waiting for one, as the route is being stopped or reloaded)",
+ e));
// stay marked as interrupted, as
setException(InterruptedException) did, so the error handler stops
// routing instead of handling a failure (onException, redelivery,
dead letter channel, the ERROR log)
exchange.getExchangeExtension().setInterrupted(true);
diff --git
a/core/camel-api/src/main/java/org/apache/camel/RouteStoppingException.java
b/core/camel-api/src/main/java/org/apache/camel/RouteStoppingException.java
new file mode 100644
index 000000000000..19b4507c72ca
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/RouteStoppingException.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * An exchange cut off because its route, or the {@link CamelContext}, is
being stopped while the exchange is in flight:
+ * a route stop, a route reload in dev mode, or the graceful shutdown timing
out. Nothing in the route failed; a
+ * consumer that rolls back, such as file, delivers the message again.
+ * <p/>
+ * It extends {@link RejectedExecutionException}, which Camel used for this
before, so an
+ * {@code onException(RejectedExecutionException.class)} or a catch of it
still matches. A real rejection (a thread pool
+ * or a queue that is full) stays a plain {@link RejectedExecutionException}.
+ */
+public class RouteStoppingException extends RejectedExecutionException {
+
+ public RouteStoppingException(String message) {
+ super(message);
+ }
+
+ public RouteStoppingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git
a/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistry.java
b/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistry.java
index 17b505be71b4..ca3dd80f6801 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistry.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistry.java
@@ -165,4 +165,18 @@ public interface ErrorRegistry extends ErrorRegistryView,
StaticService {
* This is by default enabled.
*/
void setIncludeExchangeVariables(boolean includeExchangeVariables);
+
+ /**
+ * Whether to also capture an exchange cut off by a route stop, a route
reload or a CamelContext stop (a
+ * {@link org.apache.camel.RouteStoppingException}). Nothing in the route
failed, but a consumer that does not roll
+ * back may lose the message, so enable it to see how often stops cut off
work.
+ */
+ boolean isIncludeRouteStopping();
+
+ /**
+ * Sets whether to also capture an exchange cut off by a route stop, a
route reload or a CamelContext stop.
+ * <p/>
+ * This is by default disabled.
+ */
+ void setIncludeRouteStopping(boolean includeRouteStopping);
}
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
index bbaa2efb053b..1a22992d7ac1 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
@@ -23,7 +23,6 @@ import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.RejectedExecutionException;
import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
@@ -39,6 +38,7 @@ import org.apache.camel.NonManagedService;
import org.apache.camel.Ordered;
import org.apache.camel.Processor;
import org.apache.camel.Route;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.StatefulService;
import org.apache.camel.impl.debugger.BacklogTracer;
import org.apache.camel.impl.debugger.DefaultBacklogTracerEventMessage;
@@ -370,7 +370,7 @@ public class CamelInternalProcessor extends
DelegateAsyncProcessor implements In
+ exchange;
LOG.debug(msg);
if (exchange.getException() == null) {
- exchange.setException(new RejectedExecutionException(msg));
+ exchange.setException(new RouteStoppingException(msg));
}
// force shutdown so we should not continue
originalCallback.done(true);
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
index c47a53cdfcbe..f21546f64038 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
@@ -31,6 +31,7 @@ import java.util.concurrent.atomic.AtomicLong;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePropertyKey;
import org.apache.camel.MessageHistory;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.spi.BacklogErrorEventMessage;
import org.apache.camel.spi.CamelEvent;
import org.apache.camel.spi.ErrorRegistry;
@@ -38,6 +39,7 @@ import org.apache.camel.spi.ErrorRegistryView;
import org.apache.camel.support.EventNotifierSupport;
import org.apache.camel.support.LoggerHelper;
import org.apache.camel.support.MessageHelper;
+import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.json.JsonObject;
import org.apache.camel.util.json.Jsonable;
import org.apache.camel.util.json.Jsoner;
@@ -61,6 +63,7 @@ public class DefaultErrorRegistry extends
EventNotifierSupport implements ErrorR
private volatile boolean bodyIncludeFiles = true;
private volatile boolean includeExchangeProperties = true;
private volatile boolean includeExchangeVariables = true;
+ private volatile boolean includeRouteStopping;
public DefaultErrorRegistry() {
setIgnoreCamelContextEvents(true);
@@ -120,6 +123,10 @@ public class DefaultErrorRegistry extends
EventNotifierSupport implements ErrorR
if (exception == null) {
return;
}
+ if (!includeRouteStopping &&
ObjectHelper.getException(RouteStoppingException.class, exception) != null) {
+ // cut off by a route stop, a reload or a CamelContext stop:
nothing in the route failed (CAMEL-25502)
+ return;
+ }
// for correlated copy exchanges (e.g., created by circuit breaker,
multicast, splitter)
// use the original exchange ID so the error is tracked under the
parent exchange
@@ -517,6 +524,16 @@ public class DefaultErrorRegistry extends
EventNotifierSupport implements ErrorR
this.includeExchangeVariables = includeExchangeVariables;
}
+ @Override
+ public boolean isIncludeRouteStopping() {
+ return includeRouteStopping;
+ }
+
+ @Override
+ public void setIncludeRouteStopping(boolean includeRouteStopping) {
+ this.includeRouteStopping = includeRouteStopping;
+ }
+
/**
* A filtered view over entries for a specific route.
*/
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SharedCamelInternalProcessor.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SharedCamelInternalProcessor.java
index 5ab5b8ddf66d..bdf7a06db2bf 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SharedCamelInternalProcessor.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SharedCamelInternalProcessor.java
@@ -18,13 +18,13 @@ package org.apache.camel.impl.engine;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.RejectedExecutionException;
import org.apache.camel.AsyncCallback;
import org.apache.camel.AsyncProcessor;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.spi.AsyncProcessorAwaitManager;
import org.apache.camel.spi.CamelInternalProcessorAdvice;
import org.apache.camel.spi.ReactiveExecutor;
@@ -246,7 +246,7 @@ public class SharedCamelInternalProcessor implements
SharedInternalProcessor {
+ exchange;
LOG.debug(msg);
if (exchange.getException() == null) {
- exchange.setException(new RejectedExecutionException(msg));
+ exchange.setException(new RouteStoppingException(msg));
}
}
return false;
diff --git
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/errors.json
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/errors.json
index 8b44a9600807..de288992e4ec 100644
---
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/errors.json
+++
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/errors.json
@@ -118,6 +118,10 @@
"type": "string",
"description": "The time to live for entries"
},
+ "includeRouteStopping": {
+ "type": "boolean",
+ "description": "Whether exchanges cut off by a route stop, a route
reload or a CamelContext stop are also captured"
+ },
"errors": {
"type": "array",
"items": {
@@ -130,7 +134,8 @@
"required": [
"enabled",
"size",
- "maximumEntries"
+ "maximumEntries",
+ "includeRouteStopping"
]
}
}
diff --git
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ErrorRegistryConsole.java
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ErrorRegistryConsole.java
index 583853dcc37c..8e45d2606fe4 100644
---
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ErrorRegistryConsole.java
+++
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ErrorRegistryConsole.java
@@ -38,6 +38,7 @@ public class ErrorRegistryConsole extends AbstractDevConsole {
@Metadata(description = "Number of captured errors") int size,
@Metadata(description = "The maximum number of entries retained")
int maximumEntries,
@Metadata(description = "The time to live for entries") String
timeToLive,
+ @Metadata(description = "Whether exchanges cut off by a route
stop, a route reload or a CamelContext stop are also captured") boolean
includeRouteStopping,
@Metadata(description = "The captured errors; shape depends on the
underlying message implementation") List<Map<String, Object>> errors) {
}
@@ -76,6 +77,7 @@ public class ErrorRegistryConsole extends AbstractDevConsole {
ErrorRegistry registry = getCamelContext().getErrorRegistry();
sb.append(String.format("%n Enabled: %s", registry.isEnabled()));
sb.append(String.format("%n Size: %s", registry.size()));
+ sb.append(String.format("%n Include Route Stopping: %s",
registry.isIncludeRouteStopping()));
List<BacklogErrorEventMessage> entries = fetchAndFilter(registry,
options);
@@ -133,7 +135,7 @@ public class ErrorRegistryConsole extends
AbstractDevConsole {
Response response = new Response(
registry.isEnabled(), registry.size(),
registry.getMaximumEntries(), registry.getTimeToLive().toString(),
- errors);
+ registry.isIncludeRouteStopping(), errors);
return JsonRecordSupport.toJsonObject(response);
}
diff --git
a/core/camel-console/src/test/java/org/apache/camel/impl/console/ErrorRegistryConsoleTest.java
b/core/camel-console/src/test/java/org/apache/camel/impl/console/ErrorRegistryConsoleTest.java
index 73ae8a778742..c1811b0a81f5 100644
---
a/core/camel-console/src/test/java/org/apache/camel/impl/console/ErrorRegistryConsoleTest.java
+++
b/core/camel-console/src/test/java/org/apache/camel/impl/console/ErrorRegistryConsoleTest.java
@@ -21,6 +21,7 @@ import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class ErrorRegistryConsoleTest extends AbstractDevConsoleTest {
@@ -33,6 +34,7 @@ public class ErrorRegistryConsoleTest extends
AbstractDevConsoleTest {
assertNotNull(out.getBoolean("enabled"));
assertNotNull(out.getInteger("size"));
assertNotNull(out.getInteger("maximumEntries"));
+ assertEquals(Boolean.FALSE, out.getBoolean("includeRouteStopping"));
assertNotNull(out.getString("timeToLive"));
JsonArray errors = out.getJsonArray("errors");
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/AbstractThrottler.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/AbstractThrottler.java
index 725e51122aa8..3a7bb0b973cd 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/AbstractThrottler.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/AbstractThrottler.java
@@ -16,13 +16,13 @@
*/
package org.apache.camel.processor;
-import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.Traceable;
import org.apache.camel.spi.IdAware;
import org.apache.camel.spi.RouteIdAware;
@@ -70,7 +70,7 @@ public abstract class AbstractThrottler extends
BaseProcessorSupport
String msg = "Run not allowed as ShutdownStrategy is forcing
shutting down, will reject executing exchange: "
+ exchange;
LOG.debug(msg);
- exchange.setException(new RejectedExecutionException(msg, e));
+ exchange.setException(new RouteStoppingException(msg, e));
} else {
exchange.setException(e);
}
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/ConcurrentRequestsThrottler.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/ConcurrentRequestsThrottler.java
index afd90ce238e0..f89805341521 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/ConcurrentRequestsThrottler.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/ConcurrentRequestsThrottler.java
@@ -32,6 +32,7 @@ import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.RuntimeExchangeException;
import org.apache.camel.spi.Synchronization;
import org.apache.camel.util.ObjectHelper;
@@ -89,7 +90,7 @@ public class ConcurrentRequestsThrottler extends
AbstractThrottler {
try {
if (!isRunAllowed()) {
- throw new RejectedExecutionException("Run is not allowed");
+ throw new RouteStoppingException("Run is not allowed");
}
return doProcess(exchange, callback, state, queuedStart, doneSync);
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/DelayProcessorSupport.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/DelayProcessorSupport.java
index 0854e4c05d53..7193c9ab1a72 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/DelayProcessorSupport.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/DelayProcessorSupport.java
@@ -25,6 +25,7 @@ import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -63,7 +64,7 @@ public abstract class DelayProcessorSupport extends
BaseDelegateProcessorSupport
LOG.trace("Delayed task woke up and continues routing for
exchangeId: {}", exchange.getExchangeId());
}
if (!isRunAllowed()) {
- exchange.setException(new RejectedExecutionException("Run is
not allowed"));
+ exchange.setException(new RouteStoppingException("Run is not
allowed"));
}
// process the exchange now that we woke up
@@ -131,7 +132,7 @@ public abstract class DelayProcessorSupport extends
BaseDelegateProcessorSupport
delayedCount.decrementAndGet();
if (isCallerRunsWhenRejected()) {
if (!isRunAllowed()) {
- exchange.setException(new
RejectedExecutionException());
+ exchange.setException(new RouteStoppingException("Run
is not allowed"));
} else {
if (LOG.isDebugEnabled()) {
LOG.debug(
@@ -161,7 +162,7 @@ public abstract class DelayProcessorSupport extends
BaseDelegateProcessorSupport
@Override
public boolean process(Exchange exchange, AsyncCallback callback) {
if (!isRunAllowed()) {
- exchange.setException(new RejectedExecutionException("Run is not
allowed"));
+ exchange.setException(new RouteStoppingException("Run is not
allowed"));
callback.done(true);
return true;
}
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/ThreadsProcessor.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/ThreadsProcessor.java
index a5a7e820475b..1629d4859a46 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/ThreadsProcessor.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/ThreadsProcessor.java
@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.spi.IdAware;
import org.apache.camel.spi.RouteIdAware;
import org.apache.camel.spi.StepIdAware;
@@ -78,7 +79,7 @@ public class ThreadsProcessor extends BaseProcessorSupport
implements IdAware, R
public void run() {
LOG.trace("Continue routing exchange {}", exchange);
if (shutdown.get()) {
- exchange.setException(new
RejectedExecutionException("ThreadsProcessor is not running."));
+ exchange.setException(new
RouteStoppingException("ThreadsProcessor is not running."));
}
callback.done(done);
}
@@ -89,7 +90,7 @@ public class ThreadsProcessor extends BaseProcessorSupport
implements IdAware, R
exchange.setException(new RejectedExecutionException());
LOG.trace("Rejected routing exchange {}", exchange);
if (shutdown.get()) {
- exchange.setException(new
RejectedExecutionException("ThreadsProcessor is not running."));
+ exchange.setException(new
RouteStoppingException("ThreadsProcessor is not running."));
}
callback.done(done);
}
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/TotalRequestsThrottler.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/TotalRequestsThrottler.java
index a7f4708bad72..06363fa377cc 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/TotalRequestsThrottler.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/TotalRequestsThrottler.java
@@ -32,6 +32,7 @@ import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.RuntimeExchangeException;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
@@ -88,7 +89,7 @@ public class TotalRequestsThrottler extends AbstractThrottler
{
try {
if (!isRunAllowed()) {
- throw new RejectedExecutionException("Run is not allowed");
+ throw new RouteStoppingException("Run is not allowed");
}
String key = DEFAULT_KEY;
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
index e7494304853c..f0281e9857b6 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
@@ -35,6 +35,7 @@ import org.apache.camel.Navigate;
import org.apache.camel.Predicate;
import org.apache.camel.Processor;
import org.apache.camel.Route;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.processor.PooledExchangeTask;
import org.apache.camel.processor.PooledExchangeTaskFactory;
@@ -858,7 +859,7 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
private void runNotAllowed() {
LOG.trace("Run not allowed, will reject executing exchange: {}",
exchange);
if (exchange.getException() == null) {
- exchange.setException(new
RejectedExecutionException(notAllowedReason()));
+ exchange.setException(new
RouteStoppingException(notAllowedReason()));
}
AsyncCallback cb = callback;
taskFactory.release(this);
@@ -1121,7 +1122,7 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
if (!isRunAllowed()) {
LOG.trace("Run not allowed, will reject executing exchange:
{}", exchange);
if (exchange.getException() == null) {
- exchange.setException(new
RejectedExecutionException(notAllowedReason()));
+ exchange.setException(new
RouteStoppingException(notAllowedReason()));
}
AsyncCallback cb = callback;
taskFactory.release(this);
@@ -2231,16 +2232,16 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
}
/**
- * Whether the failure is an exchange cut off by a route stop or a dev
mode reload (not a forced CamelContext stop):
- * nothing in the route failed, and a consumer that rolls back, such as
file, delivers it again (CAMEL-25484).
+ * Whether the failure is an exchange cut off by a route stop, a dev mode
reload or a CamelContext stop: nothing in
+ * the route failed, and a consumer that rolls back, such as file,
delivers it again (CAMEL-25484, CAMEL-25502).
*/
static boolean isCutOffByRouteStop(Throwable e) {
- return e instanceof RejectedExecutionException &&
ROUTE_STOPPING_REASON.equals(e.getMessage());
+ return e instanceof RouteStoppingException;
}
/**
- * Logs an exchange that a route stop or a dev mode reload cut off as one
line at most WARN, without the message
- * history and stack trace of a failure, as nothing in the route failed
(CAMEL-25484).
+ * Logs an exchange that a route stop, a dev mode reload or a CamelContext
stop cut off as one line at most WARN,
+ * without the message history and stack trace of a failure, as nothing in
the route failed (CAMEL-25484).
*
* @return whether the exchange was cut off, and so is logged
*/
@@ -2248,8 +2249,10 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
if (!isCutOffByRouteStop(e) || exchange.isRollbackOnly() ||
exchange.isRollbackOnlyLast()) {
return false;
}
+ String reason = shutdownStrategy.isForceShutdown()
+ ? "the CamelContext is being stopped" : "its route is being
stopped or reloaded";
logger.log("Exchange cut off " + ExchangeHelper.logIds(exchange) +
failureOrigin(exchange)
- + ": its route is being stopped or reloaded, so it is not
continued."
+ + ": " + reason + ", so it is not continued."
+ " A consumer that rolls back, such as file, delivers it
again",
level == LoggingLevel.ERROR ? LoggingLevel.WARN : level);
return true;
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/loadbalancer/FailOverLoadBalancer.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/loadbalancer/FailOverLoadBalancer.java
index d6be8d9a4ea4..1e565805a8da 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/loadbalancer/FailOverLoadBalancer.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/loadbalancer/FailOverLoadBalancer.java
@@ -17,7 +17,6 @@
package org.apache.camel.processor.loadbalancer;
import java.util.List;
-import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.camel.AsyncCallback;
@@ -25,6 +24,7 @@ import org.apache.camel.AsyncProcessor;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.Exchange;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.Traceable;
import org.apache.camel.support.ExchangeHelper;
import org.apache.camel.util.ObjectHelper;
@@ -220,7 +220,7 @@ public class FailOverLoadBalancer extends
LoadBalancerSupport implements Traceab
if (!isRunAllowed()) {
LOG.trace("Run not allowed, will reject executing exchange:
{}", exchange);
if (exchange.getException() == null) {
- exchange.setException(new RejectedExecutionException());
+ exchange.setException(new RouteStoppingException("Run is
not allowed"));
}
// we cannot process so invoke callback
callback.done(false);
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/resequencer/ResequencerEngine.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/resequencer/ResequencerEngine.java
index 5b5f910e7f18..780cfd200051 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/resequencer/ResequencerEngine.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/resequencer/ResequencerEngine.java
@@ -25,6 +25,7 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.util.concurrent.ThreadHelper;
/**
@@ -193,7 +194,7 @@ public class ResequencerEngine<E> {
lock.lock();
try {
if (stopped) {
- throw new RejectedExecutionException("Resequencer is stopped");
+ throw new RouteStoppingException("Resequencer is stopped");
}
if (pred.test(sequence)) {
return;
@@ -207,7 +208,7 @@ public class ResequencerEngine<E> {
latch.await();
// compare with the stop count rather than read the flag, as a quick
restart may have reset it already
if (stopped || stopCount != stopCountAtWait) {
- throw new RejectedExecutionException("Resequencer is stopped");
+ throw new RouteStoppingException("Resequencer is stopped");
}
}
@@ -304,7 +305,7 @@ public class ResequencerEngine<E> {
try {
// a stopped resequencer has cancelled its timer, so the element
could not be scheduled for timing out
if (stopped) {
- throw new RejectedExecutionException("Resequencer is stopped");
+ throw new RouteStoppingException("Resequencer is stopped");
}
// wrap object into internal element
diff --git
a/core/camel-core/src/test/java/org/apache/camel/impl/engine/ShutdownWaitingAtTest.java
b/core/camel-core/src/test/java/org/apache/camel/impl/engine/ShutdownWaitingAtTest.java
index 3e7e95683c1e..f3aa7a6d8372 100644
---
a/core/camel-core/src/test/java/org/apache/camel/impl/engine/ShutdownWaitingAtTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/impl/engine/ShutdownWaitingAtTest.java
@@ -25,8 +25,8 @@ import java.util.concurrent.atomic.AtomicReference;
import org.apache.camel.CamelContext;
import org.apache.camel.ContextTestSupport;
import org.apache.camel.Exchange;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.builder.RouteBuilder;
-import org.apache.camel.component.direct.DirectConsumerNotAvailableException;
import org.apache.camel.component.log.ConsumingAppender;
import org.apache.camel.spi.CamelEvent;
import org.apache.camel.spi.RouteStartupOrder;
@@ -104,8 +104,8 @@ public class ShutdownWaitingAtTest extends
ContextTestSupport {
await().atMost(5, TimeUnit.SECONDS).until(() -> failure.get() != null);
Exchange exchange = failure.get();
- DirectConsumerNotAvailableException e
- = assertInstanceOf(DirectConsumerNotAvailableException.class,
exchange.getException());
+ // a cut-off by the route stop, not a failure (CAMEL-25502)
+ RouteStoppingException e =
assertInstanceOf(RouteStoppingException.class, exchange.getException());
assertTrue(e.getMessage().contains("direct://shipment"),
e.getMessage());
assertTrue(e.getMessage().contains("interrupted while waiting for one,
as the route is being stopped or reloaded"),
e.getMessage());
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/errorhandler/RouteStopCutOffLogTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/errorhandler/RouteStopCutOffLogTest.java
index 70cc9a6946b8..4d33c34bee03 100644
---
a/core/camel-core/src/test/java/org/apache/camel/processor/errorhandler/RouteStopCutOffLogTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/errorhandler/RouteStopCutOffLogTest.java
@@ -22,6 +22,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import org.apache.camel.ContextTestSupport;
+import org.apache.camel.RouteStoppingException;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.log.ConsumingAppender;
import org.apache.logging.log4j.Level;
@@ -37,7 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* CAMEL-25484: an exchange that a route stop or a dev mode reload cuts off is
logged as one WARN line, not as an
- * exhausted failure with message history and stack trace.
+ * exhausted failure with message history and stack trace; CAMEL-25502: and is
not recorded in the error registry.
*/
public class RouteStopCutOffLogTest extends ContextTestSupport {
@@ -84,6 +85,42 @@ public class RouteStopCutOffLogTest extends
ContextTestSupport {
assertFalse(message.endsWith("THROWN"), message);
}
+ @Test
+ public void theCutOffIsNotInTheErrorRegistry() throws Exception {
+ context.getErrorRegistry().setEnabled(true);
+ context.getShutdownStrategy().setTimeout(1);
+ context.getShutdownStrategy().setTimeUnit(TimeUnit.SECONDS);
+ context.getInflightRepository().setInflightBrowseEnabled(true);
+
+ template.sendBody("seda:start", List.of("A", "B"));
+ await().atMost(5, TimeUnit.SECONDS).until(() ->
context.getInflightRepository().browse().stream()
+ .anyMatch(e -> "shipment".equals(e.getNodeId())));
+ context.getRouteController().stopRoute("slow");
+
+ // the cut-off is a RouteStoppingException (the error handler's and
the direct producer's): nothing failed
+ await().atMost(5, TimeUnit.SECONDS).until(() ->
context.getRouteController().getRouteStatus("slow").isStopped());
+ assertEquals(0, context.getErrorRegistry().size(), () ->
context.getErrorRegistry().browse().toString());
+ }
+
+ @Test
+ public void theCutOffIsInTheErrorRegistryWhenIncluded() throws Exception {
+ context.getErrorRegistry().setEnabled(true);
+ context.getErrorRegistry().setIncludeRouteStopping(true);
+ context.getShutdownStrategy().setTimeout(1);
+ context.getShutdownStrategy().setTimeUnit(TimeUnit.SECONDS);
+ context.getInflightRepository().setInflightBrowseEnabled(true);
+
+ template.sendBody("seda:start", List.of("A", "B"));
+ await().atMost(5, TimeUnit.SECONDS).until(() ->
context.getInflightRepository().browse().stream()
+ .anyMatch(e -> "shipment".equals(e.getNodeId())));
+ context.getRouteController().stopRoute("slow");
+
+ await().atMost(5, TimeUnit.SECONDS).until(() ->
context.getErrorRegistry().size() > 0);
+ assertTrue(context.getErrorRegistry().browse().stream()
+ .allMatch(e ->
RouteStoppingException.class.getName().equals(e.getExceptionType())),
+ () -> context.getErrorRegistry().browse().toString());
+ }
+
@Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
diff --git
a/core/camel-main/src/generated/java/org/apache/camel/main/ErrorRegistryConfigurationPropertiesConfigurer.java
b/core/camel-main/src/generated/java/org/apache/camel/main/ErrorRegistryConfigurationPropertiesConfigurer.java
index 71765daf0be9..025a8ddcc363 100644
---
a/core/camel-main/src/generated/java/org/apache/camel/main/ErrorRegistryConfigurationPropertiesConfigurer.java
+++
b/core/camel-main/src/generated/java/org/apache/camel/main/ErrorRegistryConfigurationPropertiesConfigurer.java
@@ -28,6 +28,7 @@ public class ErrorRegistryConfigurationPropertiesConfigurer
extends org.apache.c
map.put("Enabled", boolean.class);
map.put("IncludeExchangeProperties", boolean.class);
map.put("IncludeExchangeVariables", boolean.class);
+ map.put("IncludeRouteStopping", boolean.class);
map.put("MaximumEntries", int.class);
map.put("MaximumEntriesPerKind", int.class);
map.put("TimeToLiveSeconds", int.class);
@@ -49,6 +50,8 @@ public class ErrorRegistryConfigurationPropertiesConfigurer
extends org.apache.c
case "includeExchangeProperties":
target.setIncludeExchangeProperties(property(camelContext, boolean.class,
value)); return true;
case "includeexchangevariables":
case "includeExchangeVariables":
target.setIncludeExchangeVariables(property(camelContext, boolean.class,
value)); return true;
+ case "includeroutestopping":
+ case "includeRouteStopping":
target.setIncludeRouteStopping(property(camelContext, boolean.class, value));
return true;
case "maximumentries":
case "maximumEntries": target.setMaximumEntries(property(camelContext,
int.class, value)); return true;
case "maximumentriesperkind":
@@ -78,6 +81,8 @@ public class ErrorRegistryConfigurationPropertiesConfigurer
extends org.apache.c
case "includeExchangeProperties": return boolean.class;
case "includeexchangevariables":
case "includeExchangeVariables": return boolean.class;
+ case "includeroutestopping":
+ case "includeRouteStopping": return boolean.class;
case "maximumentries":
case "maximumEntries": return int.class;
case "maximumentriesperkind":
@@ -103,6 +108,8 @@ public class ErrorRegistryConfigurationPropertiesConfigurer
extends org.apache.c
case "includeExchangeProperties": return
target.isIncludeExchangeProperties();
case "includeexchangevariables":
case "includeExchangeVariables": return
target.isIncludeExchangeVariables();
+ case "includeroutestopping":
+ case "includeRouteStopping": return target.isIncludeRouteStopping();
case "maximumentries":
case "maximumEntries": return target.getMaximumEntries();
case "maximumentriesperkind":
diff --git
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 8d59e0dc9e73..a1e0eca1dfa0 100644
---
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -266,6 +266,7 @@
{ "name": "camel.errorRegistry.enabled", "required": false, "description":
"Whether the error registry is enabled to capture errors during message
routing.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.errorRegistry.includeExchangeProperties", "required":
false, "description": "Whether to include the exchange properties in the
captured error data.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": true, "secret": false },
{ "name": "camel.errorRegistry.includeExchangeVariables", "required":
false, "description": "Whether to include the exchange variables in the
captured error data.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": true, "secret": false },
+ { "name": "camel.errorRegistry.includeRouteStopping", "required": false,
"description": "Whether to also capture an exchange cut off by a route stop, a
route reload or a CamelContext stop (RouteStoppingException). Nothing in the
route failed, but a consumer that does not roll back may lose the message, so
enable it to see how often stops cut off work.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue" [...]
{ "name": "camel.errorRegistry.maximumEntries", "required": false,
"description": "The maximum number of error entries to keep in the registry.
When the limit is exceeded, the oldest entries are evicted.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"integer", "javaType": "int", "defaultValue": 100, "secret": false },
{ "name": "camel.errorRegistry.maximumEntriesPerKind", "required": false,
"description": "The maximum number of error entries of the same kind (same
route, node and exception type) to keep, so a storm of one failure does not
evict all the other errors. The counter of that kind keeps rising even when its
older entries are evicted.", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"integer", "javaType": "int", "defaultValue": 3, "secret": false },
{ "name": "camel.errorRegistry.timeToLiveSeconds", "required": false,
"description": "The time-to-live in seconds for error entries. Entries older
than this are evicted. The default value is 0 (disabled).", "sourceType":
"org.apache.camel.main.ErrorRegistryConfigurationProperties", "type":
"integer", "javaType": "int", "defaultValue": 0, "secret": false },
diff --git a/core/camel-main/src/main/docs/main.adoc
b/core/camel-main/src/main/docs/main.adoc
index 5e3ffea4d8df..4d300fd6eb9c 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -748,7 +748,7 @@ The camel.lra supports 5 options, which are listed below.
=== Camel Error Registry configurations
-The camel.errorRegistry supports 9 options, which are listed below.
+The camel.errorRegistry supports 10 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -759,6 +759,7 @@ The camel.errorRegistry supports 9 options, which are
listed below.
| `camel.errorRegistry.enabled` | Whether the error registry is enabled to
capture errors during message routing. | false | boolean
| `camel.errorRegistry.includeExchangeProperties` | Whether to include the
exchange properties in the captured error data. | true | boolean
| `camel.errorRegistry.includeExchangeVariables` | Whether to include the
exchange variables in the captured error data. | true | boolean
+| `camel.errorRegistry.includeRouteStopping` | Whether to also capture an
exchange cut off by a route stop, a route reload or a CamelContext stop
(RouteStoppingException). Nothing in the route failed, but a consumer that does
not roll back may lose the message, so enable it to see how often stops cut off
work. | false | boolean
| `camel.errorRegistry.maximumEntries` | The maximum number of error entries
to keep in the registry. When the limit is exceeded, the oldest entries are
evicted. | 100 | int
| `camel.errorRegistry.maximumEntriesPerKind` | The maximum number of error
entries of the same kind (same route, node and exception type) to keep, so a
storm of one failure does not evict all the other errors. The counter of that
kind keeps rising even when its older entries are evicted. | 3 | int
| `camel.errorRegistry.timeToLiveSeconds` | The time-to-live in seconds for
error entries. Entries older than this are evicted. The default value is 0
(disabled). | 0 | int
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
index 8799470ba586..5e470b81c067 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
@@ -2730,6 +2730,7 @@ public abstract class BaseMainSupport extends BaseService
{
registry.setBodyIncludeFiles(config.isBodyIncludeFiles());
registry.setIncludeExchangeProperties(config.isIncludeExchangeProperties());
registry.setIncludeExchangeVariables(config.isIncludeExchangeVariables());
+ registry.setIncludeRouteStopping(config.isIncludeRouteStopping());
}
private void setAiObservabilityProperties(
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/ErrorRegistryConfigurationProperties.java
b/core/camel-main/src/main/java/org/apache/camel/main/ErrorRegistryConfigurationProperties.java
index 8404baf80097..fb445c0ebf80 100644
---
a/core/camel-main/src/main/java/org/apache/camel/main/ErrorRegistryConfigurationProperties.java
+++
b/core/camel-main/src/main/java/org/apache/camel/main/ErrorRegistryConfigurationProperties.java
@@ -46,6 +46,8 @@ public class ErrorRegistryConfigurationProperties implements
BootstrapCloseable
private boolean includeExchangeProperties = true;
@Metadata(defaultValue = "true")
private boolean includeExchangeVariables = true;
+ @Metadata
+ private boolean includeRouteStopping;
public ErrorRegistryConfigurationProperties(MainConfigurationProperties
parent) {
this.parent = parent;
@@ -166,6 +168,19 @@ public class ErrorRegistryConfigurationProperties
implements BootstrapCloseable
this.includeExchangeVariables = includeExchangeVariables;
}
+ public boolean isIncludeRouteStopping() {
+ return includeRouteStopping;
+ }
+
+ /**
+ * Whether to also capture an exchange cut off by a route stop, a route
reload or a CamelContext stop
+ * (RouteStoppingException). Nothing in the route failed, but a consumer
that does not roll back may lose the
+ * message, so enable it to see how often stops cut off work.
+ */
+ public void setIncludeRouteStopping(boolean includeRouteStopping) {
+ this.includeRouteStopping = includeRouteStopping;
+ }
+
// -- fluent builder methods --
/**
@@ -244,4 +259,14 @@ public class ErrorRegistryConfigurationProperties
implements BootstrapCloseable
this.includeExchangeVariables = includeExchangeVariables;
return this;
}
+
+ /**
+ * Whether to also capture an exchange cut off by a route stop, a route
reload or a CamelContext stop
+ * (RouteStoppingException). Nothing in the route failed, but a consumer
that does not roll back may lose the
+ * message, so enable it to see how often stops cut off work.
+ */
+ public ErrorRegistryConfigurationProperties
withIncludeRouteStopping(boolean includeRouteStopping) {
+ this.includeRouteStopping = includeRouteStopping;
+ return this;
+ }
}
diff --git
a/core/camel-main/src/test/java/org/apache/camel/main/ErrorRegistryConfigurationPropertiesTest.java
b/core/camel-main/src/test/java/org/apache/camel/main/ErrorRegistryConfigurationPropertiesTest.java
new file mode 100644
index 000000000000..26b049e44b2b
--- /dev/null
+++
b/core/camel-main/src/test/java/org/apache/camel/main/ErrorRegistryConfigurationPropertiesTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.main;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * CAMEL-25502: the error registry captures an exchange cut off by a route
stop only when configured to.
+ */
+class ErrorRegistryConfigurationPropertiesTest {
+
+ @Test
+ void routeStoppingIsLeftOutByDefault() throws Exception {
+ Main main = new Main();
+ main.addInitialProperty("camel.errorRegistry.enabled", "true");
+
+ main.start();
+ try {
+
assertThat(main.getCamelContext().getErrorRegistry().isEnabled()).isTrue();
+
assertThat(main.getCamelContext().getErrorRegistry().isIncludeRouteStopping()).isFalse();
+ } finally {
+ main.stop();
+ }
+ }
+
+ @Test
+ void routeStoppingIsIncludedWhenConfigured() throws Exception {
+ Main main = new Main();
+ main.addInitialProperty("camel.errorRegistry.enabled", "true");
+ main.addInitialProperty("camel.errorRegistry.includeRouteStopping",
"true");
+
+ main.start();
+ try {
+
assertThat(main.getCamelContext().getErrorRegistry().isIncludeRouteStopping()).isTrue();
+ } finally {
+ main.stop();
+ }
+ }
+}
diff --git
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedErrorRegistryMBean.java
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedErrorRegistryMBean.java
index 0f0dc2624c9a..04707ccd973c 100644
---
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedErrorRegistryMBean.java
+++
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedErrorRegistryMBean.java
@@ -80,6 +80,12 @@ public interface ManagedErrorRegistryMBean extends
ManagedServiceMBean {
@ManagedAttribute(description = "Whether to include exchange variables")
void setIncludeExchangeVariables(boolean includeExchangeVariables);
+ @ManagedAttribute(description = "Whether to also capture exchanges cut off
by a route stop, reload or CamelContext stop")
+ boolean isIncludeRouteStopping();
+
+ @ManagedAttribute(description = "Whether to also capture exchanges cut off
by a route stop, reload or CamelContext stop")
+ void setIncludeRouteStopping(boolean includeRouteStopping);
+
@ManagedOperation(description = "Browse all error entries")
TabularData browse();
diff --git
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedErrorRegistry.java
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedErrorRegistry.java
index 759d45c46f37..d80968ff89c4 100644
---
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedErrorRegistry.java
+++
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedErrorRegistry.java
@@ -142,6 +142,16 @@ public class ManagedErrorRegistry extends ManagedService
implements ManagedError
errorRegistry.setIncludeExchangeVariables(includeExchangeVariables);
}
+ @Override
+ public boolean isIncludeRouteStopping() {
+ return errorRegistry.isIncludeRouteStopping();
+ }
+
+ @Override
+ public void setIncludeRouteStopping(boolean includeRouteStopping) {
+ errorRegistry.setIncludeRouteStopping(includeRouteStopping);
+ }
+
@Override
public TabularData browse() {
return browseEntries(errorRegistry.browse());
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 8195e780d257..f022c9640d33 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -1546,9 +1546,25 @@ When a route is stopped or reloaded (for example a save
in `camel run --dev`) wh
the graceful shutdown times out, the exchange is cut off. The error handler
logged this as a failed delivery: an
ERROR with "Exhausted after delivery attempt", the message history and the
stack trace of the
`RejectedExecutionException`. It is now one line at WARN, "Exchange cut off
...: its route is being stopped or
-reloaded", as nothing in the route failed, and a consumer that rolls back,
such as file, delivers the message again.
-A level lower than WARN configured for exhausted logging is kept. A forced
stop of the CamelContext is logged as
-before. The exception set on the exchange is unchanged.
+reloaded" (or "the CamelContext is being stopped"), as nothing in the route
failed, and a consumer that rolls back,
+such as file, delivers the message again. A level lower than WARN configured
for exhausted logging is kept.
+
+=== camel-core - RouteStoppingException for an exchange cut off by a stop
+
+An exchange cut off because its route, or the CamelContext, is being stopped
(a route stop, a route reload in dev
+mode, or the graceful shutdown timing out) now fails with the new
`org.apache.camel.RouteStoppingException` instead
+of a plain `RejectedExecutionException`. It extends
`RejectedExecutionException`, so an
+`onException(RejectedExecutionException.class)` or a catch of it still
matches; a real rejection, a thread pool or a
+queue that is full, stays a plain `RejectedExecutionException`. It is set by
the error handler, the Delay, Throttle,
+Threads, Failover load balancer and Resequencer EIPs while they stop, and by a
forced shutdown.
+
+A `direct` producer that was waiting for a consumer when a forced stop
interrupted it now sets a
+`RouteStoppingException` (with the `InterruptedException` as its cause)
instead of a
+`DirectConsumerNotAvailableException`.
+
+Such a cut-off is not recorded in the error registry, as nothing in the route
failed; set
+`camel.errorRegistry.includeRouteStopping=true` to record it as well, for
example to see how often stops cut off
+work that a consumer which does not roll back may lose.
=== camel-core - stopping or removing all routes is one graceful shutdown
diff --git a/docs/user-manual/modules/ROOT/pages/error-registry.adoc
b/docs/user-manual/modules/ROOT/pages/error-registry.adoc
index 354e7d00507b..7a50f4fbde54 100644
--- a/docs/user-manual/modules/ROOT/pages/error-registry.adoc
+++ b/docs/user-manual/modules/ROOT/pages/error-registry.adoc
@@ -36,6 +36,7 @@ camel.errorRegistry.enabled = true
| `camel.errorRegistry.bodyIncludeFiles` | `true` | boolean | Whether to
include file-based message bodies.
| `camel.errorRegistry.includeExchangeProperties` | `true` | boolean | Whether
to include exchange properties in the snapshot.
| `camel.errorRegistry.includeExchangeVariables` | `true` | boolean | Whether
to include exchange variables in the snapshot.
+| `camel.errorRegistry.includeRouteStopping` | `false` | boolean | Whether to
also capture an exchange cut off by a route stop, a route reload or a
CamelContext stop (`RouteStoppingException`). Nothing in the route failed, but
a consumer that does not roll back may lose the message, so enable it to see
how often stops cut off work.
|===
== Captured Data