Skip to content

CAMEL-24134: Circuit Breaker EIP - prevent timed-out worker from writing back to exchange#24797

Merged
davsclaus merged 2 commits into
mainfrom
fix/CAMEL-24134
Jul 16, 2026
Merged

CAMEL-24134: Circuit Breaker EIP - prevent timed-out worker from writing back to exchange#24797
davsclaus merged 2 commits into
mainfrom
fix/CAMEL-24134

Conversation

@davsclaus

Copy link
Copy Markdown
Contributor

Summary

Claude Code on behalf of davsclaus

Both ResilienceProcessor and FaultToleranceProcessor have a race condition where the worker thread writes results back to the original exchange after a timeout has triggered fallback processing on the caller thread. This can:

  • Overwrite the fallback result with the late worker result
  • Reinstate an exception after the fallback cleared it
  • Corrupt the exchange through non-atomic interleaving of ExchangeHelper.copyResults

Fix: Add a shared AtomicBoolean guard between the worker task and the fallback task. The worker uses compareAndSet(false, true) before writing back — if the fallback already claimed the exchange, the write-back is skipped. The fallback sets the guard to true before touching the exchange.

Both the normal result path and the exception catch path in the worker are guarded.

Note: This PR depends on CAMEL-24133 (also included in this branch) which fixes a related pooled exchange factory release issue in the same processors.

Test plan

  • New ResilienceTimeoutWriteBackRaceTest — slow processor (3s) with short timeout (500ms), verifies fallback result survives after worker completes
  • New FaultToleranceTimeoutWriteBackRaceTest — same pattern for MicroProfile Fault Tolerance
  • All existing resilience4j tests pass
  • All existing microprofile-fault-tolerance tests pass

🤖 Generated with Claude Code

davsclaus and others added 2 commits July 16, 2026 14:41
… original exchange in pooled mode

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…ing back to exchange

Both ResilienceProcessor and FaultToleranceProcessor had a race condition
where the worker thread could write results back to the original exchange
after a timeout triggered fallback processing on the caller thread. This
could overwrite fallback results, reinstate cleared exceptions, or corrupt
the exchange through non-atomic interleaving of ExchangeHelper.copyResults.

Fix: add a shared AtomicBoolean guard between the worker task and the
fallback. The first to claim it (via CAS) gets to write to the exchange;
the other skips the write-back entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 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 have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: CAMEL-24134 — Circuit Breaker EIP - prevent timed-out worker from writing back to exchange

Verdict: Approve

Well-designed concurrency fix. The race condition is real: when a circuit breaker timeout fires, the fallback runs on the caller thread while the worker may still be executing on its thread. Without a guard, the worker can overwrite the fallback result via ExchangeHelper.copyResults() or exchange.setException().

Guard design is correct:

The shared AtomicBoolean exchangeWriteGuard provides mutual exclusion on write-back:

  • Worker uses compareAndSet(false, true) — atomic claim-or-skip before both the success path (copyResults) and the exception path (setException)
  • Fallback uses set(true) — unconditionally claims the guard before processing

This handles all orderings correctly:

  1. Fallback firstset(true) → worker's CAS fails → worker skips write-back ✓
  2. Worker first → CAS succeeds → worker writes, then fallback set(true) and overwrites → final state is the fallback result, which is the correct outcome ✓
  3. No timeout → worker CAS succeeds → writes normally, no fallback invoked ✓

Both implementations are consistent:

  • ResilienceProcessor: guard shared between CircuitBreakerTask and CircuitBreakerFallbackTask, passed to processTask() as a parameter. The call() and get() methods both forward it correctly.
  • FaultToleranceProcessor: guard set on task field, fallback handled in process() catch block with exchangeWriteGuard.set(true) before fallback processing.
  • Both include null guards for defensive safety, and both include the CAMEL-24133 fix (release(copy) instead of release(exchange)).

Tests are well-designed:
Both *TimeoutWriteBackRaceTest classes create a deterministic race (3s sleep vs 500ms timeout), verify the fallback result wins, then use a CountDownLatch to wait for the worker to fully complete and re-verify no late write-back occurred.

Minor observation (non-blocking):
The test's process() lambda has misleading indentation — exchange.getIn().setBody("Worker result") appears to be outside the try block visually but is syntactically inside it. The formatter should fix this.

Reviewed with Claude Code (claude-code/1.0.3) on behalf of gnodet. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-microprofile/camel-microprofile-fault-tolerance
  • components/camel-resilience4j

ℹ️ 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.


🔬 Scalpel shadow comparison — Scalpel: 34 tested, 29 compile-only — current: 34 all tested

Maveniverse Scalpel detected 63 affected modules (current approach: 34).

⚠️ Modules only in Scalpel (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • 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

Skip-tests mode would test 34 modules (2 direct + 32 downstream), skip tests for 29 (generated code, meta-modules)

Modules Scalpel would test (34)
  • camel-a2a
  • camel-clickup
  • camel-java-joor-dsl
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-jsonpath
  • camel-kafka
  • camel-knative-http
  • camel-launcher-container
  • camel-micrometer-prometheus
  • camel-microprofile-fault-tolerance
  • camel-netty-http
  • camel-oauth
  • camel-observability-services
  • camel-openapi-validator
  • camel-platform-http-main
  • camel-platform-http-vertx
  • camel-resilience4j
  • camel-resilience4j-micrometer
  • camel-rest-openapi
  • camel-telegram
  • camel-test-main-junit5
  • camel-test-main-junit6
  • camel-webhook
  • camel-whatsapp
  • camel-xml-io-dsl
  • camel-xml-jaxb-dsl
  • camel-xml-jaxb-dsl-test-definition
  • camel-xml-jaxb-dsl-test-spring
  • camel-yaml-dsl-validator
  • camel-yaml-dsl-validator-maven-plugin
Modules with tests skipped (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • 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

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

Build reactor — dependencies compiled but only changed modules were tested (63 modules)
  • Camel :: AI :: A2A
  • Camel :: All Components Sync point
  • Camel :: Assembly
  • Camel :: Catalog :: CSimple Maven Plugin (deprecated)
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Lucene (deprecated)
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Suggest
  • Camel :: ClickUp
  • Camel :: Component DSL
  • Camel :: Coverage
  • Camel :: Docs
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL :: Support
  • Camel :: Integration Tests
  • Camel :: JBang :: Core
  • Camel :: JBang :: Integration tests
  • Camel :: JBang :: MCP
  • Camel :: JBang :: Main
  • Camel :: JBang :: Plugin :: Edit
  • Camel :: JBang :: Plugin :: Generate
  • Camel :: JBang :: Plugin :: Kubernetes
  • Camel :: JBang :: Plugin :: MCP
  • Camel :: JBang :: Plugin :: Route Parser
  • Camel :: JBang :: Plugin :: TUI
  • Camel :: JBang :: Plugin :: Testing
  • Camel :: JBang :: Plugin :: Validate
  • Camel :: JSon Path
  • Camel :: Java DSL with jOOR
  • Camel :: Kafka
  • Camel :: Kamelet Main
  • Camel :: Knative HTTP
  • Camel :: Launcher
  • Camel :: Launcher :: Container
  • Camel :: MicroProfile :: Fault Tolerance
  • Camel :: Micrometer :: Prometheus
  • Camel :: Netty HTTP
  • Camel :: OAuth
  • Camel :: Observability Services
  • Camel :: OpenAPI :: Validator
  • Camel :: Platform HTTP :: Main
  • Camel :: Platform HTTP :: Vert.x
  • Camel :: REST OpenApi
  • Camel :: Resilience4j
  • Camel :: Resilience4j :: Micrometer
  • Camel :: Telegram
  • Camel :: Test :: Main :: JUnit5
  • Camel :: Test :: Main :: JUnit6
  • Camel :: Webhook
  • Camel :: Whatsapp
  • Camel :: XML DSL Jaxb :: Test :: Definition
  • Camel :: XML DSL Jaxb :: Test :: Spring
  • Camel :: XML DSL with camel-xml-io
  • Camel :: XML DSL with camel-xml-jaxb
  • Camel :: YAML DSL
  • Camel :: YAML DSL :: Deserializers
  • Camel :: YAML DSL :: Maven Plugins
  • Camel :: YAML DSL :: Validator
  • Camel :: YAML DSL :: Validator Maven Plugin

⚙️ View full build and test results

@davsclaus davsclaus added this to the 4.22.0 milestone Jul 16, 2026
@davsclaus davsclaus self-assigned this Jul 16, 2026
@davsclaus davsclaus added the bug Something isn't working label Jul 16, 2026
@davsclaus
davsclaus merged commit e8a372d into main Jul 16, 2026
5 checks passed
@davsclaus
davsclaus deleted the fix/CAMEL-24134 branch July 16, 2026 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants