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.

CircuitBreaker does not prevent execution of upstream Mono if flatMap is used

See original GitHub issue

Expected behavior

Both tests simple, withMap, and withFlatMap behave the same.

Actual behavior

In simple and withMap getNext() is called twice (as expected) and withFlatMap getNext() is called four times.

Log for simple/withMap test

Requested Counter: 1
16:03:19.461 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - No Consumers: Event SUCCESS not published
Ctr: 1
Requested Counter: 2
16:03:19.476 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - CircuitBreaker 'test' recorded a failure:
java.lang.RuntimeException: Error
	...
16:03:19.477 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - No Consumers: Event ERROR not published
16:03:19.494 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - No Consumers: Event STATE_TRANSITION not published
RuntimeException
16:03:19.496 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - No Consumers: Event NOT_PERMITTED not published
CircuitBreakerOpenException
16:03:19.497 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - No Consumers: Event NOT_PERMITTED not published
CircuitBreakerOpenException

Log for withFlatMap test

Requested Counter: 1
16:03:19.601 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - No Consumers: Event SUCCESS not published
Ctr: 1
Requested Counter: 2
16:03:19.603 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - CircuitBreaker 'test' recorded a failure:
java.lang.RuntimeException: Error
	...
16:03:19.603 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - No Consumers: Event ERROR not published
16:03:19.603 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - No Consumers: Event STATE_TRANSITION not published
RuntimeException
Requested Counter: 3
16:03:19.603 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - No Consumers: Event NOT_PERMITTED not published
CircuitBreakerOpenException
Requested Counter: 4
16:03:19.604 [main] DEBUG io.github.resilience4j.circuitbreaker.internal.CircuitBreakerStateMachine - No Consumers: Event NOT_PERMITTED not published
CircuitBreakerOpenException

Steps to reproduce


public class Resilience4jSimpleTest {
    int counter = 0;

    CircuitBreakerConfig breakerConfig = CircuitBreakerConfig.custom()
            .ringBufferSizeInClosedState(2)
            .build();

    CircuitBreaker circuitBreaker = CircuitBreaker.of("test", breakerConfig);

    @Test
    public void simple() {
        System.out.println(performCall(circuitBreaker));
        System.out.println(performCall(circuitBreaker));
        System.out.println(performCall(circuitBreaker));
        System.out.println(performCall(circuitBreaker));
    }

    @Test
    public void withMap() {
        System.out.println(performCallWithMap(circuitBreaker));
        System.out.println(performCallWithMap(circuitBreaker));
        System.out.println(performCallWithMap(circuitBreaker));
        System.out.println(performCallWithMap(circuitBreaker));
    }

    @Test
    public void withFlatMap() {
        System.out.println(performCallWithFlatMap(circuitBreaker));
        System.out.println(performCallWithFlatMap(circuitBreaker));
        System.out.println(performCallWithFlatMap(circuitBreaker));
        System.out.println(performCallWithFlatMap(circuitBreaker));
    }

    String getNext() {
        counter++;
        System.out.println("Requested Counter: " + counter);
        if (counter % 2 == 0) {
            throw new RuntimeException("Error");
        }
        return "Ctr: " + counter;
    }

    private String performCall(CircuitBreaker circuitBreaker) {
        return Mono.fromCallable(this::getNext)
                .transform(CircuitBreakerOperator.of(circuitBreaker))
                .onErrorResume(Exception.class, (e) -> Mono.just(e.getClass().getSimpleName()))
                .block();
    }

    private String performCallWithMap(CircuitBreaker circuitBreaker) {
        return Mono.fromCallable(this::getNext)
                .map(Function.identity())
                .transform(CircuitBreakerOperator.of(circuitBreaker))
                .onErrorResume(Exception.class, (e) -> Mono.just(e.getClass().getSimpleName()))
                .block();
    }

    private String performCallWithFlatMap(CircuitBreaker circuitBreaker) {
        return Mono.fromCallable(this::getNext)
                .flatMap(Mono::just)
                .transform(CircuitBreakerOperator.of(circuitBreaker))
                .onErrorResume(Exception.class, (e) -> Mono.just(e.getClass().getSimpleName()))
                .block();
    }
}

Versions

  • resilience4j 0.13.2
  • reactor: 3.2.6.Final

Possible related #334 issue

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:11 (11 by maintainers)

github_iconTop GitHub Comments

1reaction
RobWincommented, May 14, 2019

This will be fixed in the next release.

0reactions
RobWincommented, May 16, 2019

This test is successful now

    @Test
    public void shouldNotSubscribeToMonoFromCallable() {
        given(circuitBreaker.tryAcquirePermission()).willReturn(false);
        given(helloWorldService.returnHelloWorld()).willReturn("Hello World");

        StepVerifier.create(
                Mono.fromCallable(() -> helloWorldService.returnHelloWorld())
                        .flatMap(Mono::just)
                        .compose(CircuitBreakerOperator.of(circuitBreaker)))
                .expectError(CallNotPermittedException.class)
                .verify();

        then(helloWorldService).should(never()).returnHelloWorld();
        verify(circuitBreaker, never()).onSuccess(anyLong());
        verify(circuitBreaker, never()).onError(anyLong(), any(Throwable.class));
    }
Read more comments on GitHub >

github_iconTop Results From Across the Web

Mono.flatMap is not getting invoked - Stack Overflow
I inserted the print statement to test if it prints anything and it doesn't even execute the print statement. publishToTopic returns Mono<Void> ...
Read more >
Spring Cloud Gateway
This project provides an API Gateway built on top of the Spring Ecosystem, including: Spring 5, Spring Boot 2 and Project Reactor. Spring...
Read more >
Mono (reactor-core 3.5.0)
If an onErrorContinue(BiConsumer) variant has been used downstream, reverts to the default 'STOP' mode where errors are terminal events upstream. Mono<T> ...
Read more >
reactor.core.publisher.Mono.delayElement java code examples
Delay this Mono element ( Subscriber#onNext signal) by a given duration. Empty Monos or error signals are not delayed.
Read more >
Management & Monitoring - Micronaut Documentation
Please keep in mind that starting with 21.3.0 GraalVM doesn't release a version based on JDK 8. If you still use Java 8...
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