[ 
https://issues.apache.org/jira/browse/CAMEL-25682?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Work on CAMEL-25682 started by shashank.
----------------------------------------
> camel-resilience4j - Circuit Breaker EIP: a timeout that fires while the task 
> copies its result into the exchange lets the fallback (or the timeout 
> exception) and that copy change the exchange at the same time
> -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-25682
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25682
>             Project: Camel
>          Issue Type: Bug
>          Components: eip
>            Reporter: shashank
>            Assignee: shashank
>            Priority: Major
>
> CAMEL-24134 added a write guard between the task of the circuit breaker (it 
> runs on the timeout executor and copies its result from the correlated copy 
> into the exchange) and the fallback that the caller runs after a timeout. The 
> task claims the guard with {{compareAndSet(false, true)}} and then writes 
> ({{ResilienceProcessor.processTask}}, lines 751-764 at main cd2ce899764e; 
> async mode {{processTaskAsync}} 812-824). The fallback task only does 
> {{exchangeWriteGuard.set(true)}} (line 911) and then writes the exchange. So 
> when the timeout fires after the task has won the guard but before it has 
> finished its write-back, both write the same exchange at the same time:
> * with {{onFallback}}: the task's {{ExchangeHelper.copyResults}} replaces the 
> body of the fallback by the body of the task, or sets 
> {{CamelCircuitBreakerResponseFromFallback}} back to {{false}} and 
> {{CamelCircuitBreakerResponseSuccessfulExecution}} to {{true}} after the 
> fallback has set them, while the exchange already continues in the route;
> * without fallback: {{copyResults}} ends with {{setException(null)}} and 
> clears the {{TimeoutException}} the caller has just set (a timed-out call 
> continues as a success);
> * the headers and properties maps of the exchange are written by two threads.
> The caller does not wait for the task: with a timeout the task runs in 
> {{CompletableFuture.supplyAsync}} (591) behind 
> {{TimeLimiter.decorateFutureSupplier}} (599); async mode uses 
> {{TimeLimiter.decorateCompletionStage}} (672). The window is the time the 
> task needs for {{copyResults}} and two properties, so it needs a task that 
> ends at about the moment of the timeout.
> h3. Reproduction
> * New {{ResilienceTimeoutDuringWriteBackTest}} (4 tests: sync and 
> {{asynchronous=true}}, with and without {{onFallback}}, timeout 500 ms). The 
> task's result message holds the first read of its body by 
> {{ExchangeHelper.copyResults}} (the test checks the stack) until the caller 
> has returned, or until the caller has not returned 2 seconds after the 
> timeout. On main all 4 fail: {{expected: <Fallback result> but was: <Task 
> result>}} (sync and async), and {{expected: 
> <java.util.concurrent.TimeoutException> but was: <null>}} (sync and async).
> * Without the hold: a probe (not part of the change) with 20000 calls from 32 
> threads, timeout 20 ms and a task of 20 ms +/- 0.3 ms, a 64-thread timeout 
> executor: on main 26 exchanges ended inconsistent (for example the fallback 
> body with {{CamelCircuitBreakerResponseFromFallback=false}}); with the fix 0. 
> One run each.
> h3. Proposed fix
> The guard becomes a small lock-based guard per call: the task claims it and 
> writes while holding the lock; the fallback task claims it under the same 
> lock, so a caller that times out while the task is writing waits until that 
> write is done (bounded: {{copyResults}} and two properties). It then sets the 
> timeout result or runs the fallback on the exchange, which then holds the 
> result of the task (as today when the timeout fires just after the copy). The 
> task writes only if it claims the exchange before the caller; the caller 
> always writes after a timeout. In async mode the waiting thread is a thread 
> of the time limiter's scheduler pool, which already runs the fallback. Sync 
> and async mode. One {{ReentrantLock}} per call instead of one 
> {{AtomicBoolean}}.
> camel-microprofile-fault-tolerance has the same {{set(true)}} 
> ({{FaultToleranceProcessor}} 298) but is not affected: the same test 
> (adapted) passes on main, because with SmallRye Fault Tolerance 6.11.4 the 
> caller gets the {{TimeoutException}} only after the task has returned (it 
> waited the whole hold in the test), so the task and the fallback do not 
> overlap.
> camel-resilience4j tests with the fix: 81 run, 0 failures.
> Not changed (follow-ups): an exception thrown by {{copyResults}} after the 
> task claimed the exchange is dropped and the call counts as a success (same 
> on main); and whether a caller that finds the write-back already claimed 
> should keep the task's result instead of running the fallback (it would 
> change the outcome of such calls, so it needs a decision).
> How it was found: the backlog of an earlier review round listed the 
> {{set(true)}}. A small TLA+ model of the guard (task: claim, write body, 
> write properties; caller: timeout at any time before the task returned, 
> claim, fallback, continue) violates "the task does not write after the caller 
> took the exchange over" and "the final exchange tells one story" on main in 
> 18 / 36-49 states, and both hold with the lock (28-31 states). The model is a 
> sketch; the test and the probe are the evidence. The microprofile part of the 
> model assumption (the caller continues while the task runs) turned out to be 
> false for SmallRye, see above.
> Affected: main (cd2ce899764e; tested), camel-4.23.x and camel-4.22.x (same 
> {{set(true)}}; read, not run). camel-4.18.x has no guard at all (before 
> CAMEL-24134).
> Duplicate check (2026-10-15): JIRA text "circuit breaker timeout worker write 
> back" (CAMEL-24134 only, resolved, the fix this completes), 
> "exchangeWriteGuard" (CAMEL-25129, CAMEL-24209: other topics); GitHub pull 
> requests "exchangeWriteGuard", "FaultToleranceProcessor" (#24797 = 
> CAMEL-24134, merged; nothing open).
> _Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to