You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by "davsclaus (via GitHub)" <gi...@apache.org> on 2026/09/18 15:05:49 UTC

[PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

davsclaus opened a new pull request, #26603:
URL: https://github.com/apache/camel/pull/26603

   Fixes https://issues.apache.org/jira/browse/CAMEL-24819
   
   Stacked on #26602 (CAMEL-24818); the first two commits are that PR and this one is the third.
   
   The circuit breaker processors exposed state, successful, failed and not permitted calls, but not three things they know:
   
   - how often the `onFallback` answered, i.e. how many callers got a degraded answer
   - how many calls timed out (the breaker counts them as failures, so a slow service and a broken one looked the same)
   - how many calls a full bulkhead rejected (the not permitted metric and the fault tolerance `onPrevented` listener count only breaker-open refusals)
   
   Changes:
   
   - `ResilienceProcessor` and `FaultToleranceProcessor`: `fallbackCalls`, `timedOutCalls`, `bulkheadRejectedCalls` counters incremented on the same code paths that set the `CamelCircuitBreakerResponse*` properties, reset by `transitionToCloseState`, exposed as `getNumberOfFallbackCalls`, `getNumberOfTimedOutCalls`, `getNumberOfBulkheadRejectedCalls` managed attributes. The fault tolerance processor also handles `BulkheadException` without a fallback the way it handles the open breaker.
   - Dev consoles `resilience4j` and `fault-tolerance`: the three counts in JSON and text; generated console metadata updated.
   - `camel get circuit-breaker`: FALLBACK and TIMEOUT columns, a BULKHEAD column only when a bulkhead is configured, and the fault tolerance rows now show success, fail and reject counts (they only showed the state before). Docs updated.
   - TUI circuit breaker tab: FALLBACK and TIMEOUT columns, fallback, timeout and bulkhead in the detail panel, help page updated.
   - Tests: counters per processor (open breaker and timeout), console JSON and text, CLI columns incl. a fault tolerance row with bulkhead, TUI render.
   
   Full test suites of camel-resilience4j and camel-microprofile-fault-tolerance pass locally, as do the CLI and TUI tests.
   
   πŸ€– Generated with [Claude Code](https://claude.com/claude-code)
   
   https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #26603:
URL: https://github.com/apache/camel/pull/26603#discussion_r4049788300


##########
components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java:
##########
@@ -0,0 +1,133 @@
+/*
+ * 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.component.microprofile.faulttolerance;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit breaker listeners do not tell apart.
+ */
+public class FaultToleranceCallCountersTest extends CamelTestSupport {
+
+    private final CountDownLatch permitAcquired = new CountDownLatch(1);
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbDown", FaultToleranceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbSlow", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);

Review Comment:
   ⚠️ **Missing test: timeout and bulkhead-rejected WITHOUT a fallback.**
   
   `testTimedOutCounter` exercises the timeout-with-fallback path (the `call()` method inside the fallback handler). The no-fallback path β€” the `catch (TimeoutException e)` block in `process()` β€” is not tested. Same gap exists for `BulkheadException` without a fallback.
   
   These are structurally separate code paths (`// the circuit breaker triggered a timeout (no fallback)` comment confirms this). A test that verifies `timedOutCalls` increments when no `onFallback` is configured would lock in the counter for both paths. Without it, a refactor that removed the counter from one path would pass CI silently.
   
   Suggested test (add alongside the existing three):
   ```java
   @Test
   public void testTimedOutWithoutFallbackCounter() throws Exception {
       // no onFallback β€” the TimeoutException propagates to the caller
       from("direct:slowNoFallback")
               .circuitBreaker().id("cbSlowNoFallback")
               .faultToleranceConfiguration().timeoutEnabled(true).timeoutDuration(200).end()
               .to("direct:slowService")
               .end();
   
       Exchange result = template.request("direct:slowNoFallback",
               e -> e.getMessage().setBody("Hello World"));
       assertTrue(result.isFailed());
       FaultToleranceProcessor cb = context.getProcessor("cbSlowNoFallback", FaultToleranceProcessor.class);
       assertEquals(1, cb.getNumberOfTimedOutCalls());
       assertEquals(0, cb.getNumberOfFallbackCalls());
   }
   ```



##########
components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceCallCountersTest.java:
##########
@@ -0,0 +1,134 @@
+/*
+ * 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.component.resilience4j;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit breaker metrics do not tell apart.
+ */
+public class ResilienceCallCountersTest extends CamelTestSupport {
+
+    private final CountDownLatch permitAcquired = new CountDownLatch(1);
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        ResilienceProcessor cb = context.getProcessor("cbDown", ResilienceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        ResilienceProcessor cb = context.getProcessor("cbSlow", ResilienceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);

Review Comment:
   ⚠️ **Same gap as `FaultToleranceCallCountersTest`**: the no-fallback timeout and bulkhead paths are not covered.
   
   The `apply()` method in `ResilienceProcessor` has two branches: one for when no fallback is configured (`// there is no fallback`) and one for the fallback case. Only the fallback path is exercised by `testTimedOutCounter`. Add a companion test without `onFallback` to close the gap.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on code in PR #26603:
URL: https://github.com/apache/camel/pull/26603#discussion_r4048741524


##########
core/camel-api/src/main/java/org/apache/camel/spi/CircuitBreakerConstants.java:
##########
@@ -27,13 +27,17 @@ public interface CircuitBreakerConstants {
     @Metadata(label = "circuitBreaker",
               description = "Whether the exchange was processed by the onFallback by the circuit breaker", javaType = "boolean")
     String RESPONSE_FROM_FALLBACK = "CamelCircuitBreakerResponseFromFallback";
-    @Metadata(label = "circuitBreaker", description = "Whether the exchange was short circuited by the breaker",
+    @Metadata(label = "circuitBreaker",
+              description = "Whether the call did not complete normally and the circuit breaker short circuited the exchange, to the onFallback if there is one."
+                            + " This is true for a failed call, a timeout and a rejected call alike; use CamelCircuitBreakerResponseRejected to know whether the call was attempted.",
               javaType = "boolean")
     String RESPONSE_SHORT_CIRCUITED = "CamelCircuitBreakerResponseShortCircuited";
     @Metadata(label = "circuitBreaker", description = "Whether the exchange timed out during processing by the circuit breaker",
               javaType = "boolean")
     String RESPONSE_TIMED_OUT = "CamelCircuitBreakerResponseTimedOut";
-    @Metadata(label = "circuitBreaker", description = "Whether the circuit breaker rejected processing the exchange",
+    @Metadata(label = "circuitBreaker",
+              description = "Whether the circuit breaker rejected the call without attempting it, because the breaker is open or the bulkhead is full."
+                            + " Also set inside the onFallback, where it tells a dead service apart from a single failed call.",
               javaType = "boolean")
     String RESPONSE_REJECTED = "CamelCircuitBreakerResponseRejected";
     @Metadata(label = "circuitBreaker", description = "Whether the circuit breaker ignored an exception during processing",

Review Comment:
   Good point. The change lives in #26602, so I added it there as a section in the 4.23 upgrade guide (`camel-4x-upgrade-guide-4_23.adoc`, "Circuit Breaker EIP"): the property is now always `true` or `false` inside the `onFallback`, was absent before, and a `null` test must change. I kept the version note out of the constant's description on purpose: that text is generated into the catalog and the EIP docs, where a "prior to 4.X" sentence would go stale, while the upgrade guide is the place readers look for it. This branch is rebased on the updated #26602.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #26603:
URL: https://github.com/apache/camel/pull/26603#issuecomment-5732620624

   <!-- ci-tested-modules -->
   :test_tube: **CI tested the following changed modules:**
   
   - `catalog/camel-catalog`
   - `components/camel-microprofile/camel-microprofile-fault-tolerance`
   - `components/camel-resilience4j`
   - `core/camel-api`
   - `core/camel-core-engine`
   - `core/camel-core-model`
   - `docs`
   - `dsl/camel-jbang/camel-jbang-core`
   - `dsl/camel-jbang/camel-jbang-plugin-tui`
   
   > :information_source: Dependent modules were not tested because the total number of affected modules exceeded the threshold (50). Use the `test-dependents` label to force testing all dependents.
   
   ---
   
   <details><summary>:microscope: Scalpel shadow comparison β€” Scalpel: 566 of 692 tested, 25 compile-only β€” current: 566 all tested</summary>
   
   [Maveniverse Scalpel](https://github.com/maveniverse/scalpel) detected **594 affected modules** (current approach: 566).
   
   **Skip-tests mode would test 566 modules** (9 direct + 560 downstream), **skip tests for 25** (generated code, meta-modules)
   
   <details><summary>:warning: Modules only in Scalpel (28)</summary>
   
   - `apache-camel`
   - `camel-allcomponents`
   - `camel-catalog`
   - `camel-catalog-console`
   - `camel-catalog-maven`
   - `camel-catalog-suggest`
   - `camel-componentdsl`
   - `camel-endpointdsl`
   - `camel-endpointdsl-support`
   - `camel-itest`
   - `camel-jbang-core`
   - `camel-jbang-it`
   - `camel-jbang-main`
   - `camel-jbang-plugin-edit`
   - `camel-jbang-plugin-generate`
   - `camel-jbang-plugin-kubernetes`
   - `camel-jbang-plugin-test`
   - `camel-kamelet-main`
   - `camel-launcher`
   - `camel-report-maven-plugin`
   - `camel-route-parser`
   - `camel-salesforce-maven-plugin`
   - `camel-yaml-dsl`
   - `camel-yaml-dsl-deserializers`
   - `camel-yaml-dsl-maven-plugin`
   - `coverage`
   - `docs`
   - `dummy-component`
   
   </details>
   
   
   <details><summary>Modules Scalpel would test (566)</summary>
   
   - `archetypes` ← downstream of org.apache.camel:camel-support
   - `camel-a2a` ← downstream of org.apache.camel:camel-core-model
   - `camel-activemq` ← downstream of org.apache.camel:camel-core-model
   - `camel-activemq6` ← downstream of org.apache.camel:camel-core-model
   - `camel-ai-observability` ← downstream of org.apache.camel:camel-core-model
   - `camel-ai-observability-api` ← downstream of org.apache.camel:camel-core-model
   - `camel-ai-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-ai-resource` ← downstream of org.apache.camel:camel-core-model
   - `camel-ai-tool` ← downstream of org.apache.camel:camel-core-model
   - `camel-alibaba-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-alibaba-eventbridge` ← downstream of org.apache.camel:camel-core-model
   - `camel-alibaba-fc` ← downstream of org.apache.camel:camel-core-model
   - `camel-alibaba-kms` ← downstream of org.apache.camel:camel-core-model
   - `camel-alibaba-mns` ← downstream of org.apache.camel:camel-core-model
   - `camel-alibaba-oss` ← downstream of org.apache.camel:camel-core-model
   - `camel-alibaba-ots` ← downstream of org.apache.camel:camel-core-model
   - `camel-alibaba-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-alibaba-sls` ← downstream of org.apache.camel:camel-core-model
   - `camel-alibaba-sms` ← downstream of org.apache.camel:camel-core-model
   - `camel-amqp` ← downstream of org.apache.camel:camel-core-model
   - `camel-api` ← core/camel-api/src/main/java/org/apache/camel/spi/CircuitBreakerConstants.java
   - `camel-api-component-maven-plugin` ← downstream of org.apache.camel:camel-core
   - `camel-arangodb` ← downstream of org.apache.camel:camel-core-model
   - `camel-archetype-api-component` ← downstream of org.apache.camel:camel-support
   - `camel-archetype-component` ← downstream of org.apache.camel:camel-support
   - `camel-archetype-dataformat` ← downstream of org.apache.camel:camel-support
   - `camel-archetype-java` ← downstream of org.apache.camel:camel-support
   - `camel-archetype-main` ← downstream of org.apache.camel:camel-support
   - `camel-as2` ← downstream of org.apache.camel:camel-core-model
   - `camel-as2-api` ← downstream of org.apache.camel:camel-api
   - `camel-as2-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-asn1` ← downstream of org.apache.camel:camel-core-model
   - `camel-asterisk` ← downstream of org.apache.camel:camel-core-model
   - `camel-atmosphere-websocket` ← downstream of org.apache.camel:camel-core-model
   - `camel-atom` ← downstream of org.apache.camel:camel-core-model
   - `camel-attachments` ← downstream of org.apache.camel:camel-core-model
   - `camel-avro` ← downstream of org.apache.camel:camel-core-model
   - `camel-avro-rpc` ← downstream of org.apache.camel:camel-core-model
   - `camel-avro-rpc-jetty` ← downstream of org.apache.camel:camel-api
   - `camel-avro-rpc-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-avro-rpc-spi` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws-bedrock` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws-cloudtrail` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws-config` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws-parameter-store` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws-secrets-manager` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws-security-hub` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-athena` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-comprehend` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-cw` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-ddb` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-ec2` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-ecs` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-eks` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-eventbridge` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-iam` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-kinesis` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-kms` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-lambda` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-mq` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-msk` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-polly` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-redshift` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-rekognition` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-s3` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-s3-vectors` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-ses` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-sns` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-sqs` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-step-functions` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-sts` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-textract` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-timestream` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-transcribe` ← downstream of org.apache.camel:camel-core-model
   - `camel-aws2-translate` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-cosmosdb` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-eventgrid` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-eventhubs` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-files` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-functions` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-key-vault` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-schema-registry` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-servicebus` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-storage-blob` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-storage-datalake` ← downstream of org.apache.camel:camel-core-model
   - `camel-azure-storage-queue` ← downstream of org.apache.camel:camel-core-model
   - `camel-barcode` ← downstream of org.apache.camel:camel-core-model
   - `camel-base` ← downstream of org.apache.camel:camel-api
   - `camel-base-engine` ← downstream of org.apache.camel:camel-base
   - `camel-base64` ← downstream of org.apache.camel:camel-core-model
   - `camel-bean` ← downstream of org.apache.camel:camel-core-model
   - `camel-bean-validator` ← downstream of org.apache.camel:camel-core-model
   - `camel-beanio` ← downstream of org.apache.camel:camel-core-model
   - `camel-bindy` ← downstream of org.apache.camel:camel-core-model
   - `camel-bonita` ← downstream of org.apache.camel:camel-core-model
   - `camel-box` ← downstream of org.apache.camel:camel-core-model
   - `camel-box-api` ← downstream of org.apache.camel:camel-api
   - `camel-box-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-braintree` ← downstream of org.apache.camel:camel-core-model
   - `camel-browse` ← downstream of org.apache.camel:camel-core-model
   - `camel-caffeine` ← downstream of org.apache.camel:camel-core-model
   - `camel-camunda` ← downstream of org.apache.camel:camel-core-model
   - `camel-cassandraql` ← downstream of org.apache.camel:camel-core-model
   - `camel-catalog` ← catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json, catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/fault-tolerance.json, catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/resilience4j.json, catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/circuitBreaker-eip.adoc, catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/circuitBreaker.json
   - `camel-catalog-common` ← downstream of org.apache.camel:camel-support
   - `camel-cbor` ← downstream of org.apache.camel:camel-core-model
   - `camel-chatscript` ← downstream of org.apache.camel:camel-core-model
   - `camel-chunk` ← downstream of org.apache.camel:camel-core-model
   - `camel-cli-connector` ← downstream of org.apache.camel:camel-support
   - `camel-cli-debug` ← downstream of org.apache.camel:camel-cli-connector
   - `camel-clickhouse` ← downstream of org.apache.camel:camel-core-model
   - `camel-clickup` ← downstream of org.apache.camel:camel-core-model
   - `camel-cloudevents` ← downstream of org.apache.camel:camel-core-model
   - `camel-cluster` ← downstream of org.apache.camel:camel-core-model
   - `camel-cm-sms` ← downstream of org.apache.camel:camel-core-model
   - `camel-coap` ← downstream of org.apache.camel:camel-core-model
   - `camel-cometd` ← downstream of org.apache.camel:camel-core-model
   - `camel-console` ← downstream of org.apache.camel:camel-support
   - `camel-consul` ← downstream of org.apache.camel:camel-core-model
   - `camel-controlbus` ← downstream of org.apache.camel:camel-core-model
   - `camel-core` ← downstream of org.apache.camel:camel-core-engine
   - `camel-core-all` ← downstream of org.apache.camel:camel-api
   - `camel-core-catalog` ← downstream of org.apache.camel:camel-api
   - `camel-core-engine` ← core/camel-core-engine/src/main/docs/modules/eips/pages/circuitBreaker-eip.adoc
   - `camel-core-languages` ← downstream of org.apache.camel:camel-core-model
   - `camel-core-model` ← core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/circuitBreaker.json
   - `camel-core-processor` ← downstream of org.apache.camel:camel-api
   - `camel-core-reifier` ← downstream of org.apache.camel:camel-api
   - `camel-core-xml` ← downstream of org.apache.camel:camel-core-engine
   - `camel-couchbase` ← downstream of org.apache.camel:camel-core-model
   - `camel-couchdb` ← downstream of org.apache.camel:camel-core-model
   - `camel-cron` ← downstream of org.apache.camel:camel-core-model
   - `camel-crypto` ← downstream of org.apache.camel:camel-core-model
   - `camel-crypto-pgp` ← downstream of org.apache.camel:camel-core-model
   - `camel-csv` ← downstream of org.apache.camel:camel-core-model
   - `camel-cxf-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-cxf-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-cxf-rest` ← downstream of org.apache.camel:camel-core-model
   - `camel-cxf-soap` ← downstream of org.apache.camel:camel-core-model
   - `camel-cxf-spring-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-cxf-spring-rest` ← downstream of org.apache.camel:camel-core-model
   - `camel-cxf-spring-soap` ← downstream of org.apache.camel:camel-core-model
   - `camel-cxf-spring-transport` ← downstream of org.apache.camel:camel-core-model
   - `camel-cxf-transport` ← downstream of org.apache.camel:camel-core-model
   - `camel-cyberark-vault` ← downstream of org.apache.camel:camel-core-model
   - `camel-dapr` ← downstream of org.apache.camel:camel-core-model
   - `camel-dataformat` ← downstream of org.apache.camel:camel-core-model
   - `camel-dataset` ← downstream of org.apache.camel:camel-core-model
   - `camel-datasonnet` ← downstream of org.apache.camel:camel-core-model
   - `camel-dataweave` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-common-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-db2` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-maven-plugin` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-mongodb` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-mysql` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-oracle` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-postgres` ← downstream of org.apache.camel:camel-core-model
   - `camel-debezium-sqlserver` ← downstream of org.apache.camel:camel-core-model
   - `camel-debug` ← downstream of org.apache.camel:camel-core-model
   - `camel-dfdl` ← downstream of org.apache.camel:camel-core-model
   - `camel-dhis2` ← downstream of org.apache.camel:camel-core-model
   - `camel-dhis2-api` ← downstream of org.apache.camel:camel-core-model
   - `camel-dhis2-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-diagram` ← downstream of org.apache.camel:camel-core-model
   - `camel-direct` ← downstream of org.apache.camel:camel-core-model
   - `camel-disruptor` ← downstream of org.apache.camel:camel-core-model
   - `camel-djl` ← downstream of org.apache.camel:camel-core-model
   - `camel-dns` ← downstream of org.apache.camel:camel-core-model
   - `camel-docker` ← downstream of org.apache.camel:camel-core-model
   - `camel-docling` ← downstream of org.apache.camel:camel-core-model
   - `camel-drill` ← downstream of org.apache.camel:camel-core-model
   - `camel-dropbox` ← downstream of org.apache.camel:camel-core-model
   - `camel-dsl-modeline` ← downstream of org.apache.camel:camel-support
   - `camel-dsl-support` ← downstream of org.apache.camel:camel-core-model
   - `camel-duckdb` ← downstream of org.apache.camel:camel-core-model
   - `camel-dynamic-router` ← downstream of org.apache.camel:camel-core-model
   - `camel-ehcache` ← downstream of org.apache.camel:camel-core-model
   - `camel-elasticsearch` ← downstream of org.apache.camel:camel-core-model
   - `camel-elasticsearch-rest-client` ← downstream of org.apache.camel:camel-core-model
   - `camel-event` ← downstream of org.apache.camel:camel-core-model
   - `camel-exec` ← downstream of org.apache.camel:camel-core-model
   - `camel-fastjson` ← downstream of org.apache.camel:camel-core-model
   - `camel-fhir` ← downstream of org.apache.camel:camel-core-model
   - `camel-fhir-api` ← downstream of org.apache.camel:camel-core-model
   - `camel-fhir-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-file` ← downstream of org.apache.camel:camel-core-engine
   - `camel-file-watch` ← downstream of org.apache.camel:camel-core-model
   - `camel-flatpack` ← downstream of org.apache.camel:camel-core-engine
   - `camel-flink` ← downstream of org.apache.camel:camel-core-model
   - `camel-flowable` ← downstream of org.apache.camel:camel-core-model
   - `camel-fop` ← downstream of org.apache.camel:camel-core-model
   - `camel-fory` ← downstream of org.apache.camel:camel-core-model
   - `camel-freemarker` ← downstream of org.apache.camel:camel-core-model
   - `camel-ftp` ← downstream of org.apache.camel:camel-core-model
   - `camel-ftp-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-geocoder` ← downstream of org.apache.camel:camel-core-model
   - `camel-git` ← downstream of org.apache.camel:camel-core-model
   - `camel-github2` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-bigquery` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-calendar` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-drive` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-firestore` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-functions` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-mail` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-pubsub` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-secret-manager` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-sheets` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-speech-to-text` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-storage` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-text-to-speech` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-vertexai` ← downstream of org.apache.camel:camel-core-model
   - `camel-google-vision` ← downstream of org.apache.camel:camel-core-model
   - `camel-graphql` ← downstream of org.apache.camel:camel-core-model
   - `camel-grok` ← downstream of org.apache.camel:camel-core-model
   - `camel-groovy` ← downstream of org.apache.camel:camel-core-model
   - `camel-grpc` ← downstream of org.apache.camel:camel-core-model
   - `camel-gson` ← downstream of org.apache.camel:camel-core-model
   - `camel-hashicorp-vault` ← downstream of org.apache.camel:camel-core-model
   - `camel-hazelcast` ← downstream of org.apache.camel:camel-core-model
   - `camel-health` ← downstream of org.apache.camel:camel-core-engine
   - `camel-hivemq` ← downstream of org.apache.camel:camel-core-model
   - `camel-hl7` ← downstream of org.apache.camel:camel-core-engine
   - `camel-http` ← downstream of org.apache.camel:camel-core-model
   - `camel-http-base` ← downstream of org.apache.camel:camel-core-model
   - `camel-http-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-huawei-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-huaweicloud-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-huaweicloud-dms` ← downstream of org.apache.camel:camel-core-model
   - `camel-huaweicloud-frs` ← downstream of org.apache.camel:camel-core-model
   - `camel-huaweicloud-functiongraph` ← downstream of org.apache.camel:camel-core-model
   - `camel-huaweicloud-iam` ← downstream of org.apache.camel:camel-core-model
   - `camel-huaweicloud-imagerecognition` ← downstream of org.apache.camel:camel-core-model
   - `camel-huaweicloud-obs` ← downstream of org.apache.camel:camel-core-model
   - `camel-huaweicloud-smn` ← downstream of org.apache.camel:camel-core-model
   - `camel-huggingface` ← downstream of org.apache.camel:camel-core-model
   - `camel-ibm-cos` ← downstream of org.apache.camel:camel-core-model
   - `camel-ibm-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-ibm-secrets-manager` ← downstream of org.apache.camel:camel-core-model
   - `camel-ibm-watson-discovery` ← downstream of org.apache.camel:camel-core-model
   - `camel-ibm-watson-language` ← downstream of org.apache.camel:camel-core-model
   - `camel-ibm-watson-speech-to-text` ← downstream of org.apache.camel:camel-core-model
   - `camel-ibm-watson-text-to-speech` ← downstream of org.apache.camel:camel-core-model
   - `camel-ibm-watsonx-ai` ← downstream of org.apache.camel:camel-core-model
   - `camel-ibm-watsonx-data` ← downstream of org.apache.camel:camel-core-model
   - `camel-ical` ← downstream of org.apache.camel:camel-core-model
   - `camel-iggy` ← downstream of org.apache.camel:camel-core-model
   - `camel-ignite` ← downstream of org.apache.camel:camel-core-model
   - `camel-infinispan` ← downstream of org.apache.camel:camel-core-model
   - `camel-infinispan-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-infinispan-embedded` ← downstream of org.apache.camel:camel-core-model
   - `camel-infinispan-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-influxdb` ← downstream of org.apache.camel:camel-core-model
   - `camel-influxdb2` ← downstream of org.apache.camel:camel-core-model
   - `camel-iso8583` ← downstream of org.apache.camel:camel-core-model
   - `camel-jackson` ← downstream of org.apache.camel:camel-core-engine
   - `camel-jackson-avro` ← downstream of org.apache.camel:camel-core-model
   - `camel-jackson-protobuf` ← downstream of org.apache.camel:camel-core-model
   - `camel-jackson3` ← downstream of org.apache.camel:camel-core-engine
   - `camel-jackson3-avro` ← downstream of org.apache.camel:camel-core-model
   - `camel-jackson3-protobuf` ← downstream of org.apache.camel:camel-core-model
   - `camel-jackson3xml` ← downstream of org.apache.camel:camel-core-model
   - `camel-jacksonxml` ← downstream of org.apache.camel:camel-core-model
   - `camel-jactl` ← downstream of org.apache.camel:camel-core-model
   - `camel-jandex` ← downstream of org.apache.camel:camel-core-model
   - `camel-jasypt` ← downstream of org.apache.camel:camel-core-model
   - `camel-java-io` ← downstream of org.apache.camel:camel-core-engine
   - `camel-java-joor-dsl` ← downstream of org.apache.camel:camel-main
   - `camel-javascript` ← downstream of org.apache.camel:camel-core-model
   - `camel-jaxb` ← downstream of org.apache.camel:camel-core-model
   - `camel-jbang-console` ← downstream of org.apache.camel:camel-support
   - `camel-jbang-core` ← dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json, dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreaker.java, dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java
   - `camel-jbang-mcp` ← downstream of org.apache.camel:camel-catalog
   - `camel-jbang-plugin-mcp` ← downstream of org.apache.camel:camel-jbang-core
   - `camel-jbang-plugin-route-parser` ← downstream of org.apache.camel:camel-jbang-core
   - `camel-jbang-plugin-tui` ← dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerInfo.java, dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java, dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java, dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/circuit-breaker.md, dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTabRenderTest.java
   - `camel-jbang-plugin-validate` ← downstream of org.apache.camel:camel-jbang-core
   - `camel-jcache` ← downstream of org.apache.camel:camel-core-model
   - `camel-jcr` ← downstream of org.apache.camel:camel-core-model
   - `camel-jdbc` ← downstream of org.apache.camel:camel-core-model
   - `camel-jetty` ← downstream of org.apache.camel:camel-core-model
   - `camel-jetty-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-jfr` ← downstream of org.apache.camel:camel-core-model
   - `camel-jgroups` ← downstream of org.apache.camel:camel-core-model
   - `camel-jgroups-raft` ← downstream of org.apache.camel:camel-core-model
   - `camel-jira` ← downstream of org.apache.camel:camel-core-model
   - `camel-jms` ← downstream of org.apache.camel:camel-core-model
   - `camel-jmx` ← downstream of org.apache.camel:camel-core-model
   - `camel-jolt` ← downstream of org.apache.camel:camel-core-model
   - `camel-jooq` ← downstream of org.apache.camel:camel-core-model
   - `camel-joor` ← downstream of org.apache.camel:camel-core-model
   - `camel-jpa` ← downstream of org.apache.camel:camel-core-model
   - `camel-jq` ← downstream of org.apache.camel:camel-core-model
   - `camel-jsch` ← downstream of org.apache.camel:camel-core-model
   - `camel-jslt` ← downstream of org.apache.camel:camel-core-model
   - `camel-json-validator` ← downstream of org.apache.camel:camel-core-model
   - `camel-jsonapi` ← downstream of org.apache.camel:camel-core-model
   - `camel-jsonata` ← downstream of org.apache.camel:camel-core-model
   - `camel-jsonb` ← downstream of org.apache.camel:camel-core-model
   - `camel-jsonpath` ← downstream of org.apache.camel:camel-core-model
   - `camel-jsoup` ← downstream of org.apache.camel:camel-core-model
   - `camel-jt400` ← downstream of org.apache.camel:camel-core-model
   - `camel-jta` ← downstream of org.apache.camel:camel-core-engine
   - `camel-jte` ← downstream of org.apache.camel:camel-core-model
   - `camel-kafka` ← downstream of org.apache.camel:camel-resilience4j
   - `camel-kamelet` ← downstream of org.apache.camel:camel-core-model
   - `camel-kamelet-main-support` ← downstream of org.apache.camel:camel-main
   - `camel-keycloak` ← downstream of org.apache.camel:camel-core-model
   - `camel-knative` ← downstream of org.apache.camel:camel-core-model
   - `camel-knative-api` ← downstream of org.apache.camel:camel-core-model
   - `camel-knative-http` ← downstream of org.apache.camel:camel-core-model
   - `camel-knative-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-kserve` ← downstream of org.apache.camel:camel-core-model
   - `camel-kubernetes` ← downstream of org.apache.camel:camel-core-model
   - `camel-kudu` ← downstream of org.apache.camel:camel-core-model
   - `camel-langchain4j-agent` ← downstream of org.apache.camel:camel-core-model
   - `camel-langchain4j-agent-api` ← downstream of org.apache.camel:camel-api
   - `camel-langchain4j-chat` ← downstream of org.apache.camel:camel-core-model
   - `camel-langchain4j-core` ← downstream of org.apache.camel:camel-core-model
   - `camel-langchain4j-embeddings` ← downstream of org.apache.camel:camel-core-model
   - `camel-langchain4j-embeddingstore` ← downstream of org.apache.camel:camel-core-model
   - `camel-langchain4j-embeddingstore-api` ← downstream of org.apache.camel:camel-api
   - `camel-langchain4j-ingest` ← downstream of org.apache.camel:camel-core-model
   - `camel-langchain4j-tokenizer` ← downstream of org.apache.camel:camel-core-model
   - `camel-langchain4j-web-search` ← downstream of org.apache.camel:camel-core-model
   - `camel-language` ← downstream of org.apache.camel:camel-core-model
   - `camel-launcher-container` ← downstream of org.apache.camel:camel-launcher
   - `camel-ldap` ← downstream of org.apache.camel:camel-core-model
   - `camel-ldif` ← downstream of org.apache.camel:camel-core-model
   - `camel-log` ← downstream of org.apache.camel:camel-core-model
   - `camel-lra` ← downstream of org.apache.camel:camel-core-engine
   - `camel-lucene` ← downstream of org.apache.camel:camel-core-model
   - `camel-lumberjack` ← downstream of org.apache.camel:camel-core-model
   - `camel-lzf` ← downstream of org.apache.camel:camel-core-model
   - `camel-mail` ← downstream of org.apache.camel:camel-core-model
   - `camel-mail-microsoft-oauth` ← downstream of org.apache.camel:camel-core-model
   - `camel-main` ← downstream of org.apache.camel:camel-api
   - `camel-management` ← downstream of org.apache.camel:camel-core-engine
   - `camel-management-api` ← downstream of org.apache.camel:camel-api
   - `camel-mapstruct` ← downstream of org.apache.camel:camel-core-model
   - `camel-master` ← downstream of org.apache.camel:camel-core-model
   - `camel-maven-plugin` ← downstream of org.apache.camel:camel-core
   - `camel-mcp-server` ← downstream of org.apache.camel:camel-core-model
   - `camel-mcp-server-api` ← downstream of org.apache.camel:camel-core-model
   - `camel-mdc` ← downstream of org.apache.camel:camel-core-model
   - `camel-metrics` ← downstream of org.apache.camel:camel-core-model
   - `camel-micrometer` ← downstream of org.apache.camel:camel-core-model
   - `camel-micrometer-observability` ← downstream of org.apache.camel:camel-core-model
   - `camel-micrometer-prometheus` ← downstream of org.apache.camel:camel-core-model
   - `camel-microprofile-config` ← downstream of org.apache.camel:camel-core-model
   - `camel-microprofile-fault-tolerance` ← components/camel-microprofile/camel-microprofile-fault-tolerance/src/generated/resources/META-INF/org/apache/camel/dev-console/fault-tolerance.json, components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceConsole.java, components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceProcessor.java, components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java, components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceConsoleTest.java, components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultT
 oleranceOpenWithFallbackExchangePropertyTest.java
   - `camel-microprofile-health` ← downstream of org.apache.camel:camel-core-model
   - `camel-microprofile-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-milo` ← downstream of org.apache.camel:camel-core-model
   - `camel-milvus` ← downstream of org.apache.camel:camel-core-model
   - `camel-mina` ← downstream of org.apache.camel:camel-core-model
   - `camel-mina-sftp` ← downstream of org.apache.camel:camel-core-model
   - `camel-minio` ← downstream of org.apache.camel:camel-core-model
   - `camel-mllp` ← downstream of org.apache.camel:camel-core-model
   - `camel-mock` ← downstream of org.apache.camel:camel-core-model
   - `camel-mongodb` ← downstream of org.apache.camel:camel-core-model
   - `camel-mongodb-gridfs` ← downstream of org.apache.camel:camel-core-model
   - `camel-mustache` ← downstream of org.apache.camel:camel-core-model
   - `camel-mvel` ← downstream of org.apache.camel:camel-core-model
   - `camel-mybatis` ← downstream of org.apache.camel:camel-core-model
   - `camel-nats` ← downstream of org.apache.camel:camel-core-model
   - `camel-neo4j` ← downstream of org.apache.camel:camel-core-model
   - `camel-netty` ← downstream of org.apache.camel:camel-core-model
   - `camel-netty-http` ← downstream of org.apache.camel:camel-resilience4j
   - `camel-oaipmh` ← downstream of org.apache.camel:camel-core-model
   - `camel-oauth` ← downstream of org.apache.camel:camel-core-model
   - `camel-observability-services` ← downstream of org.apache.camel:camel-core-model
   - `camel-observation` ← downstream of org.apache.camel:camel-core-model
   - `camel-ocsf` ← downstream of org.apache.camel:camel-core-model
   - `camel-ognl` ← downstream of org.apache.camel:camel-core-model
   - `camel-olingo2` ← downstream of org.apache.camel:camel-core-model
   - `camel-olingo2-api` ← downstream of org.apache.camel:camel-core-model
   - `camel-olingo2-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-olingo4` ← downstream of org.apache.camel:camel-core-model
   - `camel-olingo4-api` ← downstream of org.apache.camel:camel-core-model
   - `camel-olingo4-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-once` ← downstream of org.apache.camel:camel-core-model
   - `camel-opa` ← downstream of org.apache.camel:camel-core-model
   - `camel-openai` ← downstream of org.apache.camel:camel-core-model
   - `camel-openapi-java` ← downstream of org.apache.camel:camel-core-engine
   - `camel-openapi-rest-dsl-generator` ← downstream of org.apache.camel:camel-core-engine
   - `camel-openapi-validator` ← downstream of org.apache.camel:camel-core-model
   - `camel-opensearch` ← downstream of org.apache.camel:camel-core-model
   - `camel-openstack` ← downstream of org.apache.camel:camel-core-model
   - `camel-opentelemetry` ← downstream of org.apache.camel:camel-core-model
   - `camel-opentelemetry-metrics` ← downstream of org.apache.camel:camel-core-model
   - `camel-opentelemetry2` ← downstream of org.apache.camel:camel-core-model
   - `camel-optaplanner` ← downstream of org.apache.camel:camel-core-model
   - `camel-paho` ← downstream of org.apache.camel:camel-core-model
   - `camel-paho-mqtt5` ← downstream of org.apache.camel:camel-core-model
   - `camel-parquet-avro` ← downstream of org.apache.camel:camel-core-model
   - `camel-pdf` ← downstream of org.apache.camel:camel-core-model
   - `camel-pg-replication-slot` ← downstream of org.apache.camel:camel-core-model
   - `camel-pgevent` ← downstream of org.apache.camel:camel-core-model
   - `camel-pgvector` ← downstream of org.apache.camel:camel-core-model
   - `camel-pinecone` ← downstream of org.apache.camel:camel-core-model
   - `camel-platform-http` ← downstream of org.apache.camel:camel-core-model
   - `camel-platform-http-jolokia` ← downstream of org.apache.camel:camel-core-model
   - `camel-platform-http-main` ← downstream of org.apache.camel:camel-core-model
   - `camel-platform-http-vertx` ← downstream of org.apache.camel:camel-core-model
   - `camel-plc4x` ← downstream of org.apache.camel:camel-core-model
   - `camel-pqc` ← downstream of org.apache.camel:camel-core-model
   - `camel-printer` ← downstream of org.apache.camel:camel-core-model
   - `camel-protobuf` ← downstream of org.apache.camel:camel-core-model
   - `camel-pubnub` ← downstream of org.apache.camel:camel-core-model
   - `camel-pulsar` ← downstream of org.apache.camel:camel-core-model
   - `camel-python` ← downstream of org.apache.camel:camel-core-model
   - `camel-python3` ← downstream of org.apache.camel:camel-core-model
   - `camel-qdrant` ← downstream of org.apache.camel:camel-core-model
   - `camel-quartz` ← downstream of org.apache.camel:camel-core-model
   - `camel-quickfix` ← downstream of org.apache.camel:camel-core-model
   - `camel-quickjs` ← downstream of org.apache.camel:camel-core-model
   - `camel-reactive-streams` ← downstream of org.apache.camel:camel-core-engine
   - `camel-reactor` ← downstream of org.apache.camel:camel-core-model
   - `camel-redis` ← downstream of org.apache.camel:camel-core-model
   - `camel-ref` ← downstream of org.apache.camel:camel-core-model
   - `camel-resilience4j` ← components/camel-resilience4j/src/generated/resources/META-INF/org/apache/camel/dev-console/resilience4j.json, components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceConsole.java, components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java, components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceCallCountersTest.java, components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceConsoleTest.java, components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceOpenWithFallbackExchangePropertyTest.java
   - `camel-resilience4j-micrometer` ← downstream of org.apache.camel:camel-resilience4j
   - `camel-resourceresolver-github` ← downstream of org.apache.camel:camel-core-model
   - `camel-rest` ← downstream of org.apache.camel:camel-core-engine
   - `camel-rest-openapi` ← downstream of org.apache.camel:camel-core-model
   - `camel-rest-postman` ← downstream of org.apache.camel:camel-core-model
   - `camel-restdsl-openapi-plugin` ← downstream of org.apache.camel:camel-openapi-rest-dsl-generator
   - `camel-robotframework` ← downstream of org.apache.camel:camel-core-model
   - `camel-rocketmq` ← downstream of org.apache.camel:camel-core-model
   - `camel-rss` ← downstream of org.apache.camel:camel-core-model
   - `camel-rxjava` ← downstream of org.apache.camel:camel-core-model
   - `camel-saga` ← downstream of org.apache.camel:camel-core-model
   - `camel-salesforce` ← downstream of org.apache.camel:camel-core-engine
   - `camel-salesforce-codegen` ← downstream of org.apache.camel:camel-core-model
   - `camel-salesforce-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-sap-netweaver` ← downstream of org.apache.camel:camel-core-model
   - `camel-saxon` ← downstream of org.apache.camel:camel-core-model
   - `camel-scheduler` ← downstream of org.apache.camel:camel-core-model
   - `camel-schematron` ← downstream of org.apache.camel:camel-core-model
   - `camel-seda` ← downstream of org.apache.camel:camel-core-model
   - `camel-servicenow` ← downstream of org.apache.camel:camel-core-model
   - `camel-servicenow-maven-plugin` ← downstream of org.apache.camel:camel-core-engine
   - `camel-servicenow-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-servlet` ← downstream of org.apache.camel:camel-core-model
   - `camel-shell` ← downstream of org.apache.camel:camel-core-model
   - `camel-shiro` ← downstream of org.apache.camel:camel-core-model
   - `camel-sjms` ← downstream of org.apache.camel:camel-core-model
   - `camel-sjms2` ← downstream of org.apache.camel:camel-core-model
   - `camel-slack` ← downstream of org.apache.camel:camel-core-model
   - `camel-smb` ← downstream of org.apache.camel:camel-core-model
   - `camel-smooks` ← downstream of org.apache.camel:camel-core-model
   - `camel-smpp` ← downstream of org.apache.camel:camel-core-model
   - `camel-snakeyaml` ← downstream of org.apache.camel:camel-core-model
   - `camel-snmp` ← downstream of org.apache.camel:camel-core-model
   - `camel-soap` ← downstream of org.apache.camel:camel-core-model
   - `camel-solr` ← downstream of org.apache.camel:camel-core-model
   - `camel-spiffe` ← downstream of org.apache.camel:camel-core-model
   - `camel-splunk-hec` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring` ← downstream of org.apache.camel:camel-core-engine
   - `camel-spring-ai-chat` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-ai-embeddings` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-ai-image` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-ai-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-ai-vector-store` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-batch` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-cloud-config` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-jdbc` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-ldap` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-main` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-rabbitmq` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-redis` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-security` ← downstream of org.apache.camel:camel-core-engine
   - `camel-spring-ws` ← downstream of org.apache.camel:camel-core-model
   - `camel-spring-xml` ← downstream of org.apache.camel:camel-core-model
   - `camel-sql` ← downstream of org.apache.camel:camel-core-model
   - `camel-ssh` ← downstream of org.apache.camel:camel-core-model
   - `camel-state-store` ← downstream of org.apache.camel:camel-core-model
   - `camel-state-store-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-stax` ← downstream of org.apache.camel:camel-core-model
   - `camel-stitch` ← downstream of org.apache.camel:camel-core-model
   - `camel-stream` ← downstream of org.apache.camel:camel-core-model
   - `camel-streamcaching-test` ← downstream of org.apache.camel:camel-core
   - `camel-stringtemplate` ← downstream of org.apache.camel:camel-core-model
   - `camel-stripe` ← downstream of org.apache.camel:camel-core-model
   - `camel-stub` ← downstream of org.apache.camel:camel-core-model
   - `camel-support` ← downstream of org.apache.camel:camel-api
   - `camel-swift` ← downstream of org.apache.camel:camel-core-model
   - `camel-syslog` ← downstream of org.apache.camel:camel-core-model
   - `camel-tahu` ← downstream of org.apache.camel:camel-api
   - `camel-tarfile` ← downstream of org.apache.camel:camel-core-model
   - `camel-telegram` ← downstream of org.apache.camel:camel-core-model
   - `camel-telemetry` ← downstream of org.apache.camel:camel-core-model
   - `camel-telemetry-dev` ← downstream of org.apache.camel:camel-core-model
   - `camel-tensorflow-serving` ← downstream of org.apache.camel:camel-core-model
   - `camel-test-infra-all` ← downstream of org.apache.camel:camel-test-infra-artemis
   - `camel-test-infra-artemis` ← downstream of org.apache.camel:camel-test-junit6
   - `camel-test-infra-cli` ← downstream of org.apache.camel:camel-support
   - `camel-test-infra-core` ← downstream of org.apache.camel:camel-core
   - `camel-test-infra-smb` ← downstream of org.apache.camel:camel-test-junit6
   - `camel-test-junit5` ← downstream of org.apache.camel:camel-core-engine
   - `camel-test-junit6` ← downstream of org.apache.camel:camel-core-engine
   - `camel-test-main-junit5` ← downstream of org.apache.camel:camel-core-model
   - `camel-test-main-junit6` ← downstream of org.apache.camel:camel-core-model
   - `camel-test-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-test-spring-junit5` ← downstream of org.apache.camel:camel-core-model
   - `camel-test-spring-junit6` ← downstream of org.apache.camel:camel-core-model
   - `camel-thrift` ← downstream of org.apache.camel:camel-core-model
   - `camel-thymeleaf` ← downstream of org.apache.camel:camel-core-model
   - `camel-tika` ← downstream of org.apache.camel:camel-core-model
   - `camel-timer` ← downstream of org.apache.camel:camel-core-model
   - `camel-tooling-maven` ← downstream of org.apache.camel:camel-support
   - `camel-toon` ← downstream of org.apache.camel:camel-core-model
   - `camel-tracing` ← downstream of org.apache.camel:camel-core-model
   - `camel-twilio` ← downstream of org.apache.camel:camel-core-model
   - `camel-twitter` ← downstream of org.apache.camel:camel-core-model
   - `camel-ubl` ← downstream of org.apache.camel:camel-core-model
   - `camel-undertow` ← downstream of org.apache.camel:camel-core-model
   - `camel-undertow-spring-security` ← downstream of org.apache.camel:camel-core-model
   - `camel-univocity-parsers` ← downstream of org.apache.camel:camel-core-model
   - `camel-validator` ← downstream of org.apache.camel:camel-core-model
   - `camel-velocity` ← downstream of org.apache.camel:camel-core-model
   - `camel-vertx` ← downstream of org.apache.camel:camel-core-model
   - `camel-vertx-common` ← downstream of org.apache.camel:camel-core-model
   - `camel-vertx-http` ← downstream of org.apache.camel:camel-core-model
   - `camel-vertx-parent` ← downstream of org.apache.camel:camel-core-model
   - `camel-vertx-websocket` ← downstream of org.apache.camel:camel-core-model
   - `camel-wal` ← downstream of org.apache.camel:camel-core-model
   - `camel-wasm` ← downstream of org.apache.camel:camel-core-model
   - `camel-weather` ← downstream of org.apache.camel:camel-core-model
   - `camel-weaviate` ← downstream of org.apache.camel:camel-core-model
   - `camel-web3j` ← downstream of org.apache.camel:camel-core-model
   - `camel-webhook` ← downstream of org.apache.camel:camel-core-model
   - `camel-whatsapp` ← downstream of org.apache.camel:camel-core-model
   - `camel-wordpress` ← downstream of org.apache.camel:camel-core-model
   - `camel-workday` ← downstream of org.apache.camel:camel-core-model
   - `camel-xchange` ← downstream of org.apache.camel:camel-core-model
   - `camel-xj` ← downstream of org.apache.camel:camel-core-model
   - `camel-xml-io` ← downstream of org.apache.camel:camel-core-engine
   - `camel-xml-io-dsl` ← downstream of org.apache.camel:camel-core-engine
   - `camel-xml-jaxb` ← downstream of org.apache.camel:camel-core-engine
   - `camel-xml-jaxb-dsl` ← downstream of org.apache.camel:camel-main
   - `camel-xml-jaxb-dsl-test-definition` ← downstream of org.apache.camel:camel-core-model
   - `camel-xml-jaxb-dsl-test-spring` ← downstream of org.apache.camel:camel-support
   - `camel-xml-jaxp` ← downstream of org.apache.camel:camel-core-model
   - `camel-xmlsecurity` ← downstream of org.apache.camel:camel-core-model
   - `camel-xmpp` ← downstream of org.apache.camel:camel-core-model
   - `camel-xpath` ← downstream of org.apache.camel:camel-core-model
   - `camel-xslt` ← downstream of org.apache.camel:camel-core-model
   - `camel-xslt-saxon` ← downstream of org.apache.camel:camel-core-model
   - `camel-yaml-dsl-common` ← downstream of org.apache.camel:camel-core-engine
   - `camel-yaml-dsl-validator` ← downstream of org.apache.camel:camel-catalog
   - `camel-yaml-dsl-validator-maven-plugin` ← downstream of org.apache.camel:camel-yaml-dsl-validator
   - `camel-yaml-io` ← downstream of org.apache.camel:camel-core-engine
   - `camel-zendesk` ← downstream of org.apache.camel:camel-core-model
   - `camel-zip-deflater` ← downstream of org.apache.camel:camel-core-model
   - `camel-zipfile` ← downstream of org.apache.camel:camel-core-model
   - `camel-zookeeper` ← downstream of org.apache.camel:camel-core-model
   - `camel-zookeeper-master` ← downstream of org.apache.camel:camel-core-model
   - `components` ← downstream of org.apache.camel:camel-core-model
   - `docs` ← docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
   
   </details>
   
   <details><summary>Modules with tests skipped (25)</summary>
   
   - `apache-camel`
   - `camel-allcomponents`
   - `camel-catalog-console`
   - `camel-catalog-maven`
   - `camel-catalog-suggest`
   - `camel-componentdsl`
   - `camel-endpointdsl`
   - `camel-endpointdsl-support`
   - `camel-itest`
   - `camel-jbang-it`
   - `camel-jbang-main`
   - `camel-jbang-plugin-edit`
   - `camel-jbang-plugin-generate`
   - `camel-jbang-plugin-kubernetes`
   - `camel-jbang-plugin-test`
   - `camel-kamelet-main`
   - `camel-launcher`
   - `camel-report-maven-plugin`
   - `camel-route-parser`
   - `camel-salesforce-maven-plugin`
   - `camel-yaml-dsl`
   - `camel-yaml-dsl-deserializers`
   - `camel-yaml-dsl-maven-plugin`
   - `coverage`
   - `dummy-component`
   
   </details>
   
   > :information_source: Shadow mode β€” Scalpel observes but does not affect test execution. [Learn more](https://github.com/maveniverse/scalpel)
   
   </details>
   
   :warning: **Some tests are disabled on GitHub Actions** (`@DisabledIfSystemProperty(named = "ci.env.name")`) and require manual verification:
   
   - `dsl/camel-jbang/camel-jbang-core`: 2 test(s) disabled on GitHub Actions
   
   :bulb: **Manual integration tests recommended:**
   
   > You modified `dsl/camel-jbang/camel-jbang-core`. The related integration tests in `dsl/camel-jbang/camel-jbang-it` are excluded from CI. Consider running them manually:
   > ```
   > mvn verify -f dsl/camel-jbang/camel-jbang-it -Djbang-it-test
   > ```
   
   <details><summary>Build reactor β€” dependencies compiled but only changed modules were tested (9 modules, 2m 47s total)</summary>
   
   **Total reactor time:** 2m 47s
   
   | Module | Duration | Status |
   | --- | --- | --- |
   | Camel :: Resilience4j | 41.9s | SUCCESS |
   | Camel :: Catalog :: Camel Catalog | 28.2s | SUCCESS |
   | Camel :: MicroProfile :: Fault Tolerance | 27.4s | SUCCESS |
   | Camel :: JBang :: Plugin :: TUI | 25.7s | SUCCESS |
   | Camel :: Docs | 19.6s | SUCCESS |
   | Camel :: API | 11.8s | SUCCESS |
   | Camel :: Core Model | 10.4s | SUCCESS |
   | Camel :: Core Engine | 1.4s | SUCCESS |
   | Camel :: JBang :: Core | n/a |  |
   
   **Top 20 slowest modules:**
   - `Camel :: Resilience4j` (41.9s)
   - `Camel :: Catalog :: Camel Catalog` (28.2s)
   - `Camel :: MicroProfile :: Fault Tolerance` (27.4s)
   - `Camel :: JBang :: Plugin :: TUI` (25.7s)
   - `Camel :: Docs` (19.6s)
   - `Camel :: API` (11.8s)
   - `Camel :: Core Model` (10.4s)
   - `Camel :: Core Engine` (1.4s)
   
   </details>
   
   ---
   :gear: [View full build and test results](https://github.com/apache/camel/actions/runs/35362419059)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #26603:
URL: https://github.com/apache/camel/pull/26603#discussion_r4050131770


##########
components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java:
##########
@@ -0,0 +1,180 @@
+/*
+ * 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.component.microprofile.faulttolerance;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit breaker listeners do not tell apart.
+ */
+public class FaultToleranceCallCountersTest extends CamelTestSupport {
+
+    private final CountDownLatch permitAcquired = new CountDownLatch(1);
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbDown", FaultToleranceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbSlow", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);
+
+        // the first call holds the only bulkhead permit while it is slow,
+        // so the second call is rejected by the bulkhead and answered by the fallback
+        template.asyncSendBody("direct:bulkhead", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call should be inside the bulkhead");
+        template.sendBody("direct:bulkhead", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        // the rejected call reached the fallback with the bulkhead exception (and came back first,
+        // before the slow call finished), so it was rejected, not queued
+        long rejected = getMockEndpoint("mock:bulkhead").getExchanges().stream()
+                .filter(e -> e.getProperty(Exchange.EXCEPTION_CAUGHT) instanceof BulkheadException).count();
+        assertEquals(1, rejected, "exactly one of the two calls should have been rejected by the bulkhead");
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkhead", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls(),
+                "the rejected call is counted from inside the fallback path");
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+    }
+
+    @Test
+    public void testTimedOutWithoutFallbackCounter() throws Exception {
+        // no onFallback: the timeout fails the exchange, and the counter is kept on that path too
+        Exchange result = template.request("direct:slowNoFallback", e -> e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the timed out call should fail the exchange");
+        FaultToleranceProcessor cb = context.getProcessor("cbSlowNoFallback", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithoutFallbackCounter() throws Exception {

Review Comment:
   **Nit:** The comment "SmallRye needs a waiting queue of at least 1; the second call is still rejected, not queued" misrepresents the reason. `FaultToleranceProcessor` calls `typedGuard.call(task)` synchronously (`TypedGuard<Exchange>` returning `Exchange`, not `CompletionStage<Exchange>`). For synchronous actions, SmallRye FT uses a semaphore bulkhead where `queueSize()` has no effect β€” calls beyond the concurrency limit are immediately rejected regardless of queue size.
   
   The `bulkheadWaitingTaskQueue(1)` setting is silently ignored by the semaphore bulkhead, so the second call is rejected for the right reason but the explanation is wrong. A future developer could be misled into thinking they need to keep the queue β‰₯ 1 to make this work.
   
   ```suggestion
           // FaultToleranceProcessor uses TypedGuard.call() which is a synchronous action in SmallRye FT terms.
           // Synchronous actions use a semaphore bulkhead β€” queueSize has no effect, so the second concurrent
           // call is rejected immediately regardless of bulkheadWaitingTaskQueue.
           assertInstanceOf(BulkheadException.class, result.getException());
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #26603:
URL: https://github.com/apache/camel/pull/26603#issuecomment-5732197117

   <!-- regen-patch -->
   ### :warning: Generated files are out of date
   
   The [CI build](https://github.com/apache/camel/actions/runs/35360948161) regenerated files that are not committed on this PR.
   
   If *Allow edits from maintainers* is enabled, a maintainer bot will automatically
   push a "Regen" commit to this branch shortly.
   
   Otherwise, you can apply the patch manually β€” the missing changes are available
   as the `regen-patch` artifact of that run:
   
   ```bash
   gh pr checkout 26603 --repo apache/camel
   gh run download 35360948161 --repo apache/camel -n regen-patch
   git apply --index regen.patch && rm regen.patch pr-number
   git commit -m "Regen" && git push
   ```
   
   Alternatively, run `./etc/scripts/regen.sh` locally and commit the resulting changes.
   If the patch does not apply cleanly, update the branch from the base branch first β€”
   the patch was generated against the merge with the base branch.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on code in PR #26603:
URL: https://github.com/apache/camel/pull/26603#discussion_r4050074871


##########
components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java:
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.component.microprofile.faulttolerance;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit breaker listeners do not tell apart.
+ */
+public class FaultToleranceCallCountersTest extends CamelTestSupport {
+
+    private final CountDownLatch permitAcquired = new CountDownLatch(1);
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbDown", FaultToleranceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbSlow", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);
+
+        // the first call holds the only bulkhead permit while it is slow,
+        // so the second call is rejected by the bulkhead and answered by the fallback
+        template.asyncSendBody("direct:bulkhead", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call should be inside the bulkhead");
+        template.sendBody("direct:bulkhead", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkhead", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls(),
+                "the rejected call is counted from inside the fallback path");
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+    }
+
+    @Test
+    public void testTimedOutWithoutFallbackCounter() throws Exception {
+        // no onFallback: the timeout fails the exchange, and the counter is kept on that path too
+        Exchange result = template.request("direct:slowNoFallback", e -> e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the timed out call should fail the exchange");
+        FaultToleranceProcessor cb = context.getProcessor("cbSlowNoFallback", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithoutFallbackCounter() throws Exception {
+        // no onFallback: the second call is rejected by the bulkhead and fails the exchange
+        template.asyncSendBody("direct:bulkheadNoFallback", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call should be inside the bulkhead");
+        Exchange result = template.request("direct:bulkheadNoFallback", e -> e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the rejected call should fail the exchange");
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkheadNoFallback", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:down")
+                        .circuitBreaker().id("cbDown")
+                        .faultToleranceConfiguration()
+                        .requestVolumeThreshold(2).failureRatio(50).delay(60000).end()
+                        .throwException(new IllegalStateException("Service is down"))
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:down");
+
+                from("direct:slow")
+                        .circuitBreaker().id("cbSlow")
+                        .faultToleranceConfiguration().timeoutEnabled(true).timeoutDuration(200).end()
+                        .to("direct:slowService")
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:slow");
+
+                from("direct:slowService")
+                        .delay(2000).transform().constant("Slow response");
+
+                from("direct:bulkhead")
+                        .circuitBreaker().id("cbBulkhead")
+                        .faultToleranceConfiguration()
+                        .bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadWaitingTaskQueue(1).end()

Review Comment:
   I tried the suggestion: SmallRye rejects it at route start with `IllegalArgumentException: Queue size must be >= 1, was 0`, so a queue of 0 is not possible for the fault tolerance bulkhead. With `bulkheadWaitingTaskQueue(1)` the second call is rejected, not queued: the test already asserted the counter at 1, and it now also asserts that exactly one of the two exchanges carries a `BulkheadException` in `EXCEPTION_CAUGHT` (with fallback) or as the exchange's exception (without fallback). The rejected call also reaches the mock first, before the slow call finishes, which is the other tell that it never waited in the queue. Five tests pass.



##########
components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java:
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.component.microprofile.faulttolerance;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit breaker listeners do not tell apart.
+ */
+public class FaultToleranceCallCountersTest extends CamelTestSupport {
+
+    private final CountDownLatch permitAcquired = new CountDownLatch(1);
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbDown", FaultToleranceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbSlow", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);
+
+        // the first call holds the only bulkhead permit while it is slow,
+        // so the second call is rejected by the bulkhead and answered by the fallback
+        template.asyncSendBody("direct:bulkhead", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call should be inside the bulkhead");
+        template.sendBody("direct:bulkhead", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkhead", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls(),
+                "the rejected call is counted from inside the fallback path");
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+    }
+
+    @Test
+    public void testTimedOutWithoutFallbackCounter() throws Exception {
+        // no onFallback: the timeout fails the exchange, and the counter is kept on that path too
+        Exchange result = template.request("direct:slowNoFallback", e -> e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the timed out call should fail the exchange");
+        FaultToleranceProcessor cb = context.getProcessor("cbSlowNoFallback", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithoutFallbackCounter() throws Exception {
+        // no onFallback: the second call is rejected by the bulkhead and fails the exchange
+        template.asyncSendBody("direct:bulkheadNoFallback", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call should be inside the bulkhead");
+        Exchange result = template.request("direct:bulkheadNoFallback", e -> e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the rejected call should fail the exchange");
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkheadNoFallback", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:down")
+                        .circuitBreaker().id("cbDown")
+                        .faultToleranceConfiguration()
+                        .requestVolumeThreshold(2).failureRatio(50).delay(60000).end()
+                        .throwException(new IllegalStateException("Service is down"))
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:down");
+
+                from("direct:slow")
+                        .circuitBreaker().id("cbSlow")
+                        .faultToleranceConfiguration().timeoutEnabled(true).timeoutDuration(200).end()
+                        .to("direct:slowService")
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:slow");
+
+                from("direct:slowService")
+                        .delay(2000).transform().constant("Slow response");
+
+                from("direct:bulkhead")
+                        .circuitBreaker().id("cbBulkhead")
+                        .faultToleranceConfiguration()
+                        .bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadWaitingTaskQueue(1).end()
+                        .process(e -> permitAcquired.countDown())
+                        .to("direct:slowService")
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:bulkhead");
+
+                from("direct:slowNoFallback")
+                        .circuitBreaker().id("cbSlowNoFallback")
+                        .faultToleranceConfiguration().timeoutEnabled(true).timeoutDuration(200).end()
+                        .to("direct:slowService")
+                        .end();
+
+                from("direct:bulkheadNoFallback")
+                        .circuitBreaker().id("cbBulkheadNoFallback")
+                        .faultToleranceConfiguration().bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadWaitingTaskQueue(1).end()

Review Comment:
   I tried the suggestion: SmallRye rejects it at route start with `IllegalArgumentException: Queue size must be >= 1, was 0`, so a queue of 0 is not possible for the fault tolerance bulkhead. With `bulkheadWaitingTaskQueue(1)` the second call is rejected, not queued: the test already asserted the counter at 1, and it now also asserts that exactly one of the two exchanges carries a `BulkheadException` in `EXCEPTION_CAUGHT` (with fallback) or as the exchange's exception (without fallback). The rejected call also reaches the mock first, before the slow call finishes, which is the other tell that it never waited in the queue. Five tests pass.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus merged PR #26603:
URL: https://github.com/apache/camel/pull/26603


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #26603:
URL: https://github.com/apache/camel/pull/26603#discussion_r4049307825


##########
components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java:
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.component.microprofile.faulttolerance;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit breaker listeners do not tell apart.
+ */
+public class FaultToleranceCallCountersTest extends CamelTestSupport {
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbDown", FaultToleranceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbSlow", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);
+
+        // the first call holds the only bulkhead permit while it is slow,
+        // so the second call is rejected by the bulkhead and answered by the fallback
+        template.asyncSendBody("direct:bulkhead", "Hello World");
+        Thread.sleep(500);
+        template.sendBody("direct:bulkhead", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkhead", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls(),
+                "the rejected call is counted from inside the fallback path");
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {

Review Comment:
   ⚠️ **`Thread.sleep(500)` is a timing assumption, not a synchronisation guarantee.**
   
   The test assumes the first `asyncSendBody` will have acquired the bulkhead permit within 500 ms. The slow service takes 2 s to respond, so the window is comfortable on developer hardware β€” but on a heavily loaded CI worker where thread scheduling can be delayed by several seconds, the second call may arrive before the first one holds the permit, making both calls succeed and the assertion `assertEquals(1, cb.getNumberOfBulkheadRejectedCalls())` fail non-deterministically.
   
   Use a latch to synchronise on permit acquisition instead:
   
   ```java
   CountDownLatch permitAcquired = new CountDownLatch(1);
   // in the route: .process(e -> permitAcquired.countDown()).to("direct:slowService")
   template.asyncSendBody("direct:bulkhead", "Hello World");
   permitAcquired.await(5, TimeUnit.SECONDS);
   template.sendBody("direct:bulkhead", "Hello World");
   ```
   
   Same issue exists in `ResilienceCallCountersTest.testBulkheadRejectedWithFallbackCounter` β€” both should be fixed.



##########
components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java:
##########
@@ -409,11 +429,14 @@ public String getCircuitBreakerState() {
         }
     }
 
-    @ManagedOperation(description = "Transitions the circuit breaker to CLOSED state.")
+    @ManagedOperation(description = "Transitions the circuit breaker to CLOSED state and resets the fallback, timed out and bulkhead rejected call counters.")

Review Comment:
   ⚠️ **Counter reset gap: `transitionToOpenState()` and `transitionToHalfOpenState()` don't reset the new counters.**
   
   `transitionToCloseState()` now resets `fallbackCalls`, `timedOutCalls`, and `bulkheadRejectedCalls` (lines 430-432). The other two manual transition operations don't, so forcing the breaker OPEN or HALF_OPEN via JMX leaves stale counter values from the previous cycle β€” a user looking at the JMX attributes after `transitionToOpenState()` will see counters that no longer reflect the current state.
   
   `FaultToleranceProcessor` has no equivalent `transitionToOpenState`, so this is specific to `ResilienceProcessor`. Fix:
   
   ```suggestion
           fallbackCalls.set(0);
           timedOutCalls.set(0);
           bulkheadRejectedCalls.set(0);
       }
   
       @ManagedOperation(description = "Transitions the circuit breaker to OPEN state.")
       public void transitionToOpenState() {
           if (circuitBreaker != null) {
               circuitBreaker.transitionToOpenState();
           }
           fallbackCalls.set(0);
           timedOutCalls.set(0);
           bulkheadRejectedCalls.set(0);
   ```
   
   (Same reset block needed in `transitionToHalfOpenState()` and `transitionToForcedOpenState()`.)



##########
dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java:
##########
@@ -78,6 +78,9 @@ void testShowsClosedCircuitBreaker() throws Exception {
             String output = printer.getOutput();
             assertTrue(output.contains("myCB"), "Should show circuit breaker ID");
             assertTrue(output.contains("CLOSED"), "Should show CLOSED state");
+            assertTrue(output.contains("FALLBACK"), "Should show FALLBACK column");
+            assertTrue(output.contains("TIMEOUT"), "Should show TIMEOUT column");

Review Comment:
   Nit: use `assertFalse` instead of `assertTrue(!...)`.
   
   ```suggestion
               assertFalse(output.contains("BULKHEAD"), "Should not show BULKHEAD column without a bulkhead");
   ```
   
   `assertFalse` produces a cleaner failure message and both `assertFalse` and `assertTrue` are already imported in this file.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on code in PR #26603:
URL: https://github.com/apache/camel/pull/26603#discussion_r4049438050


##########
components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java:
##########
@@ -0,0 +1,126 @@
+/*
+ * 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.component.microprofile.faulttolerance;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit breaker listeners do not tell apart.
+ */
+public class FaultToleranceCallCountersTest extends CamelTestSupport {
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbDown", FaultToleranceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbSlow", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);
+
+        // the first call holds the only bulkhead permit while it is slow,
+        // so the second call is rejected by the bulkhead and answered by the fallback
+        template.asyncSendBody("direct:bulkhead", "Hello World");
+        Thread.sleep(500);
+        template.sendBody("direct:bulkhead", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkhead", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls(),
+                "the rejected call is counted from inside the fallback path");
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {

Review Comment:
   Agreed, replaced the sleep with a `CountDownLatch` counted down by a `process` step inside the breaker, right before the slow call, and the test awaits it (5 s) before sending the second call. Same in `ResilienceCallCountersTest`. Both pass.



##########
dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java:
##########
@@ -78,6 +78,9 @@ void testShowsClosedCircuitBreaker() throws Exception {
             String output = printer.getOutput();
             assertTrue(output.contains("myCB"), "Should show circuit breaker ID");
             assertTrue(output.contains("CLOSED"), "Should show CLOSED state");
+            assertTrue(output.contains("FALLBACK"), "Should show FALLBACK column");
+            assertTrue(output.contains("TIMEOUT"), "Should show TIMEOUT column");

Review Comment:
   Done, `assertFalse` it is (the import was not there yet, added).



##########
components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java:
##########
@@ -409,11 +429,14 @@ public String getCircuitBreakerState() {
         }
     }
 
-    @ManagedOperation(description = "Transitions the circuit breaker to CLOSED state.")
+    @ManagedOperation(description = "Transitions the circuit breaker to CLOSED state and resets the fallback, timed out and bulkhead rejected call counters.")

Review Comment:
   I left this as it is, on purpose. The three counters are totals since start, like the fault tolerance processor's success, failed and not-permitted counters, which are cleared by `transitionToCloseState` alone; `transitionToCloseState` is the one operation documented as a reset in both processors, so they stay in step. Clearing them on `transitionToOpenState` or `transitionToForcedOpenState` would hide exactly the history an operator forcing a breaker open wants to see, and a half-open trial is part of the same cycle. If a separate reset is wanted, a `resetCallCounters` operation is the cleaner way to add it, rather than tying it to state transitions.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on code in PR #26603:
URL: https://github.com/apache/camel/pull/26603#discussion_r4049978070


##########
components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java:
##########
@@ -0,0 +1,133 @@
+/*
+ * 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.component.microprofile.faulttolerance;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit breaker listeners do not tell apart.
+ */
+public class FaultToleranceCallCountersTest extends CamelTestSupport {
+
+    private final CountDownLatch permitAcquired = new CountDownLatch(1);
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbDown", FaultToleranceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbSlow", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);

Review Comment:
   Added `testTimedOutWithoutFallbackCounter` and `testBulkheadRejectedWithoutFallbackCounter` here, with routes that have no `onFallback`: the exchange fails, the timed out or bulkhead rejected counter is 1 and `fallbackCalls` stays 0, so both branches are locked in. Five tests per class now, all passing.



##########
components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceCallCountersTest.java:
##########
@@ -0,0 +1,134 @@
+/*
+ * 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.component.resilience4j;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit breaker metrics do not tell apart.
+ */
+public class ResilienceCallCountersTest extends CamelTestSupport {
+
+    private final CountDownLatch permitAcquired = new CountDownLatch(1);
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        ResilienceProcessor cb = context.getProcessor("cbDown", ResilienceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        ResilienceProcessor cb = context.getProcessor("cbSlow", ResilienceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);

Review Comment:
   Added `testTimedOutWithoutFallbackCounter` and `testBulkheadRejectedWithoutFallbackCounter` here, with routes that have no `onFallback`: the exchange fails, the timed out or bulkhead rejected counter is 1 and `fallbackCalls` stays 0, so both branches are locked in. Five tests per class now, all passing.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on PR #26603:
URL: https://github.com/apache/camel/pull/26603#issuecomment-5732697466

   **No test covers the bulkhead-with-fallback counter path.**
   
   Both `FaultToleranceCallCountersTest` and `ResilienceCallCountersTest` test:
   - open-breaker + fallback (`testFallbackAndRejectedCounters`)
   - timeout + fallback (`testTimedOutCounter`)
   
   But neither tests a full bulkhead + fallback scenario β€” the most interesting combination since `bulkheadRejectedCalls` is incremented from the `FallbackTask` in that case (not from the outer catch). The `testShowsFaultToleranceCountersAndBulkheadColumn` in `ListCircuitBreakerTest` only validates JSON serialization against hardcoded values, not actual counter behavior.
   
   Consider adding a `testBulkheadRejectedWithFallbackCounter()` test to each processor test class.
   
   _This review was generated by an AI agent, Hermès on behalf of @gnodet._
   <!-- reviewer: gnodet-bot -->


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #26603:
URL: https://github.com/apache/camel/pull/26603#discussion_r4048948731


##########
core/camel-api/src/main/java/org/apache/camel/spi/CircuitBreakerConstants.java:
##########
@@ -27,13 +27,17 @@ public interface CircuitBreakerConstants {
     @Metadata(label = "circuitBreaker",
               description = "Whether the exchange was processed by the onFallback by the circuit breaker", javaType = "boolean")
     String RESPONSE_FROM_FALLBACK = "CamelCircuitBreakerResponseFromFallback";
-    @Metadata(label = "circuitBreaker", description = "Whether the exchange was short circuited by the breaker",
+    @Metadata(label = "circuitBreaker",
+              description = "Whether the call did not complete normally and the circuit breaker short circuited the exchange, to the onFallback if there is one."
+                            + " This is true for a failed call, a timeout and a rejected call alike; use CamelCircuitBreakerResponseRejected to know whether the call was attempted.",
               javaType = "boolean")
     String RESPONSE_SHORT_CIRCUITED = "CamelCircuitBreakerResponseShortCircuited";
     @Metadata(label = "circuitBreaker", description = "Whether the exchange timed out during processing by the circuit breaker",
               javaType = "boolean")
     String RESPONSE_TIMED_OUT = "CamelCircuitBreakerResponseTimedOut";
-    @Metadata(label = "circuitBreaker", description = "Whether the circuit breaker rejected processing the exchange",
+    @Metadata(label = "circuitBreaker",
+              description = "Whether the circuit breaker rejected the call without attempting it, because the breaker is open or the bulkhead is full."
+                            + " Also set inside the onFallback, where it tells a dead service apart from a single failed call.",
               javaType = "boolean")
     String RESPONSE_REJECTED = "CamelCircuitBreakerResponseRejected";
     @Metadata(label = "circuitBreaker", description = "Whether the circuit breaker ignored an exception during processing",

Review Comment:
   Makes sense β€” the upgrade guide is the right place for migration callouts, and the section in #26602 covers the semantics clearly. No objection.
   
   _This review was generated by an AI agent, Hermès on behalf of @gnodet._
   <!-- reviewer: gnodet-bot -->



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #26603:
URL: https://github.com/apache/camel/pull/26603#issuecomment-5731959666

   :star2: Thank you for your contribution to the Apache Camel project! :star2:
     :robot: CI automation will test this PR automatically.
   
     :camel: Apache Camel Committers, please review the following items:
   
     * First-time contributors **require MANUAL approval** for the GitHub Actions to run
     * You can use the command `/component-test (camel-)component-name1 (camel-)component-name2..` to request a test from the test bot although they are normally detected and executed by CI.
     * You can label PRs using `skip-tests` and `test-dependents` to fine-tune the checks executed by this PR.
     * Build and test logs are available in the summary page. **Only** [Apache Camel committers](https://camel.apache.org/community/team/#committers) have access to the summary.
     
     :warning: Be careful when sharing logs. Review their contents before sharing them publicly.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on PR #26603:
URL: https://github.com/apache/camel/pull/26603#issuecomment-5733983730

   Added `testBulkheadRejectedWithFallbackCounter` to both `ResilienceCallCountersTest` and `FaultToleranceCallCountersTest`: a bulkhead with one permit, the first call holds it while it is slow, the second is rejected by the bulkhead and answered by the fallback, and the test asserts `getNumberOfBulkheadRejectedCalls() == 1`, `getNumberOfFallbackCalls() == 1` and the not-permitted and timed-out counters at zero. That exercises the `instanceof BulkheadFullException` / `BulkheadException` check inside the fallback path, which is the one you pointed at. Both pass locally.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #26603:
URL: https://github.com/apache/camel/pull/26603#discussion_r4050021805


##########
components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java:
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.component.microprofile.faulttolerance;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit breaker listeners do not tell apart.
+ */
+public class FaultToleranceCallCountersTest extends CamelTestSupport {
+
+    private final CountDownLatch permitAcquired = new CountDownLatch(1);
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbDown", FaultToleranceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbSlow", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);
+
+        // the first call holds the only bulkhead permit while it is slow,
+        // so the second call is rejected by the bulkhead and answered by the fallback
+        template.asyncSendBody("direct:bulkhead", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call should be inside the bulkhead");
+        template.sendBody("direct:bulkhead", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkhead", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls(),
+                "the rejected call is counted from inside the fallback path");
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+    }
+
+    @Test
+    public void testTimedOutWithoutFallbackCounter() throws Exception {
+        // no onFallback: the timeout fails the exchange, and the counter is kept on that path too
+        Exchange result = template.request("direct:slowNoFallback", e -> e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the timed out call should fail the exchange");
+        FaultToleranceProcessor cb = context.getProcessor("cbSlowNoFallback", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithoutFallbackCounter() throws Exception {
+        // no onFallback: the second call is rejected by the bulkhead and fails the exchange
+        template.asyncSendBody("direct:bulkheadNoFallback", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call should be inside the bulkhead");
+        Exchange result = template.request("direct:bulkheadNoFallback", e -> e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the rejected call should fail the exchange");
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkheadNoFallback", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:down")
+                        .circuitBreaker().id("cbDown")
+                        .faultToleranceConfiguration()
+                        .requestVolumeThreshold(2).failureRatio(50).delay(60000).end()
+                        .throwException(new IllegalStateException("Service is down"))
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:down");
+
+                from("direct:slow")
+                        .circuitBreaker().id("cbSlow")
+                        .faultToleranceConfiguration().timeoutEnabled(true).timeoutDuration(200).end()
+                        .to("direct:slowService")
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:slow");
+
+                from("direct:slowService")
+                        .delay(2000).transform().constant("Slow response");
+
+                from("direct:bulkhead")
+                        .circuitBreaker().id("cbBulkhead")
+                        .faultToleranceConfiguration()
+                        .bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadWaitingTaskQueue(1).end()

Review Comment:
   ⚠️ **`bulkheadWaitingTaskQueue(1)` will queue the second call rather than reject it β€” the bulkhead counter assertion will always read 0.**
   
   `FaultToleranceProcessor` uses SmallRye FT with `withThreadOffload(true)`, which maps `.queueSize(config.getBulkheadWaitingTaskQueue())` directly to a thread-pool bulkhead queue. With `queueSize=1` and `limit=1`, the bulkhead capacity is 1 running + 1 waiting before any rejection fires. The test only sends 2 concurrent calls: the first is executing and the second queues (it doesn't get rejected). The `countDownLatch` await guarantees the first is inside before the second is sent, but the queue swallows it silently. `getNumberOfBulkheadRejectedCalls()` stays 0, and `assertEquals(1, ...)` would fail β€” or, if the test infrastructure serialises them, it passes vacuously because the second call runs normally after the first finishes.
   
   For immediate rejection you need an empty queue (`queueSize=0`). Compare with the resilience4j side, which correctly uses `bulkheadMaxWaitDuration(0)` for the same effect.
   
   ```suggestion
                           .bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadWaitingTaskQueue(0).end()
   ```



##########
components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java:
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.component.microprofile.faulttolerance;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit breaker listeners do not tell apart.
+ */
+public class FaultToleranceCallCountersTest extends CamelTestSupport {
+
+    private final CountDownLatch permitAcquired = new CountDownLatch(1);
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbDown", FaultToleranceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbSlow", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);
+
+        // the first call holds the only bulkhead permit while it is slow,
+        // so the second call is rejected by the bulkhead and answered by the fallback
+        template.asyncSendBody("direct:bulkhead", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call should be inside the bulkhead");
+        template.sendBody("direct:bulkhead", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkhead", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls(),
+                "the rejected call is counted from inside the fallback path");
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+    }
+
+    @Test
+    public void testTimedOutWithoutFallbackCounter() throws Exception {
+        // no onFallback: the timeout fails the exchange, and the counter is kept on that path too
+        Exchange result = template.request("direct:slowNoFallback", e -> e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the timed out call should fail the exchange");
+        FaultToleranceProcessor cb = context.getProcessor("cbSlowNoFallback", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithoutFallbackCounter() throws Exception {
+        // no onFallback: the second call is rejected by the bulkhead and fails the exchange
+        template.asyncSendBody("direct:bulkheadNoFallback", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call should be inside the bulkhead");
+        Exchange result = template.request("direct:bulkheadNoFallback", e -> e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the rejected call should fail the exchange");
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkheadNoFallback", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:down")
+                        .circuitBreaker().id("cbDown")
+                        .faultToleranceConfiguration()
+                        .requestVolumeThreshold(2).failureRatio(50).delay(60000).end()
+                        .throwException(new IllegalStateException("Service is down"))
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:down");
+
+                from("direct:slow")
+                        .circuitBreaker().id("cbSlow")
+                        .faultToleranceConfiguration().timeoutEnabled(true).timeoutDuration(200).end()
+                        .to("direct:slowService")
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:slow");
+
+                from("direct:slowService")
+                        .delay(2000).transform().constant("Slow response");
+
+                from("direct:bulkhead")
+                        .circuitBreaker().id("cbBulkhead")
+                        .faultToleranceConfiguration()
+                        .bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadWaitingTaskQueue(1).end()
+                        .process(e -> permitAcquired.countDown())
+                        .to("direct:slowService")
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:bulkhead");
+
+                from("direct:slowNoFallback")
+                        .circuitBreaker().id("cbSlowNoFallback")
+                        .faultToleranceConfiguration().timeoutEnabled(true).timeoutDuration(200).end()
+                        .to("direct:slowService")
+                        .end();
+
+                from("direct:bulkheadNoFallback")
+                        .circuitBreaker().id("cbBulkheadNoFallback")
+                        .faultToleranceConfiguration().bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadWaitingTaskQueue(1).end()

Review Comment:
   Same issue as the `direct:bulkhead` route above β€” `bulkheadWaitingTaskQueue(1)` allows queuing, so the second call will not be rejected.
   
   ```suggestion
                           .faultToleranceConfiguration().bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadWaitingTaskQueue(0).end()
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] CAMEL-24819: count fallback, timed out and bulkhead rejected circuit breaker calls [camel]

Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #26603:
URL: https://github.com/apache/camel/pull/26603#discussion_r4048440311


##########
core/camel-api/src/main/java/org/apache/camel/spi/CircuitBreakerConstants.java:
##########
@@ -27,13 +27,17 @@ public interface CircuitBreakerConstants {
     @Metadata(label = "circuitBreaker",
               description = "Whether the exchange was processed by the onFallback by the circuit breaker", javaType = "boolean")
     String RESPONSE_FROM_FALLBACK = "CamelCircuitBreakerResponseFromFallback";
-    @Metadata(label = "circuitBreaker", description = "Whether the exchange was short circuited by the breaker",
+    @Metadata(label = "circuitBreaker",
+              description = "Whether the call did not complete normally and the circuit breaker short circuited the exchange, to the onFallback if there is one."
+                            + " This is true for a failed call, a timeout and a rejected call alike; use CamelCircuitBreakerResponseRejected to know whether the call was attempted.",
               javaType = "boolean")
     String RESPONSE_SHORT_CIRCUITED = "CamelCircuitBreakerResponseShortCircuited";
     @Metadata(label = "circuitBreaker", description = "Whether the exchange timed out during processing by the circuit breaker",
               javaType = "boolean")
     String RESPONSE_TIMED_OUT = "CamelCircuitBreakerResponseTimedOut";
-    @Metadata(label = "circuitBreaker", description = "Whether the circuit breaker rejected processing the exchange",
+    @Metadata(label = "circuitBreaker",
+              description = "Whether the circuit breaker rejected the call without attempting it, because the breaker is open or the bulkhead is full."
+                            + " Also set inside the onFallback, where it tells a dead service apart from a single failed call.",
               javaType = "boolean")
     String RESPONSE_REJECTED = "CamelCircuitBreakerResponseRejected";
     @Metadata(label = "circuitBreaker", description = "Whether the circuit breaker ignored an exception during processing",

Review Comment:
   The description says `RESPONSE_REJECTED` is now "also set inside the onFallback", but it doesn't call out the breaking semantics for existing users: **previously this property was `null` (unset) inside the `onFallback`; now it is always set to `true` or `false`**.
   
   Any user who tested `== null` to detect "call was attempted" will see different behavior. Worth adding a brief note, e.g.:
   
   ```
   + " Prior to Camel 4.X this property was not set inside the onFallback; it is now always present."
   ```
   
   Not a blocker, just a migration callout for the upgrade notes.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org