private Object proceed(MethodInvocation invocation, io.github.resilience4j.circuitbreaker.CircuitBreaker breaker, RecoveryFunction<?> recoveryFunction) throws Throwable { Object result; long start = System.nanoTime(); try { result = invocation.proceed(); } catch (Exception e) { long durationInNanos = System.nanoTime() - start; breaker.onError(durationInNanos, e); return recoveryFunction.apply(e); } return result; }
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; } }
/** * Returns a consumer which is decorated by a CircuitBreaker. * @param circuitBreaker the CircuitBreaker * @param consumer the original consumer * @param <T> the type of the input to the consumer * * @return a consumer which is decorated by a CircuitBreaker. */ static <T> Consumer<T> decorateConsumer(CircuitBreaker circuitBreaker, Consumer<T> consumer){ return (t) -> { CircuitBreakerUtils.isCallPermitted(circuitBreaker); long start = System.nanoTime(); try { consumer.accept(t); long durationInNanos = System.nanoTime() - start; circuitBreaker.onSuccess(durationInNanos); } catch (Throwable throwable) { long durationInNanos = System.nanoTime() - start; circuitBreaker.onError(durationInNanos, throwable); throw throwable; } }; }
/** * Returns a function which is decorated by a CircuitBreaker. * @param circuitBreaker the CircuitBreaker * @param function the original function * @param <T> the type of the input to the function * @param <R> the type of the result of the function * @return a function which is decorated by a CircuitBreaker. */ static <T, R> Function<T, R> decorateFunction(CircuitBreaker circuitBreaker, Function<T, R> function){ return (T t) -> { CircuitBreakerUtils.isCallPermitted(circuitBreaker); long start = System.nanoTime(); try{ R returnValue = function.apply(t); long durationInNanos = System.nanoTime() - start; circuitBreaker.onSuccess(durationInNanos); return returnValue; } catch (Throwable throwable){ long durationInNanos = System.nanoTime() - start; circuitBreaker.onError(durationInNanos, throwable); throw throwable; } }; }
/** * Returns a runnable which is decorated by a CircuitBreaker. * * @param circuitBreaker the CircuitBreaker * @param runnable the original runnable * * @return a runnable which is decorated by a CircuitBreaker. */ static Runnable decorateRunnable(CircuitBreaker circuitBreaker, Runnable runnable){ return () -> { CircuitBreakerUtils.isCallPermitted(circuitBreaker); long start = System.nanoTime(); try{ runnable.run(); long durationInNanos = System.nanoTime() - start; circuitBreaker.onSuccess(durationInNanos); } catch (Throwable throwable){ long durationInNanos = System.nanoTime() - start; circuitBreaker.onError(durationInNanos, throwable); throw throwable; } }; }
private void markFailure(Throwable e) { if (wasCallPermitted()) { circuitBreaker.onError(stopWatch.stop().getProcessingDuration().toNanos(), e); } }
private void markFailure(Throwable e) { if (wasCallPermitted()) { circuitBreaker.onError(stopWatch.stop().getProcessingDuration().toNanos(), e); } }
private void markFailure(Throwable e) { if (wasCallPermitted()) { circuitBreaker.onError(stopWatch.stop().getProcessingDuration().toNanos(), e); } }
/** * Returns a function which is decorated by a CircuitBreaker. * * @param circuitBreaker the CircuitBreaker * @param function the original function * @param <T> the type of the input to the function * @param <R> the type of the result of the function * @return a function which is decorated by a CircuitBreaker. */ static <T, R> CheckedFunction1<T, R> decorateCheckedFunction(CircuitBreaker circuitBreaker, CheckedFunction1<T, R> function){ return (T t) -> { CircuitBreakerUtils.isCallPermitted(circuitBreaker); long start = System.nanoTime(); try{ R returnValue = function.apply(t); long durationInNanos = System.nanoTime() - start; circuitBreaker.onSuccess(durationInNanos); return returnValue; } catch (Throwable throwable){ long durationInNanos = System.nanoTime() - start; circuitBreaker.onError(durationInNanos, throwable); throw throwable; } }; }
/** * Returns a runnable which is decorated by a CircuitBreaker. * * @param circuitBreaker the CircuitBreaker * @param runnable the original runnable * * @return a runnable which is decorated by a CircuitBreaker. */ static CheckedRunnable decorateCheckedRunnable(CircuitBreaker circuitBreaker, CheckedRunnable runnable){ return () -> { CircuitBreakerUtils.isCallPermitted(circuitBreaker); long start = System.nanoTime(); try{ runnable.run(); long durationInNanos = System.nanoTime() - start; circuitBreaker.onSuccess(durationInNanos); } catch (Throwable throwable){ long durationInNanos = System.nanoTime() - start; circuitBreaker.onError(durationInNanos, throwable); throw throwable; } }; }
/** * Returns a consumer which is decorated by a CircuitBreaker. * @param circuitBreaker the CircuitBreaker * @param consumer the original consumer * @param <T> the type of the input to the consumer * * @return a consumer which is decorated by a CircuitBreaker. */ static <T> CheckedConsumer<T> decorateCheckedConsumer(CircuitBreaker circuitBreaker, CheckedConsumer<T> consumer){ return (t) -> { CircuitBreakerUtils.isCallPermitted(circuitBreaker); long start = System.nanoTime(); try { consumer.accept(t); long durationInNanos = System.nanoTime() - start; circuitBreaker.onSuccess(durationInNanos); } catch (Throwable throwable) { long durationInNanos = System.nanoTime() - start; circuitBreaker.onError(durationInNanos, throwable); throw throwable; } }; }
@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 shouldConsumeOnErrorEvent() { circuitBreaker.getEventPublisher() .onError(this::logEventType); circuitBreaker.onError(1000, new IOException("BAM!")); then(logger).should(times(1)).info("ERROR"); }
@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"); }
@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); } }
@Test public void shouldBufferErrorEvents() { // Given // tag::shouldBufferEvents[] CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName"); CircularEventConsumer<CircuitBreakerEvent> ringBuffer = new CircularEventConsumer<>(2); circuitBreaker.getEventPublisher().onEvent(ringBuffer); // end::shouldBufferEvents[] assertThat(ringBuffer.getBufferedEvents()).isEmpty(); //When circuitBreaker.onError(0, new RuntimeException("Bla")); circuitBreaker.onError(0, new RuntimeException("Bla")); circuitBreaker.onError(0, new RuntimeException("Bla")); //Then CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(3); assertThat(metrics.getNumberOfFailedCalls()).isEqualTo(3); //Should only store 2 events, because capacity is 2 assertThat(ringBuffer.getBufferedEvents()).hasSize(2); //ringBuffer.getBufferedEvents().forEach(event -> LOG.info(event.toString())); }
@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"); }
@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 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"); }