This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch circuit-breaker-rejected in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
commit f631aaf013ea13a4331c1cb770a204da7def4ad5 Author: Claus Ibsen <[email protected]> AuthorDate: Tue Oct 20 09:55:47 2026 +0200 fail-well/circuit-breaker - the fallback tells a failed call from one the open breaker did not make On Camel 4.23 CamelCircuitBreakerResponseRejected is set in onFallback (CAMEL-24818), so the fallback answers "supplier failed" when the call was made and failed, and "breaker open, supplier not called" when the open breaker rejected it. The README explains both and points at camel get circuit-breaker and its new fallback, rejected and timeout counters (CAMEL-24819); the test checks both lines. Closes #81. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- README.md | 2 +- camel-jbang-example-catalog.json | 2 +- fail-well/README.md | 2 +- fail-well/circuit-breaker/README.md | 37 +++++++++++++++------- .../circuit-breaker/circuit-breaker.camel.yaml | 21 +++++++++--- fail-well/circuit-breaker/metadata.json | 2 +- .../test/circuit-breaker.citrus.it.yaml | 7 +++- 7 files changed, 52 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 278df37..4d4304d 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Retries, a dead letter channel, and a circuit breaker in front of a flaky servic | Example | What you will see | Needs | |---|---|---| | [Error handling](fail-well/error-handling/) | The three orders go to a payment provider: the first is charged at once, the second gets no answer twice and is charged on the third attempt after two retries logged as warnings, and the third is declined, logged as such and parked as a file for manual review. | nothing | -| [Circuit breaker](fail-well/circuit-breaker/) | A stock check calls the supplier every second; the supplier goes down for nine calls, the breaker opens after two failures in its window of four, answers from the fallback while open, tries the supplier again after five seconds and closes once a call succeeds; each line logs the breaker state. | nothing | +| [Circuit breaker](fail-well/circuit-breaker/) | A stock check calls the supplier every second; the supplier goes down for nine calls, the breaker opens after two failures in its window of four, answers from the fallback without calling the supplier while open, tries it again after five seconds and closes once a call succeeds; each line logs the breaker state and whether the supplier failed or was not called. | nothing | ### [Connect without a service](connect/) diff --git a/camel-jbang-example-catalog.json b/camel-jbang-example-catalog.json index 54d2fc4..3dfd6ef 100644 --- a/camel-jbang-example-catalog.json +++ b/camel-jbang-example-catalog.json @@ -868,7 +868,7 @@ { "name": "fail-well/circuit-breaker", "title": "Circuit breaker", - "description": "A stock check calls the supplier every second; the supplier goes down for nine calls, the breaker opens after two failures in its window of four, answers from the fallback while open, tries the supplier again after five seconds and closes once a call succeeds; each line logs the breaker state.", + "description": "A stock check calls the supplier every second; the supplier goes down for nine calls, the breaker opens after two failures in its window of four, answers from the fallback without calling the supplier while open, tries it again after five seconds and closes once a call succeeds; each line logs the breaker state and whether the supplier failed or was not called.", "level": "fail-well", "teaches": { "components": [ diff --git a/fail-well/README.md b/fail-well/README.md index 3612799..8e50e0f 100644 --- a/fail-well/README.md +++ b/fail-well/README.md @@ -6,7 +6,7 @@ Retries, a dead letter channel, and a circuit breaker in front of a flaky servic | Example | What you will see | Needs | |---|---|---| | [Error handling](error-handling/) | The three orders go to a payment provider: the first is charged at once, the second gets no answer twice and is charged on the third attempt after two retries logged as warnings, and the third is declined, logged as such and parked as a file for manual review. | nothing | -| [Circuit breaker](circuit-breaker/) | A stock check calls the supplier every second; the supplier goes down for nine calls, the breaker opens after two failures in its window of four, answers from the fallback while open, tries the supplier again after five seconds and closes once a call succeeds; each line logs the breaker state. | nothing | +| [Circuit breaker](circuit-breaker/) | A stock check calls the supplier every second; the supplier goes down for nine calls, the breaker opens after two failures in its window of four, answers from the fallback without calling the supplier while open, tries it again after five seconds and closes once a call succeeds; each line logs the breaker state and whether the supplier failed or was not called. | nothing | Start with [Error handling](error-handling/); the examples read best in the order above, each one building on what the one before it set up. diff --git a/fail-well/circuit-breaker/README.md b/fail-well/circuit-breaker/README.md index c38d159..a688f20 100644 --- a/fail-well/circuit-breaker/README.md +++ b/fail-well/circuit-breaker/README.md @@ -7,14 +7,18 @@ fallback without calling the supplier, and tries again after five seconds until ## What you will see ```text -INFO ... circuit-breaker.camel.yaml:30 : Stock check 1 (breaker CLOSED): CAMEL-MUG: 47 in stock at the supplier -INFO ... circuit-breaker.camel.yaml:30 : Stock check 2 (breaker CLOSED): CAMEL-MUG: 35 in stock at the supplier -INFO ... circuit-breaker.camel.yaml:30 : Stock check 3 (breaker CLOSED): CAMEL-MUG: 49 in stock at the supplier -INFO ... circuit-breaker.camel.yaml:30 : Stock check 4 (breaker CLOSED): no answer, using last known stock -INFO ... circuit-breaker.camel.yaml:30 : Stock check 5 (breaker OPEN): no answer, using last known stock +INFO ... circuit-breaker.camel.yaml:43 : Stock check 1 (breaker CLOSED): CAMEL-MUG: 24 in stock at the supplier +INFO ... circuit-breaker.camel.yaml:43 : Stock check 2 (breaker CLOSED): CAMEL-MUG: 44 in stock at the supplier +INFO ... circuit-breaker.camel.yaml:43 : Stock check 3 (breaker CLOSED): CAMEL-MUG: 31 in stock at the supplier +INFO ... circuit-breaker.camel.yaml:43 : Stock check 4 (breaker CLOSED): supplier failed, using last known stock +INFO ... circuit-breaker.camel.yaml:43 : Stock check 5 (breaker OPEN): supplier failed, using last known stock +INFO ... circuit-breaker.camel.yaml:43 : Stock check 6 (breaker OPEN): breaker open, supplier not called, using last known stock ... -INFO ... circuit-breaker.camel.yaml:30 : Stock check 14 (breaker OPEN): no answer, using last known stock -INFO ... circuit-breaker.camel.yaml:30 : Stock check 15 (breaker CLOSED): CAMEL-MUG: 58 in stock at the supplier +INFO ... circuit-breaker.camel.yaml:43 : Stock check 10 (breaker OPEN): supplier failed, using last known stock +INFO ... circuit-breaker.camel.yaml:43 : Stock check 11 (breaker OPEN): breaker open, supplier not called, using last known stock +... +INFO ... circuit-breaker.camel.yaml:43 : Stock check 14 (breaker OPEN): breaker open, supplier not called, using last known stock +INFO ... circuit-breaker.camel.yaml:43 : Stock check 15 (breaker CLOSED): CAMEL-MUG: 46 in stock at the supplier ``` ## Install Camel CLI @@ -35,13 +39,18 @@ Stop it with `ctrl` + `c`; the state logged on each line is the breaker's state - `circuitBreaker` wraps the call to `direct:supplier` with [Resilience4j](https://resilience4j.readme.io/): the breaker counts the last four calls (`slidingWindowSize`) and opens when half of them failed (`failureRateThreshold`), which happens on check 5. -- While open, the steps are not run at all; `onFallback` sets the body instead. After `waitDurationInOpenState`, - five seconds, the breaker goes half open and lets one call through (`permittedNumberOfCallsInHalfOpenState`): - check 10 still fails, so it opens for another five seconds; check 15 succeeds and closes it. +- `onFallback` runs both when a call fails and when the open breaker does not make the call at all, and + `CamelCircuitBreakerResponseRejected` tells them apart: `false` on checks 4 and 5, where the supplier was + called and failed (the failure on check 5 opens the breaker, so the state after it is `OPEN`), and `true` on + check 6 onwards, where the steps are not run at all. That difference is what the breaker buys: no call to a + service that is known to be down. +- After `waitDurationInOpenState`, five seconds, the breaker goes half open and lets one call through + (`permittedNumberOfCallsInHalfOpenState`): check 10 is that call and still fails, so it opens for another five + seconds; check 15 succeeds and closes it. - The supplier is a second route with `noErrorHandler`, so its exception reaches the breaker; it is down while the timer counter is 4 to 12. -- `CamelCircuitBreakerState` is one of the exchange properties the breaker sets; `CamelCircuitBreakerResponseSuccessfulExecution` - and `CamelCircuitBreakerResponseFromFallback` are the others. +- `CamelCircuitBreakerState` and `CamelCircuitBreakerResponseRejected` are exchange properties the breaker sets; + `CamelCircuitBreakerResponseSuccessfulExecution` and `CamelCircuitBreakerResponseFromFallback` are others. ## Build it step by step @@ -51,9 +60,13 @@ Stop it with `ctrl` + `c`; the state logged on each line is the breaker's state 3. Wrap the call in `circuitBreaker` with `onFallback`: no more stack traces, but the supplier is still called nine times because the default window is 100 calls. 4. Add the `resilience4jConfiguration` shown here and log `CamelCircuitBreakerState` to watch it open and close. +5. Choose the fallback body on `CamelCircuitBreakerResponseRejected`, so the log shows which checks the breaker + saved from calling the supplier. ## Try changing +- Run `camel get circuit-breaker` in another terminal while it runs: the state and the success, failure, + rejected, fallback and timeout counters of the breaker. - Set `waitDurationInOpenState` to 2000 and see the breaker try the supplier sooner. - Add `timeoutEnabled: true` and `timeoutDuration: 500` and a `delay` of 1000 in the supplier: a slow supplier counts as a failure too. diff --git a/fail-well/circuit-breaker/circuit-breaker.camel.yaml b/fail-well/circuit-breaker/circuit-breaker.camel.yaml index 90d73bf..87f6ef9 100644 --- a/fail-well/circuit-breaker/circuit-breaker.camel.yaml +++ b/fail-well/circuit-breaker/circuit-breaker.camel.yaml @@ -23,10 +23,23 @@ uri: direct:supplier onFallback: steps: - - setBody: - expression: - simple: - expression: "no answer, using last known stock" + # rejected: the breaker is open, so the supplier was not called at all + - choice: + when: + - expression: + simple: + expression: "${exchangeProperty.CamelCircuitBreakerResponseRejected} == true" + steps: + - setBody: + expression: + simple: + expression: "breaker open, supplier not called, using last known stock" + otherwise: + steps: + - setBody: + expression: + simple: + expression: "supplier failed, using last known stock" - log: message: "Stock check ${header.CamelTimerCounter} (breaker ${exchangeProperty.CamelCircuitBreakerState}): ${body}" diff --git a/fail-well/circuit-breaker/metadata.json b/fail-well/circuit-breaker/metadata.json index 21bb5fc..07753a1 100644 --- a/fail-well/circuit-breaker/metadata.json +++ b/fail-well/circuit-breaker/metadata.json @@ -1,6 +1,6 @@ { "title": "Circuit breaker", - "description": "A stock check calls the supplier every second; the supplier goes down for nine calls, the breaker opens after two failures in its window of four, answers from the fallback while open, tries the supplier again after five seconds and closes once a call succeeds; each line logs the breaker state.", + "description": "A stock check calls the supplier every second; the supplier goes down for nine calls, the breaker opens after two failures in its window of four, answers from the fallback without calling the supplier while open, tries it again after five seconds and closes once a call succeeds; each line logs the breaker state and whether the supplier failed or was not called.", "level": "fail-well", "order": 2, "teaches": { diff --git a/fail-well/circuit-breaker/test/circuit-breaker.citrus.it.yaml b/fail-well/circuit-breaker/test/circuit-breaker.citrus.it.yaml index 8821505..068b417 100644 --- a/fail-well/circuit-breaker/test/circuit-breaker.citrus.it.yaml +++ b/fail-well/circuit-breaker/test/circuit-breaker.citrus.it.yaml @@ -13,7 +13,12 @@ actions: jbang: verify: integration: "circuit-breaker" - logMessage: "(breaker OPEN): no answer" + logMessage: "(breaker OPEN): supplier failed, using last known stock" + - camel: + jbang: + verify: + integration: "circuit-breaker" + logMessage: "(breaker OPEN): breaker open, supplier not called, using last known stock" - camel: jbang: verify:
