You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by "allthingssecurity (via GitHub)" <gi...@apache.org> on 2026/10/16 00:05:09 UTC
[PR] CAMEL-25682: camel-resilience4j - a timed-out call waits for a write-back of the task that is in progress [camel]
allthingssecurity opened a new pull request, #27921:
URL: https://github.com/apache/camel/pull/27921
# Description
[CAMEL-25682](https://issues.apache.org/jira/browse/CAMEL-25682)
CAMEL-24134 added a write guard so that the task of the circuit breaker does not write its result into the exchange after a timeout handed the exchange to the fallback. The task claims the guard with `compareAndSet(false, true)` and then copies its result (`ResilienceProcessor.processTask`, and `processTaskAsync` in asynchronous mode). The fallback task only did `exchangeWriteGuard.set(true)` before writing the exchange. When the timeout fires after the task has won the guard and before it has finished copying, the two write the same exchange at the same time:
- with `onFallback`, the body of the fallback is replaced by the body of the task, or `CamelCircuitBreakerResponseFromFallback` is set back to `false` (and `CamelCircuitBreakerResponseSuccessfulExecution` to `true`) after the fallback set them, while the exchange already continues in the route;
- without fallback, `ExchangeHelper.copyResults` ends with `setException(null)` and clears the `TimeoutException` that the caller has just set;
- headers and properties are written by two threads.
The caller does not wait for the task here (the task runs in `CompletableFuture.supplyAsync` behind `TimeLimiter.decorateFutureSupplier`, or `decorateCompletionStage` in async mode), so the window is the time the task needs to copy its result; it needs a task that ends at about the moment of the timeout.
This change replaces the `AtomicBoolean` by a small per-call guard with a lock. The task claims it and copies its result while holding the lock; the fallback task claims it under the same lock. A caller that times out while the task is copying waits until the copy is done (`copyResults` and two properties). It then sets the timeout result or runs the fallback on the exchange, which at that point holds the result of the task (its body, headers and properties), as on main when the timeout fires just after the copy. The guard only decides whether the task writes: it writes only if it claims the exchange before the caller; the caller always writes after a timeout. Cost: one `ReentrantLock` per call instead of one `AtomicBoolean`, and an uncontended lock/unlock on the write-back. In asynchronous mode the "caller" of a timed-out call is a thread of the time limiter's scheduler pool (by default the same pool that runs the tasks); the fallback already runs there, and that thread can now w
ait for a write-back in progress, which is bounded by the copy.
Not changed:
- camel-microprofile-fault-tolerance has the same `set(true)`, but it is not affected: with SmallRye Fault Tolerance the caller gets the `TimeoutException` only after the task has returned. The same test, adapted to that module, passes on main (the caller waited for the held task).
- When `copyResults` itself throws after the task claimed the exchange, the exception is dropped and the half-copied exchange counts as a success (the second claim in the `catch` fails). main has the same flaw with the `AtomicBoolean`; follow-up.
- Whether a caller that finds the write-back already claimed should keep the result of the task instead of running the fallback: that would change the outcome of such a call (a success after the breaker recorded a timeout), so it is left as a question, not changed here.
- With the pooled exchange factory, a task that is still running after the timeout uses a task object that the caller has already given back to its pool; that is a separate issue and not changed here.
No upgrade guide entry: no option, default or output changes; the only difference is that a caller can wait for a write-back that is in progress.
Tests:
- New `ResilienceTimeoutDuringWriteBackTest` (4 tests: sync and `asynchronous=true`, with and without `onFallback`, timeout 500 ms). The result message of the task holds the first read of its body by `ExchangeHelper.copyResults` (the test checks the stack, so the hold is inside the write-back) until the caller has returned, or has not returned 2 seconds after the timeout. The tests also check that the result is the one of the fallback or the timeout, set on the exchange that holds the result of the task (a header the task set). Without the change all 4 fail: `expected: <Fallback result> but was: <Task result>` and `expected: <java.util.concurrent.TimeoutException> but was: <null>`, sync and async. With the change each test takes about 2.5 s (the caller waits for the held write-back).
- Without the hold, a load run that is not part of this PR (20000 calls from 32 threads, timeout 20 ms, a task of 20 ms +/- 0.3 ms) gave 26 inconsistent exchanges on main and 0 with the change (one run each).
- camel-resilience4j unit tests with the change: 81 run, 0 failures.
# Target
- [x] I checked that the commit is targeting the correct branch (Camel 4 uses the `main` branch)
# Tracking
- [ ] If this is a large change, bug fix, or code improvement, I checked there is a [JIRA issue](https://issues.apache.org/jira/browse/CAMEL) filed for the change (usually before you start working on it).
(The ticket is drafted and not filed yet; CAMEL-25682 is a placeholder until it is.)
# Apache Camel coding standards and style
- [x] I checked that each commit in the pull request has a meaningful subject line and body.
- [ ] I have run `mvn clean install -DskipTests` locally from root folder and I have committed all auto-generated changes.
(I built and tested the affected module, including the formatter and import-sort plugins. I did not run the full root build.)
# AI-assisted contributions
- [x] If this PR includes AI-generated code, commits have proper co-authorship attribution (e.g., `Co-authored-by` trailers) and the PR description identifies the AI tool used.
This PR was prepared with Claude Code (Claude Opus 5.5). The commit carries a `Co-Authored-By` trailer.
_Claude Code on behalf of allthingssecurity_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
--
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-25682: camel-resilience4j - a timed-out call waits for a write-back of the task that is in progress [camel]
Posted by "gnodet-bot (via GitHub)" <gi...@apache.org>.
gnodet-bot commented on code in PR #27921:
URL: https://github.com/apache/camel/pull/27921#discussion_r4289588426
##########
components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java:
##########
@@ -845,10 +863,55 @@ private CompletableFuture<Exchange> processTaskAsync(
return future;
}
+ /**
+ * Makes the write-back of the task and the takeover by the caller (the fallback, or the timeout exception) mutually
+ * exclusive, for one call. The task writes its result only if it claims the exchange before the caller. The caller
+ * always writes: if it takes over while the write-back of the task is in progress, it waits until that is done, and
+ * then works on the exchange that holds the result of the task.
+ */
+ private static final class ExchangeWriteGuard {
+ private final Lock lock = new ReentrantLock();
+ private boolean claimed;
+
+ /**
+ * Claims the exchange for the write-back of the task. When this returns true, the lock is held until
+ * {@link #writeBackDone()} is called.
+ */
+ boolean tryClaimForWriteBack() {
+ lock.lock();
+ if (claimed) {
+ lock.unlock();
+ return false;
+ }
+ claimed = true;
+ return true;
+ }
+
+ /**
+ * Ends the write-back of the task, after {@link #tryClaimForWriteBack()} returned true.
+ */
Review Comment:
`tryClaimForWriteBack()` returns `true` with the lock held — an accidental call without a `writeBackDone()` `finally` block deadlocks the next caller permanently. Document the contract:
```suggestion
/**
* Ends the write-back of the task, after {@link #tryClaimForWriteBack()} returned true.
* Must be called in a {@code finally} block.
*/
```
--
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-25682: camel-resilience4j - a timed-out call waits for a write-back of the task that is in progress [camel]
Posted by "allthingssecurity (via GitHub)" <gi...@apache.org>.
allthingssecurity commented on code in PR #27921:
URL: https://github.com/apache/camel/pull/27921#discussion_r4289667717
##########
components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java:
##########
@@ -845,10 +863,55 @@ private CompletableFuture<Exchange> processTaskAsync(
return future;
}
+ /**
+ * Makes the write-back of the task and the takeover by the caller (the fallback, or the timeout exception) mutually
+ * exclusive, for one call. The task writes its result only if it claims the exchange before the caller. The caller
+ * always writes: if it takes over while the write-back of the task is in progress, it waits until that is done, and
+ * then works on the exchange that holds the result of the task.
+ */
+ private static final class ExchangeWriteGuard {
+ private final Lock lock = new ReentrantLock();
+ private boolean claimed;
+
+ /**
+ * Claims the exchange for the write-back of the task. When this returns true, the lock is held until
+ * {@link #writeBackDone()} is called.
+ */
+ boolean tryClaimForWriteBack() {
+ lock.lock();
+ if (claimed) {
+ lock.unlock();
+ return false;
+ }
+ claimed = true;
+ return true;
+ }
+
+ /**
+ * Ends the write-back of the task, after {@link #tryClaimForWriteBack()} returned true.
+ */
Review Comment:
Thanks, done in 526c608a1d46: the javadoc of `writeBackDone()` now says it must be called in a `finally` block, as the lock stays held until then (javadoc only). On the backport: I agree camel-4.22.x has the same `AtomicBoolean` guard from CAMEL-24134 and camel-4.18.x does not, as the PR description says. I cannot set labels on this repository, so a committer would need to add `port/camel-4.22.x` if wanted.
_Claude Code on behalf of allthingssecurity_
--
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-25682: camel-resilience4j - a timed-out call waits for a write-back of the task that is in progress [camel]
Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #27921:
URL: https://github.com/apache/camel/pull/27921#issuecomment-6211800299
<!-- ci-tested-modules -->
:test_tube: **CI tested the following changed modules:**
- `components/camel-resilience4j`
> :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: 40 of 705 tested, 27 compile-only — current: 40 all tested</summary>
[Maveniverse Scalpel](https://github.com/maveniverse/scalpel) detected **40 affected modules** (current approach: 40).
**Skip-tests mode would test 40 modules** (1 direct + 39 downstream), **skip tests for 27** (generated code, meta-modules)
<details><summary>Modules Scalpel would test (40)</summary>
- `camel-a2a` ← downstream of org.apache.camel:camel-platform-http-vertx
- `camel-clickup` ← downstream of org.apache.camel:camel-netty-http
- `camel-java-joor-dsl` ← downstream of org.apache.camel:camel-telegram
- `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-tui` ← downstream of org.apache.camel:camel-catalog
- `camel-jbang-plugin-validate` ← downstream of org.apache.camel:camel-yaml-dsl-validator
- `camel-jsonpath` ← downstream of org.apache.camel:camel-platform-http-vertx
- `camel-kafka` ← downstream of org.apache.camel:camel-resilience4j
- `camel-knative-http` ← downstream of org.apache.camel:camel-platform-http-vertx
- `camel-launcher-container` ← downstream of org.apache.camel:camel-launcher
- `camel-mcp-server` ← downstream of org.apache.camel:camel-platform-http-vertx
- `camel-micrometer-prometheus` ← downstream of org.apache.camel:camel-platform-http-main
- `camel-netty-http` ← downstream of org.apache.camel:camel-resilience4j
- `camel-oauth` ← downstream of org.apache.camel:camel-netty-http
- `camel-observability-services` ← downstream of org.apache.camel:camel-micrometer-prometheus
- `camel-openai` ← downstream of org.apache.camel:camel-platform-http-vertx
- `camel-openapi-validator` ← downstream of org.apache.camel:camel-rest-openapi
- `camel-platform-http-main` ← downstream of org.apache.camel:camel-platform-http-vertx
- `camel-platform-http-vertx` ← downstream of org.apache.camel:camel-rest-openapi
- `camel-resilience4j` ← components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java, components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceTimeoutDuringWriteBackTest.java
- `camel-resilience4j-micrometer` ← downstream of org.apache.camel:camel-resilience4j
- `camel-rest-openapi` ← downstream of org.apache.camel:camel-netty-http
- `camel-semantic` ← downstream of org.apache.camel:camel-xml-io-dsl
- `camel-semantic-openai` ← downstream of org.apache.camel:camel-semantic
- `camel-semantic-typesafe-ai` ← downstream of org.apache.camel:camel-xml-io-dsl
- `camel-semantic-wolf-defender` ← downstream of org.apache.camel:camel-semantic
- `camel-state-store` ← downstream of org.apache.camel:camel-test-main-junit5
- `camel-telegram` ← downstream of org.apache.camel:camel-netty-http
- `camel-test-main-junit5` ← downstream of org.apache.camel:camel-xml-io-dsl
- `camel-test-main-junit6` ← downstream of org.apache.camel:camel-xml-io-dsl
- `camel-typesafe-ai` ← downstream of org.apache.camel:camel-xml-io-dsl
- `camel-webhook` ← downstream of org.apache.camel:camel-netty-http
- `camel-whatsapp` ← downstream of org.apache.camel:camel-netty-http
- `camel-xml-io-dsl` ← downstream of org.apache.camel:camel-telegram
- `camel-xml-jaxb-dsl` ← downstream of org.apache.camel:camel-telegram
- `camel-xml-jaxb-dsl-test-definition` ← downstream of org.apache.camel:camel-telegram
- `camel-xml-jaxb-dsl-test-spring` ← downstream of org.apache.camel:camel-telegram
- `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
</details>
<details><summary>Modules with tests skipped (27)</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-yaml-dsl`
- `camel-yaml-dsl-deserializers`
- `camel-yaml-dsl-maven-plugin`
- `coverage`
- `docs`
- `dummy-component`
</details>
> :information_source: Shadow mode — Scalpel observes but does not affect test execution. [Learn more](https://github.com/maveniverse/scalpel)
</details>
---
:gear: [View full build and test results](https://github.com/apache/camel/actions/runs/38830166699)
--
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-25682: camel-resilience4j - a timed-out call waits for a write-back of the task that is in progress [camel]
Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #27921:
URL: https://github.com/apache/camel/pull/27921#issuecomment-6211150623
: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-25682: camel-resilience4j - a timed-out call waits for a write-back of the task that is in progress [camel]
Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus merged PR #27921:
URL: https://github.com/apache/camel/pull/27921
--
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