Skip to content

CAMEL-26701: camel-chaos - a fault caught by a circuit breaker fallback counts as completed, and a recovered fault marks no later error - #29363

Merged
davsclaus merged 5 commits into
mainfrom
CAMEL-26701-chaos-effect-breaker
Nov 6, 2026
Merged

davsclaus merged 5 commits into
mainfrom
CAMEL-26701-chaos-effect-breaker

Conversation

@davsclaus

@davsclaus davsclaus commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

CAMEL-26701: two bugs from a hands-on round of the chaos screen, using a demo shop with a payment call behind a resilience4j circuit breaker that has a fallback.

1. A fault caught by a circuit breaker fallback counted as failed

The rule on the payment call showed 156 failed, while the payment route had 0 failed and every order shipped through the fallback. The circuit breaker runs its steps on a correlated copy of the exchange, with its own unit of work (ResilienceProcessor.processTask). Chaos faulted the copy and counted the effect when the copy's unit of work was done, so as failed. The exchange itself was never counted.

Now the faults of a copy made inside a try block (a correlated copy with TRY_ROUTE_BLOCK, which the circuit breaker sets) count when the exchange they are a copy of is done, with its outcome:

  • completed when the fallback took over;
  • handled by the dead letter channel;
  • recovered when a redelivery succeeded;
  • failed otherwise.

A message that the circuit breaker runs again at a redelivery (a new copy each time) counts once (Faults.merge).

The exchange is not touched from the copy's thread, as a circuit breaker with a timeout runs its fallback on the caller while the copy may still run. The faults wait under the exchange id in ChaosManager until its ExchangeCompletedEvent or ExchangeFailedEvent. The notifier listens for completed events only while something waits (isIgnoreExchangeCompletedEvent), so it costs nothing otherwise. The map is bounded. A wait with no event within a minute counts with how the copy was done, once, even when a copy merges into it at that moment (it is removed with remove(key, value) before it counts): for example a wire tap inside a doTry, whose exchange may be done before its copy. The inflight repository is not used to find the exchange, as browsing it is off by default.

Without a fallback, a circuit breaker's failure is not redelivered by the error handler of its own route. So the redelivery test puts the circuit breaker in a route called with direct:, from a route with a dead letter channel.

Known limit, in the javadoc: a splitter inside a doTry also makes correlated copies in a try block, so a fault in a split part counts with the parent's outcome (completed when the doCatch caught it).

2. A recovered fault marked a later, real error

CamelChaosRule stayed on an exchange after a redelivery recovered its fault. So a later failure that chaos did not make showed the mark of the old rule on the Errors tab: in the demo, the open circuit breaker's CallNotPermittedException was marked 🎲r1, the rule on the stock call. Now:

  • the property is removed when the error handler redelivers the exchange, and a fault at the redelivery sets it again;
  • the Errors tab trusts an error's properties when it has them (ChaosMarks.ruleOf), and uses the recent faults of the report only when an error has no properties. An error whose properties were not captured has an empty map, which counts as none.

Also

  • camel-resilience4j is now a test dependency of camel-chaos.
  • chaos.adoc describes both; the catalog copy is updated.

Tests

  • ChaosCircuitBreakerEffectTest: the fallback, the dead letter channel without a fallback (one message over three copies), recovered by a redelivery, no mark on a later failure, and the one-minute timeout (two copies of one exchange count once, as handled).
  • All 656 camel-chaos tests pass, and the TUI chaos tests pass (ChaosMarksTest gets a case for an error with properties and no mark).

Claude Code on behalf of Claus Ibsen

🤖 Generated with Claude Code

https://claude.ai/code/session_012nJsFsjqqFiwroeHRWaKbS

…ck counts as completed, and a recovered fault marks no later error

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

github-actions Bot commented Nov 6, 2026

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.

…eeps the mark from the recent faults

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

@gnodet-bot gnodet-bot 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.

The two bug fixes are correct and well-motivated. The circuit breaker copy detection via CORRELATION_ID + TRY_ROUTE_BLOCK is the right approach, and the deferred-counting design through ConcurrentHashMap is sound. One low-severity concurrency gap in countWaitedTooLong worth fixing before merge (see inline).

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • catalog/camel-catalog
  • components/camel-chaos
  • dsl/camel-jbang/camel-jbang-plugin-tui

🔬 Scalpel shadow comparison — Scalpel: 11 of 717 tested, 3 compile-only — current: 11 all tested

Maveniverse Scalpel detected 11 affected modules (current approach: 11).

Skip-tests mode would test 11 modules (3 direct + 0 downstream), skip tests for 3 (generated code, meta-modules)

Modules Scalpel would test (11)
  • camel-chaos ← components/camel-chaos/src/main/docs/chaos.adoc, components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosEventNotifier.java, components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosExchangeState.java, components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java, components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosRule.java, components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosCircuitBreakerEffectTest.java, own pom components/camel-chaos/pom.xml changed
  • camel-cli-connector ← depends on affected reactor module org.apache.camel:camel-chaos
  • camel-cli-debug ← depends on affected reactor module org.apache.camel:camel-cli-connector
  • camel-jbang-mcp ← depends on affected reactor module org.apache.camel:camel-chaos
  • camel-jbang-plugin-mcp ← depends on affected reactor module org.apache.camel:camel-jbang-core
  • camel-jbang-plugin-tui ← dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosMarks.java, dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosMarksTest.java
  • camel-jbang-plugin-validate ← depends on affected reactor module org.apache.camel:camel-jbang-core
  • camel-jbang-run ← depends on affected reactor module org.apache.camel:camel-catalog
  • camel-launcher-container ← depends on affected reactor module org.apache.camel:camel-launcher
  • camel-yaml-dsl-validator ← depends on affected reactor module org.apache.camel:camel-catalog
  • camel-yaml-dsl-validator-maven-plugin ← depends on affected reactor module org.apache.camel:camel-yaml-dsl-validator
Modules with tests skipped (3)
  • camel-itest
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers

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

All tested modules (38 modules, 7m 2s total)

Total reactor time: 7m 2s

Module Duration Status
Camel :: JBang :: Plugin :: TUI 57.6s SUCCESS
Camel :: Chaos 57.2s SUCCESS
Camel :: Launcher 56.7s SUCCESS
Camel :: JBang :: MCP 36.4s SUCCESS
Camel :: Component DSL 33.4s SUCCESS
Camel :: JBang :: Plugin :: Validate 22.9s SUCCESS
Camel :: Docs 20.0s SUCCESS
Camel :: YAML DSL :: Validator 19.2s SUCCESS
Camel :: YAML DSL 17.2s SUCCESS
Camel :: JBang :: Run 16.8s SUCCESS
Camel :: Catalog :: Camel Catalog 16.0s SUCCESS
Camel :: Kamelet Main 10.3s SUCCESS
Camel :: JBang :: Plugin :: Kubernetes 8.8s SUCCESS
Camel :: JBang :: Plugin :: Testing 8.1s SUCCESS
Camel :: Catalog :: Camel Report Maven Plugin 6.3s SUCCESS
Camel :: YAML DSL :: Validator Maven Plugin 5.3s SUCCESS
Camel :: All Components Sync point 4.1s SUCCESS
Camel :: YAML DSL :: Deserializers 3.9s SUCCESS
Camel :: Catalog :: Suggest (deprecated) 3.6s SUCCESS
Camel :: JBang :: Plugin :: Generate 3.6s SUCCESS
Camel :: Catalog :: Camel Route Parser 3.1s SUCCESS
Camel :: Catalog :: Maven 1.8s SUCCESS
Camel :: JBang :: Integration tests 1.4s SUCCESS
Camel :: YAML DSL :: Maven Plugins 1.3s SUCCESS
Camel :: DSL :: CLI Debug 1.3s SUCCESS
Camel :: Assembly 0.9s SUCCESS
Camel :: Catalog :: Dummy Component 0.9s SUCCESS
Camel :: Endpoint DSL :: Support 0.8s SUCCESS
Camel :: Coverage 0.8s SUCCESS
Camel :: JBang :: Plugin :: MCP 0.7s SUCCESS
Camel :: JBang :: Plugin :: Edit 0.6s SUCCESS
Camel :: JBang :: Main 0.5s SUCCESS
Camel :: Launcher :: Container 0.4s SUCCESS
Camel :: Catalog :: Console 0.3s SUCCESS
Camel :: DSL :: CLI Connector n/a
Camel :: Endpoint DSL n/a
Camel :: Integration Tests n/a
Camel :: JBang :: Core n/a

Top 20 slowest modules:

  • Camel :: JBang :: Plugin :: TUI (57.6s)
  • Camel :: Chaos (57.2s)
  • Camel :: Launcher (56.7s)
  • Camel :: JBang :: MCP (36.4s)
  • Camel :: Component DSL (33.4s)
  • Camel :: JBang :: Plugin :: Validate (22.9s)
  • Camel :: Docs (20.0s)
  • Camel :: YAML DSL :: Validator (19.2s)
  • Camel :: YAML DSL (17.2s)
  • Camel :: JBang :: Run (16.8s)
  • Camel :: Catalog :: Camel Catalog (16.0s)
  • Camel :: Kamelet Main (10.3s)
  • Camel :: JBang :: Plugin :: Kubernetes (8.8s)
  • Camel :: JBang :: Plugin :: Testing (8.1s)
  • Camel :: Catalog :: Camel Report Maven Plugin (6.3s)
  • Camel :: YAML DSL :: Validator Maven Plugin (5.3s)
  • Camel :: All Components Sync point (4.1s)
  • Camel :: YAML DSL :: Deserializers (3.9s)
  • Camel :: Catalog :: Suggest (deprecated) (3.6s)
  • Camel :: JBang :: Plugin :: Generate (3.6s)

⚙️ View full build and test results

…, also when a copy merges into them meanwhile

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

@gnodet-bot gnodet-bot 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.

Following up on my earlier review — the previous finding is now fully addressed.

Previous finding — ⚠️ Data race in countWaitedTooLong (removeIf predicate running without bin lock): ✅ Fixed.

The rewrite uses waitingForOriginal.remove(e.getKey(), w) which atomically removes the entry only when the value hasn't been replaced by a concurrent merge(). This is the correct ConcurrentHashMap pattern: if a redelivery copy merges into the entry and replaces the value reference between the iterator read and the remove, remove(key, oldValue) returns false and the count is skipped — the new entry will be counted when the original exchange fires originalDone() or on the next countWaitedTooLong sweep. No double-counting, no missed count.

The rest of the implementation is correct:

  • Faults.merge() runs only inside the ConcurrentHashMap.merge() remapping function (under the bin lock), and after removal in countWaitedTooLong or originalDone(), the Faults instance is exclusive — no concurrent access.
  • The isIgnoreExchangeCompletedEvent() optimization is safe: a missed event is handled by the 60-second countWaitedTooLong fallback (documented known limit).
  • The ChaosMarks.ruleOf() semantic change is correct: empty map = properties not captured (fall back to recent faults), non-empty but no PROPERTY key = not chaos (return null, do not fall back).
  • WaitingFaults.since being reset to copy.since on each redelivery merge is intentional and correct — the 60-second timeout should start from the last copy, not the first.

LGTM.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

davsclaus and others added 2 commits November 6, 2026 23:35
…e merge of main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
@davsclaus davsclaus added this to the 4.24.0 milestone Nov 6, 2026
@davsclaus davsclaus self-assigned this Nov 6, 2026
@davsclaus davsclaus added the bug Something isn't working label Nov 6, 2026
@davsclaus
davsclaus merged commit 91e44cf into main Nov 6, 2026
7 checks passed
@davsclaus
davsclaus deleted the CAMEL-26701-chaos-effect-breaker branch November 6, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants