This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch r4b
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 7dc52da468f68d702ebecfe3b3a503ee75e728f0
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jun 25 14:25:05 2025 +0200

    CAMEL-22198: camel-resilience4j - throwExceptionWhenHalfOpenOrOpenState is 
not always thrown if in OPEN / HALF_OPEN state
---
 .../catalog/models/resilience4jConfiguration.json  |  2 +-
 .../apache/camel/catalog/schemas/camel-spring.xsd  |  4 ++-
 .../apache/camel/catalog/schemas/camel-xml-io.xsd  |  4 ++-
 .../resilience4j/ResilienceProcessor.java          | 30 +++++++++++++---------
 .../camel/model/resilience4jConfiguration.json     |  2 +-
 .../model/Resilience4jConfigurationCommon.java     |  5 +++-
 .../model/Resilience4jConfigurationDefinition.java |  5 +++-
 .../dsl/yaml/deserializers/ModelDeserializers.java |  2 +-
 .../generated/resources/schema/camelYamlDsl.json   |  2 +-
 9 files changed, 36 insertions(+), 20 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/resilience4jConfiguration.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/resilience4jConfiguration.json
index dc6cdad47b8..01482269073 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/resilience4jConfiguration.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/resilience4jConfiguration.json
@@ -17,7 +17,7 @@
     "config": { "index": 2, "kind": "attribute", "displayName": "Config", 
"group": "advanced", "label": "advanced", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "Refers to an existing 
io.github.resilience4j.circuitbreaker.CircuitBreakerConfig instance to lookup 
and use from the registry." },
     "failureRateThreshold": { "index": 3, "kind": "attribute", "displayName": 
"Failure Rate Threshold", "group": "common", "required": false, "type": 
"number", "javaType": "java.lang.Float", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": "50", "description": "Configures the 
failure rate threshold in percentage. If the failure rate is equal or greater 
than the threshold the CircuitBreaker transitions to open and starts 
short-circuiting calls. The threshold must  [...]
     "permittedNumberOfCallsInHalfOpenState": { "index": 4, "kind": 
"attribute", "displayName": "Permitted Number Of Calls In Half Open State", 
"group": "advanced", "label": "advanced", "required": false, "type": "integer", 
"javaType": "java.lang.Integer", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "10", "description": "Configures the number of 
permitted calls when the CircuitBreaker is half open. The size must be greater 
than 0. Default size is 10." },
-    "throwExceptionWhenHalfOpenOrOpenState": { "index": 5, "kind": 
"attribute", "displayName": "Throw Exception When Half Open Or Open State", 
"group": "common", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due circuit breaker is half open or open." },
+    "throwExceptionWhenHalfOpenOrOpenState": { "index": 5, "kind": 
"attribute", "displayName": "Throw Exception When Half Open Or Open State", 
"group": "common", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due circuit breaker is half open (and was not attempted [...]
     "slidingWindowSize": { "index": 6, "kind": "attribute", "displayName": 
"Sliding Window Size", "group": "common", "required": false, "type": "integer", 
"javaType": "java.lang.Integer", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "100", "description": "Configures the size of 
the sliding window which is used to record the outcome of calls when the 
CircuitBreaker is closed. slidingWindowSize configures the size of the sliding 
window. Sliding window can eithe [...]
     "slidingWindowType": { "index": 7, "kind": "attribute", "displayName": 
"Sliding Window Type", "group": "advanced", "label": "advanced", "required": 
false, "type": "enum", "javaType": "java.lang.String", "enum": [ "TIME_BASED", 
"COUNT_BASED" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "COUNT_BASED", "description": "Configures the type of the 
sliding window which is used to record the outcome of calls when the 
CircuitBreaker is closed. Sliding window ca [...]
     "minimumNumberOfCalls": { "index": 8, "kind": "attribute", "displayName": 
"Minimum Number Of Calls", "group": "common", "required": false, "type": 
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": "100", "description": "Configures the 
minimum number of calls which are required (per sliding window period) before 
the CircuitBreaker can calculate the error rate. For example, if 
minimumNumberOfCalls is 10, then at least  [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index 1eb90fc79fd..9df220013a0 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -4889,7 +4889,9 @@ size is 10. Default value: 10
             <xs:documentation xml:lang="en">
 <![CDATA[
 Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due circuit
-breaker is half open or open. Default value: false
+breaker is half open (and was not attempted but rejected immediately) or open 
(always rejected). This option is only in
+use when there is NOT a fallback configured on the circuit breaker. When there 
is a fallback then the fallback is always
+executed and CallNotPermittedException is not thrown. Default value: false
 ]]>
             </xs:documentation>
           </xs:annotation>
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
index 11c71226301..68c3cdcafdd 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
@@ -3550,7 +3550,9 @@ size is 10. Default value: 10
             <xs:documentation xml:lang="en">
 <![CDATA[
 Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due circuit
-breaker is half open or open. Default value: false
+breaker is half open (and was not attempted but rejected immediately) or open 
(always rejected). This option is only in
+use when there is NOT a fallback configured on the circuit breaker. When there 
is a fallback then the fallback is always
+executed and CallNotPermittedException is not thrown. Default value: false
 ]]>
             </xs:documentation>
           </xs:annotation>
diff --git 
a/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java
 
b/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java
index 8e9bbcf4e4b..bb0e8a1da0f 100644
--- 
a/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java
+++ 
b/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java
@@ -517,11 +517,16 @@ public class ResilienceProcessor extends 
AsyncProcessorSupport
     }
 
     private Exchange processTask(Exchange exchange) {
+        String state = circuitBreaker.getState().name();
+
         Exchange copy = null;
         UnitOfWork uow = null;
         Throwable cause;
         try {
-            LOG.debug("Running processor: {} with exchange: {}", processor, 
exchange);
+            if (LOG.isTraceEnabled()) {
+                LOG.trace("Processing exchange: {} using circuit breaker 
({}):{} with processor: {}",
+                        exchange.getExchangeId(), state, id, processor);
+            }
             // prepare a copy of exchange so downstream processors don't
             // cause side-effects if they mutate the exchange
             // in case timeout processing and continue with the fallback etc
@@ -627,12 +632,13 @@ public class ResilienceProcessor extends 
AsyncProcessorSupport
 
         @Override
         public Exchange apply(Throwable throwable) {
+            String state = circuitBreaker.getState().name();
+
             // check again if we should ignore or not record the throw 
exception as a failure
             if (ignorePredicate != null && ignorePredicate.test(throwable)) {
                 if (LOG.isTraceEnabled()) {
-                    LOG.trace("Processing exchange: {} recover task using 
circuit breaker: {} ignored exception: {}",
-                            exchange.getExchangeId(),
-                            id, throwable);
+                    LOG.trace("Processing exchange: {} recover task using 
circuit breaker ({}):{} ignored exception: {}",
+                            exchange.getExchangeId(), state, id, throwable);
                 }
                 // exception should be ignored
                 
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_SUCCESSFUL_EXECUTION,
 false);
@@ -644,9 +650,8 @@ public class ResilienceProcessor extends 
AsyncProcessorSupport
             }
             if (recordPredicate != null && !recordPredicate.test(throwable)) {
                 if (LOG.isTraceEnabled()) {
-                    LOG.trace("Processing exchange: {} recover task using 
circuit breaker: {} success exception: {}",
-                            exchange.getExchangeId(),
-                            id, throwable);
+                    LOG.trace("Processing exchange: {} recover task using 
circuit breaker ({}):{} success exception: {}",
+                            exchange.getExchangeId(), state, id, throwable);
                 }
                 // exception is a success
                 
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_SUCCESSFUL_EXECUTION,
 true);
@@ -657,9 +662,8 @@ public class ResilienceProcessor extends 
AsyncProcessorSupport
             }
 
             if (LOG.isTraceEnabled()) {
-                LOG.trace("Processing exchange: {} recover task using circuit 
breaker: {} failed exception: {}",
-                        exchange.getExchangeId(),
-                        id, throwable);
+                LOG.trace("Processing exchange: {} recover task using circuit 
breaker ({}):{} failed exception: {}",
+                        exchange.getExchangeId(), state, id, throwable);
             }
 
             if (fallback == null) {
@@ -721,10 +725,12 @@ public class ResilienceProcessor extends 
AsyncProcessorSupport
             exchange.getExchangeExtension().setRedeliveryExhausted(false);
             // run the fallback processor
             try {
-                LOG.debug("Running fallback: {} with exchange: {}", fallback, 
exchange);
+                if (LOG.isTraceEnabled()) {
+                    LOG.trace("Processing exchange: {} using circuit breaker 
({}):{} with fallback: {}",
+                            exchange.getExchangeId(), state, id, fallback);
+                }
                 // process the fallback until its fully done
                 fallback.process(exchange);
-                LOG.trace("Running fallback: {} with exchange: {} done", 
fallback, exchange);
             } catch (Throwable e) {
                 exchange.setException(e);
             }
diff --git 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/resilience4jConfiguration.json
 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/resilience4jConfiguration.json
index dc6cdad47b8..01482269073 100644
--- 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/resilience4jConfiguration.json
+++ 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/resilience4jConfiguration.json
@@ -17,7 +17,7 @@
     "config": { "index": 2, "kind": "attribute", "displayName": "Config", 
"group": "advanced", "label": "advanced", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "Refers to an existing 
io.github.resilience4j.circuitbreaker.CircuitBreakerConfig instance to lookup 
and use from the registry." },
     "failureRateThreshold": { "index": 3, "kind": "attribute", "displayName": 
"Failure Rate Threshold", "group": "common", "required": false, "type": 
"number", "javaType": "java.lang.Float", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": "50", "description": "Configures the 
failure rate threshold in percentage. If the failure rate is equal or greater 
than the threshold the CircuitBreaker transitions to open and starts 
short-circuiting calls. The threshold must  [...]
     "permittedNumberOfCallsInHalfOpenState": { "index": 4, "kind": 
"attribute", "displayName": "Permitted Number Of Calls In Half Open State", 
"group": "advanced", "label": "advanced", "required": false, "type": "integer", 
"javaType": "java.lang.Integer", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "10", "description": "Configures the number of 
permitted calls when the CircuitBreaker is half open. The size must be greater 
than 0. Default size is 10." },
-    "throwExceptionWhenHalfOpenOrOpenState": { "index": 5, "kind": 
"attribute", "displayName": "Throw Exception When Half Open Or Open State", 
"group": "common", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due circuit breaker is half open or open." },
+    "throwExceptionWhenHalfOpenOrOpenState": { "index": 5, "kind": 
"attribute", "displayName": "Throw Exception When Half Open Or Open State", 
"group": "common", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due circuit breaker is half open (and was not attempted [...]
     "slidingWindowSize": { "index": 6, "kind": "attribute", "displayName": 
"Sliding Window Size", "group": "common", "required": false, "type": "integer", 
"javaType": "java.lang.Integer", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "100", "description": "Configures the size of 
the sliding window which is used to record the outcome of calls when the 
CircuitBreaker is closed. slidingWindowSize configures the size of the sliding 
window. Sliding window can eithe [...]
     "slidingWindowType": { "index": 7, "kind": "attribute", "displayName": 
"Sliding Window Type", "group": "advanced", "label": "advanced", "required": 
false, "type": "enum", "javaType": "java.lang.String", "enum": [ "TIME_BASED", 
"COUNT_BASED" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "COUNT_BASED", "description": "Configures the type of the 
sliding window which is used to record the outcome of calls when the 
CircuitBreaker is closed. Sliding window ca [...]
     "minimumNumberOfCalls": { "index": 8, "kind": "attribute", "displayName": 
"Minimum Number Of Calls", "group": "common", "required": false, "type": 
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": "100", "description": "Configures the 
minimum number of calls which are required (per sliding window period) before 
the CircuitBreaker can calculate the error rate. For example, if 
minimumNumberOfCalls is 10, then at least  [...]
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/Resilience4jConfigurationCommon.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/Resilience4jConfigurationCommon.java
index 01164035d00..aef681e1d1b 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/Resilience4jConfigurationCommon.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/Resilience4jConfigurationCommon.java
@@ -189,7 +189,10 @@ public class Resilience4jConfigurationCommon extends 
IdentifiedType {
 
     /**
      * Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due
-     * circuit breaker is half open or open.
+     * circuit breaker is half open (and was not attempted but rejected 
immediately) or open (always rejected).
+     *
+     * This option is only in use when there is NOT a fallback configured on 
the circuit breaker. When there is a fallback
+     * then the fallback is always executed and CallNotPermittedException is 
not thrown.
      */
     public void setThrowExceptionWhenHalfOpenOrOpenState(String 
throwExceptionWhenHalfOpenOrOpenState) {
         this.throwExceptionWhenHalfOpenOrOpenState = 
throwExceptionWhenHalfOpenOrOpenState;
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/Resilience4jConfigurationDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/Resilience4jConfigurationDefinition.java
index 71481510fbc..227f4c53d6b 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/Resilience4jConfigurationDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/Resilience4jConfigurationDefinition.java
@@ -99,7 +99,10 @@ public class Resilience4jConfigurationDefinition extends 
Resilience4jConfigurati
 
     /**
      * Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due
-     * circuit breaker is half open or open.
+     * circuit breaker is half open (and was not attempted but rejected 
immediately) or open (always rejected).
+     *
+     * This option is only in use when there is NOT a fallback configured on 
the circuit breaker. When there is a fallback
+     * then the fallback is always executed and CallNotPermittedException is 
not thrown.
      */
     public Resilience4jConfigurationDefinition 
throwExceptionWhenHalfOpenOrOpenState(
             boolean throwExceptionWhenHalfOpenOrOpenState) {
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
index eaeb1cfb03f..273f64c0aef 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
@@ -13889,7 +13889,7 @@ public final class ModelDeserializers extends 
YamlDeserializerSupport {
                     @YamlProperty(name = "slidingWindowType", type = 
"enum:TIME_BASED,COUNT_BASED", defaultValue = "COUNT_BASED", description = 
"Configures the type of the sliding window which is used to record the outcome 
of calls when the CircuitBreaker is closed. Sliding window can either be 
count-based or time-based. If slidingWindowType is COUNT_BASED, the last 
slidingWindowSize calls are recorded and aggregated. If slidingWindowType is 
TIME_BASED, the calls of the last slidingWindo [...]
                     @YamlProperty(name = "slowCallDurationThreshold", type = 
"number", defaultValue = "60", description = "Configures the duration threshold 
(seconds) above which calls are considered as slow and increase the slow calls 
percentage. Default value is 60 seconds.", displayName = "Slow Call Duration 
Threshold"),
                     @YamlProperty(name = "slowCallRateThreshold", type = 
"number", defaultValue = "100", description = "Configures a threshold in 
percentage. The CircuitBreaker considers a call as slow when the call duration 
is greater than slowCallDurationThreshold Duration. When the percentage of slow 
calls is equal or greater the threshold, the CircuitBreaker transitions to open 
and starts short-circuiting calls. The threshold must be greater than 0 and not 
greater than 100. Default v [...]
-                    @YamlProperty(name = 
"throwExceptionWhenHalfOpenOrOpenState", type = "boolean", description = 
"Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due circuit breaker is half open or open.", displayName = "Throw 
Exception When Half Open Or Open State"),
+                    @YamlProperty(name = 
"throwExceptionWhenHalfOpenOrOpenState", type = "boolean", description = 
"Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due circuit breaker is half open (and was not attempted but 
rejected immediately) or open (always rejected). This option is only in use 
when there is NOT a fallback configured on the circuit breaker. When there is a 
fallback then the fallback is always executed and Call [...]
                     @YamlProperty(name = "timeoutCancelRunningFuture", type = 
"boolean", description = "Configures whether cancel is called on the running 
future. Defaults to true.", displayName = "Timeout Cancel Running Future"),
                     @YamlProperty(name = "timeoutDuration", type = "number", 
defaultValue = "1000", description = "Configures the thread execution timeout. 
Default value is 1 second.", displayName = "Timeout Duration"),
                     @YamlProperty(name = "timeoutEnabled", type = "boolean", 
description = "Whether timeout is enabled or not on the circuit breaker. 
Default is false.", displayName = "Timeout Enabled"),
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
index 3db8445461e..67e3081ab1d 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
@@ -5136,7 +5136,7 @@
           "throwExceptionWhenHalfOpenOrOpenState" : {
             "type" : "boolean",
             "title" : "Throw Exception When Half Open Or Open State",
-            "description" : "Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due circuit breaker is half open or open."
+            "description" : "Whether to throw 
io.github.resilience4j.circuitbreaker.CallNotPermittedException when the call 
is rejected due circuit breaker is half open (and was not attempted but 
rejected immediately) or open (always rejected). This option is only in use 
when there is NOT a fallback configured on the circuit breaker. When there is a 
fallback then the fallback is always executed and CallNotPermittedException is 
not thrown."
           },
           "timeoutCancelRunningFuture" : {
             "type" : "boolean",

Reply via email to