private CircuitBreakerMetrics(String prefix, Iterable<CircuitBreaker> circuitBreakers) { requireNonNull(prefix); requireNonNull(circuitBreakers); circuitBreakers.forEach((CircuitBreaker circuitBreaker) -> { String name = circuitBreaker.getName(); //state as an integer metricRegistry.register(name(prefix, name, STATE), (Gauge<Integer>)()-> circuitBreaker.getState().getOrder()); metricRegistry.register(name(prefix, name, SUCCESSFUL), (Gauge<Integer>) () -> circuitBreaker.getMetrics().getNumberOfSuccessfulCalls()); metricRegistry.register(name(prefix, name, FAILED), (Gauge<Integer>) () -> circuitBreaker.getMetrics().getNumberOfFailedCalls()); metricRegistry.register(name(prefix, name, NOT_PERMITTED), (Gauge<Long>) () -> circuitBreaker.getMetrics().getNumberOfNotPermittedCalls()); metricRegistry.register(name(prefix, name, BUFFERED), (Gauge<Integer>) () -> circuitBreaker.getMetrics().getNumberOfBufferedCalls()); metricRegistry.register(name(prefix, name, BUFFERED_MAX), (Gauge<Integer>) () -> circuitBreaker.getMetrics().getMaxNumberOfBufferedCalls()); } ); }
if (!circuitBreaker.isCallPermitted()) { promise.completeExceptionally( new CircuitBreakerOpenException( String.format("CircuitBreaker '%s' is open", circuitBreaker.getName()))); long durationInNanos = System.nanoTime() - start; if (throwable != null) { circuitBreaker.onError(durationInNanos, throwable); promise.completeExceptionally(throwable); } else { circuitBreaker.onSuccess(durationInNanos); promise.complete(result); } catch (Throwable throwable) { long durationInNanos = System.nanoTime() - start; circuitBreaker.onError(durationInNanos, throwable); throw throwable;
@RequestMapping(value = "stream/events/{circuitBreakerName}", produces = MEDIA_TYPE_TEXT_EVENT_STREAM) public SseEmitter getEventsStreamFilteredByCircuitBreakerName(@PathVariable("circuitBreakerName") String circuitBreakerName) { CircuitBreaker circuitBreaker = circuitBreakerRegistry.getAllCircuitBreakers() .find(cb -> cb.getName().equals(circuitBreakerName)) .getOrElseThrow(() -> new IllegalArgumentException(String.format("circuit breaker with name %s not found", circuitBreakerName))); return CircuitBreakerEventEmitter.createSseEmitter(toFlux(circuitBreaker.getEventPublisher())); }
public static void isCallPermitted(CircuitBreaker circuitBreaker) { if(!circuitBreaker.isCallPermitted()) { throw new CircuitBreakerOpenException(String.format("CircuitBreaker '%s' is open", circuitBreaker.getName())); } } }
private Object handleJoinPoint(ProceedingJoinPoint proceedingJoinPoint, io.github.resilience4j.circuitbreaker.CircuitBreaker circuitBreaker, String methodName) throws Throwable { CircuitBreakerUtils.isCallPermitted(circuitBreaker); long start = System.nanoTime(); try { Object returnValue = proceedingJoinPoint.proceed(); long durationInNanos = System.nanoTime() - start; circuitBreaker.onSuccess(durationInNanos); return returnValue; } catch (Throwable throwable) { long durationInNanos = System.nanoTime() - start; circuitBreaker.onError(durationInNanos, throwable); if (logger.isDebugEnabled()) { logger.debug("Invocation of method '" + methodName + "' failed!", throwable); } throw throwable; } }
@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); }
assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); assertThatMetricsAreReset(); circuitBreaker.onError(0, new RuntimeException()); // Should create a CircuitBreakerOnErrorEvent (1) assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); assertCircuitBreakerMetricsEqualTo(-1f, null, 1, null, 1, 0L); circuitBreaker.onError(0, new RuntimeException()); // Should create a CircuitBreakerOnErrorEvent (2) assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); assertCircuitBreakerMetricsEqualTo(-1f, null, 2, null, 2, 0L); circuitBreaker.onError(0, new RuntimeException()); // Should create a CircuitBreakerOnErrorEvent (3) assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); assertCircuitBreakerMetricsEqualTo(-1f, null, 3, null, 3, 0L); circuitBreaker.onSuccess(0); // Should create a CircuitBreakerOnSuccessEvent (4) assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); assertCircuitBreakerMetricsEqualTo(-1f, null, 4, null, 3, 0L); circuitBreaker.onSuccess(0); // Should create a CircuitBreakerOnSuccessEvent (5) assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN); // Should create a CircuitBreakerOnStateTransitionEvent (6) assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(5); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(3);
assertThat(circuitBreaker.getState(), equalTo(CircuitBreaker.State.CLOSED)); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls(), equalTo(0)); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls(), equalTo(0)); assertThat(circuitBreaker.getMetrics().getNumberOfSuccessfulCalls(), equalTo(0)); assertThat(gauges.get("resilience4j.circuitbreaker.test.state").getValue(), equalTo(0)); assertThat(gauges.get("resilience4j.circuitbreaker.test.buffered").getValue(), equalTo(0)); circuitBreaker.onError(0, new RuntimeException()); assertThat(circuitBreaker.getState(), equalTo(CircuitBreaker.State.CLOSED)); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls(), equalTo(1)); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls(), equalTo(1)); assertThat(circuitBreaker.getMetrics().getNumberOfSuccessfulCalls(), equalTo(0)); assertThat(gauges.get("resilience4j.circuitbreaker.test.state").getValue(), equalTo(0)); assertThat(gauges.get("resilience4j.circuitbreaker.test.buffered").getValue(), equalTo(1)); circuitBreaker.onError(0, new RuntimeException()); assertThat(circuitBreaker.getState(), equalTo(CircuitBreaker.State.OPEN)); assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls(), equalTo(10)); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls(), equalTo(10)); assertThat(circuitBreaker.getMetrics().getNumberOfSuccessfulCalls(), equalTo(0)); assertThat(gauges.get("resilience4j.circuitbreaker.test.state").getValue(), equalTo(1)); assertThat(gauges.get("resilience4j.circuitbreaker.test.buffered").getValue(), equalTo(10)); circuitBreaker.isCallPermitted(); return circuitBreaker.getState().equals(CircuitBreaker.State.HALF_OPEN); });
@Test public void shouldDecorateCompletionStageAndReturnWithExceptionAtSyncStage() throws ExecutionException, InterruptedException { // Given CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName"); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); // Given the HelloWorldService throws an exception // When Supplier<CompletionStage<String>> completionStageSupplier = () -> { throw new WebServiceException("BAM! At sync stage"); }; Supplier<CompletionStage<String>> decoratedCompletionStageSupplier = CircuitBreaker.decorateCompletionStage(circuitBreaker, completionStageSupplier); Try<CompletionStage<String>> result = Try.of(decoratedCompletionStageSupplier::get); assertThat(result.isFailure()).isEqualTo(true); assertThat(result.failed().get()).isInstanceOf(WebServiceException.class); CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(1); assertThat(metrics.getNumberOfFailedCalls()).isEqualTo(1); }
@Test public void shouldNotBufferEvents() { // Given CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName"); CircularEventConsumer<CircuitBreakerEvent> ringBuffer = new CircularEventConsumer<>(2); assertThat(ringBuffer.getBufferedEvents()).isEmpty(); circuitBreaker.onError(0, new RuntimeException("Bla")); circuitBreaker.onError(0, new RuntimeException("Bla")); circuitBreaker.onError(0, new RuntimeException("Bla")); //Subscription is too late circuitBreaker.getEventPublisher().onEvent(ringBuffer); //Then CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(3); assertThat(metrics.getNumberOfFailedCalls()).isEqualTo(3); //Should store 0 events, because Subscription was too late assertThat(ringBuffer.getBufferedEvents()).hasSize(0); } }
Case($(), false))) .build(); CircuitBreaker circuitBreaker = CircuitBreaker.of("testName", circuitBreakerConfig); CircularEventConsumer<CircuitBreakerEvent> ringBuffer = new CircularEventConsumer<>(10); circuitBreaker.getEventPublisher().onEvent(ringBuffer); circuitBreaker.onSuccess(0); circuitBreaker.onError(0, new WebServiceException("Bla")); circuitBreaker.onError(0, new IOException("Bla")); circuitBreaker.onError(0, new WebServiceException("Bla")); CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(3); assertThat(metrics.getNumberOfFailedCalls()).isEqualTo(2); circuitBreaker.reset(); CircuitBreaker.Metrics resetMetrics = circuitBreaker.getMetrics(); assertThat(resetMetrics.getNumberOfBufferedCalls()).isEqualTo(0); assertThat(resetMetrics.getNumberOfFailedCalls()).isEqualTo(0);
@Test public void testWithCircuitBreaker() throws Throwable { final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("test"); final CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); final FeignDecorators testSubject = FeignDecorators.builder().withCircuitBreaker(circuitBreaker).build(); final Object result = testSubject.decorate(args -> args[0], null, null, null).apply(new Object[] {"test01"}); assertThat(result) .describedAs("Returned result is correct") .isEqualTo("test01"); assertThat(metrics.getNumberOfSuccessfulCalls()) .describedAs("Successful Calls") .isEqualTo(1); }
@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"); }
protected void assertSingleSuccessfulCall() { assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getNumberOfSuccessfulCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(0); }
when(circuitBreaker.getCircuitBreakerConfig()).thenReturn(config); when(circuitBreaker.getMetrics()).thenReturn(metrics); when(circuitBreaker.getState()).thenReturn(CLOSED, OPEN, HALF_OPEN, CLOSED);
private Health.Builder addDetails(Health.Builder builder, CircuitBreaker circuitBreaker) { CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); CircuitBreakerConfig config = circuitBreaker.getCircuitBreakerConfig(); builder.withDetail(FAILURE_RATE, metrics.getFailureRate() + "%") .withDetail(FAILURE_RATE_THRESHOLD, config.getFailureRateThreshold() + "%") .withDetail(MAX_BUFFERED_CALLS, metrics.getMaxNumberOfBufferedCalls()) .withDetail(BUFFERED_CALLS, metrics.getNumberOfBufferedCalls()) .withDetail(FAILED_CALLS, metrics.getNumberOfFailedCalls()) .withDetail(NOT_PERMITTED, metrics.getNumberOfNotPermittedCalls()); return builder; } }
@Override public Response<T> execute() throws IOException { CircuitBreakerUtils.isCallPermitted(circuitBreaker); final StopWatch stopWatch = StopWatch.start(circuitBreaker.getName()); try { final Response<T> response = call.execute(); if (responseSuccess.test(response)) { circuitBreaker.onSuccess(stopWatch.stop().getProcessingDuration().toNanos()); } else { final Throwable throwable = new Throwable("Response error: HTTP " + response.code() + " - " + response.message()); circuitBreaker.onError(stopWatch.stop().getProcessingDuration().toNanos(), throwable); } return response; } catch (Throwable throwable) { circuitBreaker.onError(stopWatch.stop().getProcessingDuration().toNanos(), throwable); throw throwable; } } };
@Test public void shouldInvokeAsyncApply() throws ExecutionException, InterruptedException { // tag::shouldInvokeAsyncApply[] // Given CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName"); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); // When Supplier<String> decoratedSupplier = CircuitBreaker .decorateSupplier(circuitBreaker, () -> "This can be any method which returns: 'Hello"); CompletableFuture<String> future = CompletableFuture.supplyAsync(decoratedSupplier) .thenApply(value -> value + " world'"); //Then assertThat(future.get()).isEqualTo("This can be any method which returns: 'Hello world'"); // end::shouldInvokeAsyncApply[] CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(1); assertThat(metrics.getNumberOfFailedCalls()).isEqualTo(0); }
@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"); }
@Test public void shouldReturnFailureWithRuntimeException() { // Given CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName"); assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(0); assertThat(metrics.getNumberOfFailedCalls()).isEqualTo(0); //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(RuntimeException.class); assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(1); assertThat(metrics.getNumberOfFailedCalls()).isEqualTo(1); }