question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Spring Boot: Retry logic is never called when CircuitBreaker specifies a fallback

See original GitHub issue

I’m submitting a …

  • bug report
  • documentation issue
  • feature request

Looking for a workaround? Check Workaround below.

What is the current behaviour?

When you apply both @Retry and @CircuitBreaker annotations with fallbacks, retry logic is never called. Bellow is a slightly modified sample from the Getting Started guide to make this behaviour clearer.

@CircuitBreaker(name = BACKEND, fallbackMethod = "fallback_CB")
@Retry(name = BACKEND, fallbackMethod = "fallback_Retry")
public Mono<String> method(String param1) {
   return Mono.error(new NumberFormatException());
}

private Mono<String> fallback_Retry(String param1, RuntimeException e) {
  return Mono.just("test");
}

private Mono<String> fallback_CB(String param1, RuntimeException e) {
  return Mono.just("test");
}

Let’s create a new Spring Boot 2 application with a service like above. Then such a behavior might be observed:

  • if method succeeds then neither retry nor circuit breaker logic is called. (as expected)
  • if method fails then fallback_CB is called immediately, returns successfully and call finishes without calling any retry logic.
  • if at some point circuit breaker get open, then after method had fails, fallback_CB is called immediately, returns successfully and call finishes without calling any retry logic. (as expected)
  • if keep only @Retry(...) annotation, then on method’s constant failure, method is called for the specified amount of times and then fallback_Retry is called to finish it gracefully. (as expected)
  • if you keep only @CircuitBreaker(...) annotation, then after method fails, fallback_CB is called immediately (expected or not?)
  • if you keep everything as is and drop only @CircuitBreaker fallback, then retry logic works just fine and circuit breaker opens accordingly to config. But even with circuit breaker open retry makes all the “cursed to fail” calls (that might take up to a minute or so with exponential timeouts).

What is the expected behavior?

Ideally, with circuit breaker closed and a lot of calls left to make it open, logical behavior for the code above should be like that:

  • When method invocation fails, retry logic is called according to configuration.
  • When all retry attempts wasted, fallback_Retry is called to finish the call gracefully with fallback logic.
  • If at some stage during retries circuit breaker turns open, then the next retry attempt gets intercepted and fallback_CB is called to finish it successfully (from retry perspective).
  • While circuit breaker is in open state, all retry call gets immediately intercepted by circuit breaker and successfully finished by fallback_CB.

Steps to reproduce

Follow Spring Boot Get Started Guide and then check the behavior.

Note, that backing repo for the guide doesn’t use fallback specification at all.

Workaround

(for anyone searching for a solution)

After a full day of debugging and research, I was able to find a pretty nice solution that makes logic work as expected (was it intended, but not documented?).

To make it work, just change exception variable in fallback_CB to CallNotPermittedException type. Circuit breaker normally uses this exception type to notify calling code that its state is open and invocation is impossible to complete.

According to docs, fallback is called only when there is a cast from real exception to the one specified as parameter, otherwise circuit breaker throws it farther through the call stack. As in our case “Retry” is a sort of a caller, it will catch the exception and retry according to its logic. Otherwise, if circuit breaker is open, fallback_CB will be called intercepting any further retry attempts and making fallback instantaneous.

@CircuitBreaker(name = BACKEND, fallbackMethod = "fallback_CB")
@Retry(name = BACKEND, fallbackMethod = "fallback_Retry")
public Mono<String> method(String param1) {
   return Mono.error(new NumberFormatException());
}

private Mono<String> fallback_Retry(String param1, RuntimeException e) {
  return Mono.just("test");
}

private Mono<String> fallback_CB(String param1, <em>CallNotPermittedException e</em>) {
  return Mono.just("test");
}

Proposal

  • Make Circuit Breaker’s fallback call fire only while open, ignoring failures in other cases (breaking change) or
  • Create additional “openFallback” parameter for that specific logic or
  • Fix the docs to use CallNotPermittedException in the example.

My environment:

  • resilience4j: 1.17.0
  • Spring Boot: 2.1.6
  • Java SDK (Oracle): 12

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:13
  • Comments:15 (9 by maintainers)

github_iconTop GitHub Comments

5reactions
dlsrb6342commented, Apr 9, 2020

@rusyasoft you can change aspect order by properties.

resilience4j:
  circuitbreaker:
    circuit-breaker-aspect-order: 100 
  retry:
    retry-aspect-order: 1000

By default, RetryAspect is higher than CircuitBreakerAspect.

3reactions
evgri243commented, Aug 12, 2019

@RobWin, Thank you.

But in the case of ignoring exception it won’t call retry’s fallback, that doesn’t meet expectations.

As for an example or docs, it’s needed. I googled the whole internet looking for a solution or ideas, but found nothing at all. Looks like no one ever tried to do that… That’s crazy. Maybe I’m bad at googling? Docs didn’t helped me either to get that design.

Thankfully, my IDE allows to debug external libraries, so I found a way to do it by debugging…

But nevertheless, thank you for an exceptional and amazing library.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Retry with Spring Boot and Resilience4j - Reflectoring
A deep dive into the Spring Boot Resilience4j Retry module, this article shows why, when and how to use it to build resilient...
Read more >
Spring Boot Resilience4j circuit breaker and fall back not ...
Your RateLimiter has a fallback, so it never throws an exception, so CircuitBreaker never sees a failed invocation. Specify a fallback on ...
Read more >
Build Resilient Microservices Using Spring Retry and Circuit ...
If Service B is still unable to process, fallback method will be called. After certain number of fallback method is execute in a...
Read more >
Circuit Breaker And Retry with Spring Cloud Resiliance4j
This is because the circuit breaker fallback method was called directly and the retry was not triggered.
Read more >
Improving Resilience Using Resilience4j - Second Edition
To control the logic in a circuit breaker, Resilience4j can be configured ... the retry logic, Resilience4j can be configured using standard Spring...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found