/** * {@inheritDoc} */ @Override public CircuitBreaker circuitBreaker(String name, CircuitBreakerConfig customCircuitBreakerConfig) { return circuitBreakers.computeIfAbsent(Objects.requireNonNull(name, "Name must not be null"), (k) -> CircuitBreaker.of(name, customCircuitBreakerConfig)); }
/** * {@inheritDoc} */ @Override public CircuitBreaker circuitBreaker(String name) { return circuitBreakers.computeIfAbsent(Objects.requireNonNull(name, "Name must not be null"), (k) -> CircuitBreaker.of(name, defaultCircuitBreakerConfig)); }
@Override public CircuitBreaker circuitBreaker(String name, Supplier<CircuitBreakerConfig> circuitBreakerConfigSupplier) { return circuitBreakers.computeIfAbsent(Objects.requireNonNull(name, "Name must not be null"), (k) -> CircuitBreaker.of(name, circuitBreakerConfigSupplier.get())); } }
@Before public void setUp() { this.circuitBreaker = CircuitBreaker.of("test", circuitBreakerConfig); final long TIMEOUT = 300; // ms this.client = new OkHttpClient.Builder() .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .build(); this.service = new Retrofit.Builder() .addCallAdapterFactory(CircuitBreakerCallAdapter.of(circuitBreaker)) .addConverterFactory(ScalarsConverterFactory.create()) .baseUrl("http://localhost:8080/") .client(client) .build() .create(RetrofitService.class); }
@Test public void shouldConsumeOnStateTransitionEvent() { circuitBreaker = CircuitBreaker.of("test", CircuitBreakerConfig.custom() .ringBufferSizeInClosedState(1).build()); circuitBreaker.getEventPublisher() .onStateTransition(this::logEventType); circuitBreaker.onError(1000, new IOException("BAM!")); circuitBreaker.onError(1000, new IOException("BAM!")); then(logger).should(times(1)).info("STATE_TRANSITION"); }
@Test public void shouldConsumeCallNotPermittedEvent() { circuitBreaker = CircuitBreaker.of("test", CircuitBreakerConfig.custom() .ringBufferSizeInClosedState(1).build()); circuitBreaker.getEventPublisher() .onCallNotPermitted(this::logEventType); circuitBreaker.onError(1000, new IOException("BAM!")); circuitBreaker.onError(1000, new IOException("BAM!")); circuitBreaker.isCallPermitted(); then(logger).should(times(1)).info("NOT_PERMITTED"); }
CircuitBreaker circuitBreaker = CircuitBreaker.of("testName", circuitBreakerConfig);
@Test public void shouldThrowCircuitBreakerOpenException() { // tag::shouldThrowCircuitBreakerOpenException[] // Given CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .ringBufferSizeInClosedState(2) .waitDurationInOpenState(Duration.ofMillis(1000)) .build(); CircuitBreaker circuitBreaker = CircuitBreaker.of("testName", circuitBreakerConfig); // Simulate a failure attempt circuitBreaker.onError(0, new RuntimeException()); // CircuitBreaker is still CLOSED, because 1 failure is allowed assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); // Simulate a failure attempt circuitBreaker.onError(0, new RuntimeException()); // CircuitBreaker is OPEN, because the failure rate is above 50% assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN); // When I decorate my function and invoke the decorated function Try<String> result = Try.of(CircuitBreaker.decorateCheckedSupplier(circuitBreaker, () -> "Hello")) .map(value -> value + " world"); // Then the call fails, because CircuitBreaker is OPEN assertThat(result.isFailure()).isTrue(); // Exception is CircuitBreakerOpenException assertThat(result.failed().get()).isInstanceOf(CircuitBreakerOpenException.class); // end::shouldThrowCircuitBreakerOpenException[] CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(2); assertThat(metrics.getNumberOfFailedCalls()).isEqualTo(2); }
@Test public void shouldReturnFailureWithCircuitBreakerOpenException() { // Given // Create a custom configuration for a CircuitBreaker CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .ringBufferSizeInClosedState(2) .ringBufferSizeInHalfOpenState(2) .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofMillis(1000)) .build(); // Create a CircuitBreakerRegistry with a custom global configuration CircuitBreaker circuitBreaker = CircuitBreaker.of("testName", circuitBreakerConfig); circuitBreaker.onError(0, new RuntimeException()); circuitBreaker.onError(0, new RuntimeException()); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN); CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(2); assertThat(metrics.getNumberOfFailedCalls()).isEqualTo(2); //When CheckedRunnable checkedRunnable = CircuitBreaker.decorateCheckedRunnable(circuitBreaker, () -> { throw new RuntimeException("BAM!"); }); Try result = Try.run(checkedRunnable); //Then assertThat(result.isFailure()).isTrue(); assertThat(result.failed().get()).isInstanceOf(CircuitBreakerOpenException.class); }
@Test public void shouldNotProduceEventsInDisabledState() { //Given circuitBreaker = CircuitBreaker.of("test", CircuitBreakerConfig.custom() .ringBufferSizeInClosedState(1).build()); circuitBreaker.getEventPublisher() .onEvent(this::logEventType); //When we transition to disabled circuitBreaker.transitionToDisabledState(); //And we execute other calls that should generate events circuitBreaker.onError(1000, new IOException("BAM!")); circuitBreaker.onError(1000, new IOException("BAM!")); circuitBreaker.isCallPermitted(); circuitBreaker.onSuccess(0); circuitBreaker.onError(1000, new IOException("BAM!")); //Then we do not produce events then(logger).should(times(1)).info("STATE_TRANSITION"); then(logger).should(times(0)).info("NOT_PERMITTED"); then(logger).should(times(0)).info("SUCCESS"); then(logger).should(times(0)).info("ERROR"); then(logger).should(times(0)).info("IGNORED_ERROR"); }
@Before public void setUp() { circuitBreaker = CircuitBreaker.of("test", circuitBreakerConfig); final FeignDecorators decorators = FeignDecorators.builder().withCircuitBreaker(circuitBreaker).build(); testService = Resilience4jFeign.builder(decorators).target(TestService.class, "http://localhost:8080/"); }
Case($(), false))) .build(); CircuitBreaker circuitBreaker = CircuitBreaker.of("testName", circuitBreakerConfig);
@Test public void shouldConsumeIgnoredErrorEvent() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .recordFailure(throwable -> Match(throwable).of( Case($(instanceOf(WebServiceException.class)), true), Case($(), false))) .build(); circuitBreaker = CircuitBreaker.of("test", circuitBreakerConfig); circuitBreaker.getEventPublisher() .onIgnoredError(this::logEventType) ; circuitBreaker.onError(1000, new IOException("BAM!")); then(logger).should(times(1)).info("IGNORED_ERROR"); }
Case($(), false))) .build(); CircuitBreaker circuitBreaker = CircuitBreaker.of("testName", circuitBreakerConfig); CircularEventConsumer<CircuitBreakerEvent> ringBuffer = new CircularEventConsumer<>(10); circuitBreaker.getEventPublisher().onEvent(ringBuffer);