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",
