[
https://issues.apache.org/jira/browse/CAMEL-24136?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen resolved CAMEL-24136.
---------------------------------
Resolution: Fixed
> Circuit Breaker EIP - umbrella for medium findings (FT exchange-property
> contract, parity gaps, dead option, docs)
> ------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-24136
> URL: https://issues.apache.org/jira/browse/CAMEL-24136
> Project: Camel
> Issue Type: Bug
> Components: camel-microprofile-fault-tolerance, eip
> Affects Versions: 4.21.0
> Reporter: Federico Mariani
> Assignee: Claus Ibsen
> Priority: Major
> Fix For: 4.22.0
>
> Attachments: camel-circuitbreaker-eip-review-2026-07.md
>
>
> Umbrella for the medium-severity findings from a review of the Circuit
> Breaker EIP (core model, camel-resilience4j,
> camel-microprofile-fault-tolerance). Full review document attached. Related
> bugs filed separately: CAMEL-24133 (pooled-mode exchange corruption),
> CAMEL-24134 (timeout write-back race), CAMEL-24135 (JMX metric).
> h3. M1. FT: no-fallback timeout/short-circuit paths never set the documented
> exchange properties
> When there is no fallback, a guard exception lands in
> {{FaultToleranceProcessor.process()}} {{catch (Exception e) ->
> exchange.setException(e)}}; {{CamelCircuitBreakerResponseTimedOut}} and
> {{...SuccessfulExecution=false}} are never set. The branch that would set
> them (inside {{CircuitBreakerFallbackTask}}, the {{fallbackProcessor ==
> null}} block) is unreachable dead code, because the fallback task is only
> acquired when a fallback exists. The resilience4j implementation sets these
> properties in the same scenario. No test in either module asserts
> {{RESPONSE_TIMED_OUT}} at all.
> h3. M2. FT: guard-level exceptions invisible to the fallback
> ({{CamelExceptionCaught}} = null)
> Guard-raised exceptions (SmallRye {{TimeoutException}},
> {{CircuitBreakerOpenException}}, bulkhead rejection) are never stored on the
> exchange before the fallback runs, so the fallback route sees
> {{exchange.getException() == null}} and {{EXCEPTION_CAUGHT}} is set to null.
> Only exceptions thrown by the protected processor itself are visible. In the
> resilience4j implementation the throwable is always available to the recover
> function. Fallback routes that branch on the cause behave differently between
> the two implementations.
> h3. M3. FT feature-parity gaps vs the resilience4j implementation
> * no {{recordExceptions}} / {{ignoreExceptions}} equivalent - SmallRye's
> circuit breaker builder supports {{failOn()}} / {{skipOn()}}, so this is
> wireable; {{CamelCircuitBreakerResponseIgnored}} is never set by FT;
> * no {{throwExceptionWhenHalfOpenOrOpenState}} equivalent (open circuit + no
> fallback always silently continues routing);
> * live metrics: FT JMX exposes only configuration echoes; {{getFailureRate}}
> is described as "the current failure rate in percentage" but returns the
> *configured* ratio as a 0-1 fraction. The FT dev console is likewise
> config-only, while the resilience4j console shows live state and counters.
> h3. M4. FT: {{timeoutPoolSize}} option is dead
> Parsed by the reifier and echoed in JMX, but never applied to the
> {{TypedGuard}} (the standalone TypedGuard API has no pool-size knob; the old
> timer-thread wiring was dropped in the CAMEL-21857 TypedGuard migration).
> Either wire it or deprecate it with an upgrade-guide note.
> h3. M5. resilience4j: circuit breaker state and metrics are destroyed on
> route stop/suspend
> {{ResilienceProcessor.doStop()}} removes the breaker/timelimiter/bulkhead
> from the registries, so a route suspend/resume or stop/start resets breaker
> state (an OPEN breaker becomes CLOSED) and zeroes metrics. FT does the same
> via {{CircuitBreakerMaintenance.reset(id)}}. If intentional, it should at
> least be documented.
> h3. M6. Documentation errors
> * {{resilience4j-eip.adoc}} "supports two options" table lists
> {{resilienceConfiguration}} / {{resilienceConfigurationRef}} - neither
> exists; the actual names are {{resilience4jConfiguration}} and
> {{configuration}};
> * {{circuitBreaker-eip.adoc}} says "By default, the timeout request is just
> 1000ms" - timeout is *disabled* by default; 1000ms is only the default
> duration once enabled;
> * {{resilience4j-eip.adoc}} "If there was no fallback, then the circuit
> breaker will throw an exception" is wrong for the open-circuit case: by
> default ({{throwExceptionWhenHalfOpenOrOpenState=false}}) the message
> continues routing unchanged without an exception (behavior confirmed by
> {{*RouteRejectedTest}} in both implementations).
> h3. M7. Smaller observations
> * FT {{getCircuitBreakerState()}} uses exception-as-control-flow per message;
> with a user-supplied {{TypedGuard}} the {{CircuitBreakerMaintenance}} lookup
> throws on every exchange;
> * when an existing breaker/guard is referenced ({{circuitBreaker}} /
> {{typedGuard}} option), the remaining inline configuration is silently
> ignored (FT: timeout/bulkhead; resilience4j: CB config except record/ignore
> predicates) - should be documented or logged;
> * ignored/not-recorded exceptions are cleared and the message continues with
> the request body; tested and intended, but it diverges from resilience4j's
> own semantics (where ignored exceptions still propagate) and deserves an
> explicit doc note.
> _Filed by Claude Code on behalf of Federico Mariani (fmariani)_
--
This message was sent by Atlassian Jira
(v8.20.10#820010)