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 426c3f8a4cb8 CAMEL-26292: camel-core - ThrottlingInflightRoutePolicy
with the context scope must also throttle on failed exchanges (#28468)
426c3f8a4cb8 is described below
commit 426c3f8a4cb81268113503002605f8653a41d425
Author: allthingssecurity <[email protected]>
AuthorDate: Mon Oct 26 13:32:51 2026 +0530
CAMEL-26292: camel-core - ThrottlingInflightRoutePolicy with the context
scope must also throttle on failed exchanges (#28468)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
...RoutePolicyContextScopeFailedExchangesTest.java | 129 +++++++++++++++++++++
.../throttling/ThrottlingInflightRoutePolicy.java | 11 +-
2 files changed, 137 insertions(+), 3 deletions(-)
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/throttle/ThrottlingInflightRoutePolicyContextScopeFailedExchangesTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/throttle/ThrottlingInflightRoutePolicyContextScopeFailedExchangesTest.java
new file mode 100644
index 000000000000..884b819aec65
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/throttle/ThrottlingInflightRoutePolicyContextScopeFailedExchangesTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.processor.throttle;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Ordered;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.CamelEvent;
+import org.apache.camel.support.EventNotifierSupport;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.throttling.ThrottlingInflightRoutePolicy;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * With the context scope, the {@link ThrottlingInflightRoutePolicy} must also
check the inflight exchanges when an
+ * exchange fails, like the route scope does, otherwise a consumer it has
suspended is never resumed when the exchanges
+ * that are still inflight all fail.
+ */
+class ThrottlingInflightRoutePolicyContextScopeFailedExchangesTest extends
ContextTestSupport {
+
+ private final ThrottlingInflightRoutePolicy policy = new
ThrottlingInflightRoutePolicy();
+ private final CountDownLatch entered = new CountDownLatch(3);
+ private final CountDownLatch okGate = new CountDownLatch(1);
+ private final CountDownLatch failGate = new CountDownLatch(1);
+ private final CountDownLatch completed = new CountDownLatch(1);
+ private final CountDownLatch failed = new CountDownLatch(2);
+
+ @Test
+ void testResumeWhenTheInflightExchangesFail() throws Exception {
+ // called after the event notifier of the policy, for the same event,
for the exchanges of the route
+ context.getManagementStrategy().addEventNotifier(new
LastEventNotifier());
+
+ MockEndpoint result = getMockEndpoint("mock:result");
+ result.expectedBodiesReceived("ok", "after");
+
+ template.sendBody("seda:foo", "ok");
+ template.sendBody("seda:foo", "fail");
+ template.sendBody("seda:foo", "fail");
+ assertTrue(entered.await(10, TimeUnit.SECONDS));
+
+ // one exchange completes while three are inflight, so the policy
suspends the consumer
+ okGate.countDown();
+ assertTrue(completed.await(10, TimeUnit.SECONDS));
+ ServiceSupport consumer = (ServiceSupport)
context.getRoute("foo").getConsumer();
+ assertTrue(consumer.isSuspended(), "The policy should suspend the
consumer");
+
+ // then the two other exchanges fail, and nothing is inflight any more
+ failGate.countDown();
+ assertTrue(failed.await(10, TimeUnit.SECONDS));
+ assertFalse(consumer.isSuspended(), "The policy should resume the
consumer when the inflight exchanges fail");
+
+ template.sendBody("seda:foo", "after");
+ assertMockEndpointsSatisfied();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+
policy.setScope(ThrottlingInflightRoutePolicy.ThrottlingScope.Context);
+ policy.setMaxInflightExchanges(1);
+
+
from("seda:foo?concurrentConsumers=3").routeId("foo").routePolicy(policy)
+ .process(exchange -> {
+ String body =
exchange.getMessage().getBody(String.class);
+ if ("after".equals(body)) {
+ return;
+ }
+ entered.countDown();
+ if ("ok".equals(body)) {
+ assertTrue(okGate.await(10, TimeUnit.SECONDS));
+ } else {
+ assertTrue(failGate.await(10,
TimeUnit.SECONDS));
+ throw new IllegalArgumentException("Forced");
+ }
+ })
+ .to("mock:result");
+ }
+ };
+ }
+
+ private final class LastEventNotifier extends EventNotifierSupport
implements Ordered {
+
+ @Override
+ public void notify(CamelEvent event) {
+ // only the exchanges of the route (the exchanges of the producer
template complete as well)
+ if (!"foo".equals(((CamelEvent.ExchangeEvent)
event).getExchange().getFromRouteId())) {
+ return;
+ }
+ if (event instanceof CamelEvent.ExchangeFailedEvent) {
+ failed.countDown();
+ } else if (event instanceof CamelEvent.ExchangeCompletedEvent) {
+ completed.countDown();
+ }
+ }
+
+ @Override
+ public boolean isEnabled(CamelEvent event) {
+ return event instanceof CamelEvent.ExchangeCompletedEvent || event
instanceof CamelEvent.ExchangeFailedEvent;
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST;
+ }
+ }
+}
diff --git
a/core/camel-support/src/main/java/org/apache/camel/throttling/ThrottlingInflightRoutePolicy.java
b/core/camel-support/src/main/java/org/apache/camel/throttling/ThrottlingInflightRoutePolicy.java
index cb183f05bf63..52ec4f2a5939 100644
---
a/core/camel-support/src/main/java/org/apache/camel/throttling/ThrottlingInflightRoutePolicy.java
+++
b/core/camel-support/src/main/java/org/apache/camel/throttling/ThrottlingInflightRoutePolicy.java
@@ -32,6 +32,8 @@ import org.apache.camel.Route;
import org.apache.camel.StatefulService;
import org.apache.camel.spi.CamelEvent;
import org.apache.camel.spi.CamelEvent.ExchangeCompletedEvent;
+import org.apache.camel.spi.CamelEvent.ExchangeEvent;
+import org.apache.camel.spi.CamelEvent.ExchangeFailedEvent;
import org.apache.camel.spi.CamelLogger;
import org.apache.camel.spi.Configurer;
import org.apache.camel.spi.Metadata;
@@ -372,15 +374,18 @@ public class ThrottlingInflightRoutePolicy extends
RoutePolicySupport implements
@Override
public void notify(CamelEvent event) {
- ExchangeCompletedEvent completedEvent = (ExchangeCompletedEvent)
event;
+ ExchangeEvent exchangeEvent = (ExchangeEvent) event;
for (Route route : routes) {
- throttle(route, completedEvent.getExchange());
+ throttle(route, exchangeEvent.getExchange());
}
}
@Override
public boolean isEnabled(CamelEvent event) {
- return event instanceof ExchangeCompletedEvent;
+ // an exchange that is done with an exception is failed instead of
completed, and like with the route scope
+ // it must be throttled as well, otherwise a consumer this policy
suspended is not resumed when the
+ // exchanges that are still inflight fail
+ return event instanceof ExchangeCompletedEvent || event instanceof
ExchangeFailedEvent;
}
@Override