private RetryMetrics(String prefix, Iterable<Retry> retries){ requireNonNull(prefix); requireNonNull(retries); retries.forEach(retry -> { String name = retry.getName(); metricRegistry.register(name(prefix, name, SUCCESSFUL_CALLS_WITHOUT_RETRY), (Gauge<Long>) () -> retry.getMetrics().getNumberOfSuccessfulCallsWithoutRetryAttempt()); metricRegistry.register(name(prefix, name, SUCCESSFUL_CALLS_WITH_RETRY), (Gauge<Long>) () -> retry.getMetrics().getNumberOfSuccessfulCallsWithRetryAttempt()); metricRegistry.register(name(prefix, name, FAILED_CALLS_WITHOUT_RETRY), (Gauge<Long>) () -> retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()); metricRegistry.register(name(prefix, name, FAILED_CALLS_WITH_RETRY), (Gauge<Long>) () -> retry.getMetrics().getNumberOfFailedCallsWithRetryAttempt()); }); }
RetrySubscriber(Subscriber<? super T> actual, long count, SubscriptionArbiter sa, Publisher<? extends T> source, Retry retry) { this.actual = actual; this.sa = sa; this.source = source; this.context = retry.context(); this.remaining = count; }
public DecorateSupplier<T> withRetry(Retry retryContext) { supplier = Retry.decorateSupplier(retryContext, supplier); return this; }
@Test public void shouldNotRetryFromPredicateUsingSingle() { //Given RetryConfig config = RetryConfig.custom() .retryOnException(t -> t instanceof IOException) .maxAttempts(3).build(); Retry retry = Retry.of("testName", config); given(helloWorldService.returnHelloWorld()) .willThrow(new WebServiceException("BAM!")); //When Single.fromCallable(helloWorldService::returnHelloWorld) .compose(RetryTransformer.of(retry)) .test() .assertError(WebServiceException.class) .assertNotComplete() .assertSubscribed(); //Then BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorld(); Retry.Metrics metrics = retry.getMetrics(); assertThat(metrics.getNumberOfFailedCallsWithoutRetryAttempt()).isEqualTo(1); assertThat(metrics.getNumberOfFailedCallsWithRetryAttempt()).isEqualTo(0); }
); chain1.get("stream/events", ctx -> { Seq<Flux<RetryEvent>> eventStreams = retryRegistry.getAllRetries().map(retry -> ReactorAdapter.toFlux(retry.getEventPublisher())); Function<RetryEvent, String> data = r -> Jackson.getObjectWriter(chain1.getRegistry()).writeValueAsString(RetryEventDTO.createRetryEventDTO(r)); ServerSentEvents events = ServerSentEvents.serverSentEvents(Flux.merge(eventStreams), e -> e.id(RetryEvent::getName).event(c -> c.getEventType().name()).data(data)); String rateLimiterName = ctx.getPathTokens().get("name"); Retry retry = retryRegistry.getAllRetries() .find(rL -> rL.getName().equals(rateLimiterName)) .getOrElseThrow(() -> new IllegalArgumentException(String.format("rate limiter with name %s not found", rateLimiterName))); Function<RetryEvent, String> data = r -> Jackson.getObjectWriter(chain1.getRegistry()).writeValueAsString(RetryEventDTO.createRetryEventDTO(r)); ServerSentEvents events = ServerSentEvents.serverSentEvents(ReactorAdapter.toFlux(retry.getEventPublisher()), e -> e.id(RetryEvent::getName).event(c -> c.getEventType().name()).data(data)); ctx.render(events); }); String eventType = ctx.getPathTokens().get("type"); Retry retry = retryRegistry.getAllRetries() .find(rL -> rL.getName().equals(retryName)) .getOrElseThrow(() -> new IllegalArgumentException(String.format("rate limiter with name %s not found", retryName))); Flux<RetryEvent> eventStream = ReactorAdapter.toFlux(retry.getEventPublisher()) .filter(event -> event.getEventType() == RetryEvent.Type.valueOf(eventType.toUpperCase())); Function<RetryEvent, String> data = r -> Jackson.getObjectWriter(chain1.getRegistry()).writeValueAsString(RetryEventDTO.createRetryEventDTO(r));
@Test public void testDecorateSupplierAndInvokeTwice() { // Given the HelloWorldService throws an exception BDDMockito.given(helloWorldService.returnHelloWorld()) .willThrow(new WebServiceException("BAM!")) .willReturn("Hello world") .willThrow(new WebServiceException("BAM!")) .willReturn("Hello world"); // Create a Retry with default configuration Retry retry = Retry.ofDefaults("id"); // Decorate the invocation of the HelloWorldService Supplier<String> supplier = Retry.decorateSupplier(retry, helloWorldService::returnHelloWorld); // When String result = supplier.get(); String result2 = supplier.get(); // Then the helloWorldService should be invoked 2 times BDDMockito.then(helloWorldService).should(Mockito.times(4)).returnHelloWorld(); assertThat(result).isEqualTo("Hello world"); assertThat(result2).isEqualTo("Hello world"); assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION * 2); assertThat(retry.getMetrics().getNumberOfSuccessfulCallsWithRetryAttempt()).isEqualTo(2); }
@Test public void shouldConsumeIgnoredErrorEvent() { given(helloWorldService.returnHelloWorld()) .willThrow(new WebServiceException("BAM!")); RetryConfig retryConfig = RetryConfig.custom() .retryOnException(throwable -> Match(throwable).of( Case($(instanceOf(WebServiceException.class)), false), Case($(), true))) .build(); retry = Retry.of("testName", retryConfig); retry.getEventPublisher() .onIgnoredError(event -> logger.info(event.getEventType().toString())); Try.ofSupplier(Retry.decorateSupplier(retry, helloWorldService::returnHelloWorld)); then(logger).should(times(1)).info("IGNORED_ERROR"); then(helloWorldService).should(times(1)).returnHelloWorld(); }
@Test public void shouldReturnAfterOneAttempt() { // Given the HelloWorldService throws an exception BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!")); // Create a Retry with custom configuration RetryConfig config = RetryConfig.custom().maxAttempts(1).build(); Retry retry = Retry.of("id", config); // Decorate the invocation of the HelloWorldService CheckedFunction0<String> retryableSupplier = Retry .decorateCheckedSupplier(retry, helloWorldService::returnHelloWorld); // When Try<String> result = Try.of(retryableSupplier); // Then the helloWorldService should be invoked 1 time BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorld(); // and the result should be a failure assertThat(result.isFailure()).isTrue(); // and the returned exception should be of type RuntimeException assertThat(result.failed().get()).isInstanceOf(WebServiceException.class); assertThat(sleptTime).isEqualTo(0); }
@Test public void shouldConsumeOnErrorEvent() { given(helloWorldService.returnHelloWorld()) .willThrow(new WebServiceException("BAM!")); retry.getEventPublisher() .onError(event -> logger.info(event.getEventType().toString())); Try.ofSupplier(Retry.decorateSupplier(retry, helloWorldService::returnHelloWorld)); then(logger).should(times(1)).info("ERROR"); then(helloWorldService).should(times(3)).returnHelloWorld(); }
@Test public void shouldNotRetryWithResult() { // Given the HelloWorldService returns Hello world BDDMockito.given(helloWorldService.returnHelloWorld()).willReturn("Hello world"); // Create a Retry with default configuration final RetryConfig tryAgain = RetryConfig.<String>custom().retryOnResult(s -> s.contains("tryAgain")) .maxAttempts(2).build(); Retry retry = Retry.of("id", tryAgain); // Decorate the invocation of the HelloWorldService Supplier<String> supplier = Retry.decorateSupplier(retry, helloWorldService::returnHelloWorld); // When String result = supplier.get(); // Then the helloWorldService should be invoked 1 time BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorld(); assertThat(result).isEqualTo("Hello world"); assertThat(sleptTime).isEqualTo(0); }
@Test public void shouldReturnAfterTwoAttempts() { // Given the HelloWorldService throws an exception BDDMockito.willThrow(new WebServiceException("BAM!")).willDoNothing().given(helloWorldService).sayHelloWorld(); // Create a Retry with default configuration Retry retry = Retry.ofDefaults("id"); TestSubscriber<RetryEvent.Type> testSubscriber = toFlowable(retry.getEventPublisher()) .map(RetryEvent::getEventType) .test(); // Decorate the invocation of the HelloWorldService CheckedRunnable retryableRunnable = Retry.decorateCheckedRunnable(retry, helloWorldService::sayHelloWorld); // When Try<Void> result = Try.run(retryableRunnable); // Then the helloWorldService should be invoked 2 times BDDMockito.then(helloWorldService).should(Mockito.times(2)).sayHelloWorld(); // and the result should be a sucess Assertions.assertThat(result.isSuccess()).isTrue(); Assertions.assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION); testSubscriber.assertValueCount(2).assertValues(RetryEvent.Type.RETRY, RetryEvent.Type.SUCCESS); }
@Test public void shouldReturnAfterThreeAttemptsAndRecover() { // Given the HelloWorldService throws an exception BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!")); // Create a Retry with default configuration Retry retry = Retry.ofDefaults("id"); // Decorate the invocation of the HelloWorldService CheckedFunction0<String> retryableSupplier = Retry .decorateCheckedSupplier(retry, helloWorldService::returnHelloWorld); // When Try<String> result = Try.of(retryableSupplier).recover((throwable) -> "Hello world from recovery function"); assertThat(retry.getMetrics().getNumberOfFailedCallsWithRetryAttempt()).isEqualTo(1); // Then the helloWorldService should be invoked 3 times BDDMockito.then(helloWorldService).should(Mockito.times(3)).returnHelloWorld(); // and the returned exception should be of type RuntimeException assertThat(result.get()).isEqualTo("Hello world from recovery function"); assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION * 2); }
@Test public void shouldReturnAfterOneAttempt() { // Given the HelloWorldService throws an exception BDDMockito.willThrow(new WebServiceException("BAM!")).given(helloWorldService).sayHelloWorld(); // Create a Retry with default configuration RetryConfig config = RetryConfig.custom().maxAttempts(1).build(); Retry retry = Retry.of("id", config); // Decorate the invocation of the HelloWorldService CheckedRunnable retryableRunnable = Retry.decorateCheckedRunnable(retry, helloWorldService::sayHelloWorld); // When Try<Void> result = Try.run(retryableRunnable); // Then the helloWorldService should be invoked 1 time BDDMockito.then(helloWorldService).should(Mockito.times(1)).sayHelloWorld(); // and the result should be a failure Assertions.assertThat(result.isFailure()).isTrue(); // and the returned exception should be of type RuntimeException Assertions.assertThat(result.failed().get()).isInstanceOf(WebServiceException.class); Assertions.assertThat(sleptTime).isEqualTo(0); }
@Test public void shouldNotRetry() { // Given the HelloWorldService returns Hello world BDDMockito.given(helloWorldService.returnHelloWorld()).willReturn("Hello world"); // Create a Retry with default configuration Retry retry = Retry.ofDefaults("id"); // Decorate the invocation of the HelloWorldService Supplier<String> supplier = Retry.decorateSupplier(retry, helloWorldService::returnHelloWorld); // When String result = supplier.get(); // Then the helloWorldService should be invoked 1 time BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorld(); assertThat(result).isEqualTo("Hello world"); assertThat(sleptTime).isEqualTo(0); }
@Test public void shouldIgnoreError() { // Given the HelloWorldService throws an exception BDDMockito.willThrow(new WebServiceException("BAM!")).willDoNothing().given(helloWorldService).sayHelloWorld(); // Create a Retry with default configuration RetryConfig config = RetryConfig.custom() .retryOnException(t -> t instanceof IOException) .maxAttempts(3).build(); Retry retry = Retry.of("id", config); TestSubscriber<RetryEvent.Type> testSubscriber = toFlowable(retry.getEventPublisher()) .map(RetryEvent::getEventType) .test(); // Decorate the invocation of the HelloWorldService CheckedRunnable retryableRunnable = Retry.decorateCheckedRunnable(retry, helloWorldService::sayHelloWorld); // When Try<Void> result = Try.run(retryableRunnable); // Then the helloWorldService should be invoked 2 times BDDMockito.then(helloWorldService).should(Mockito.times(1)).sayHelloWorld(); // and the result should be a sucess Assertions.assertThat(result.isFailure()).isTrue(); Assertions.assertThat(sleptTime).isEqualTo(0); testSubscriber.assertValueCount(1).assertValues(RetryEvent.Type.IGNORED_ERROR); } }
@Test public void shouldNotRetry() { // Create a Retry with default configuration Retry retryContext = Retry.ofDefaults("id"); // Decorate the invocation of the HelloWorldService Runnable runnable = Retry.decorateRunnable(retryContext, helloWorldService::sayHelloWorld); // When runnable.run(); // Then the helloWorldService should be invoked 1 time BDDMockito.then(helloWorldService).should(Mockito.times(1)).sayHelloWorld(); Assertions.assertThat(sleptTime).isEqualTo(0); }
@Test public void shouldReturnTheCorrectName() { Retry retry = retryRegistry.retry("testName"); Assertions.assertThat(retry).isNotNull(); Assertions.assertThat(retry.getName()).isEqualTo("testName"); }
@Test public void shouldRegisterMetricsWithRetry() throws Throwable { //Given RetryRegistry retryRegistry = RetryRegistry.ofDefaults(); Retry retry = retryRegistry.retry("testName"); metricRegistry.registerAll(RetryMetrics.ofRetryRegistry(retryRegistry)); // Given the HelloWorldService returns Hello world BDDMockito.given(helloWorldService.returnHelloWorld()) .willThrow(new WebServiceException("BAM!")) .willReturn("Hello world") .willThrow(new WebServiceException("BAM!")) .willThrow(new WebServiceException("BAM!")) .willThrow(new WebServiceException("BAM!")); // Setup circuitbreaker with retry String value1 = retry.executeSupplier(helloWorldService::returnHelloWorld); Try.ofSupplier(Retry.decorateSupplier(retry, helloWorldService::returnHelloWorld)); //Then assertThat(value1).isEqualTo("Hello world"); // Then the helloWorldService should be invoked 1 time BDDMockito.then(helloWorldService).should(times(5)).returnHelloWorld(); assertThat(metricRegistry.getMetrics()).hasSize(4); assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + SUCCESSFUL_CALLS_WITH_RETRY).getValue()).isEqualTo(1L); assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + SUCCESSFUL_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L); assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + FAILED_CALLS_WITH_RETRY).getValue()).isEqualTo(1L); assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + FAILED_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L); }
@Test public void testDecorateCallableWithRetryResult() throws Exception { // Given the HelloWorldService throws an exception BDDMockito.given(helloWorldService.returnHelloWorldWithException()).willThrow(new WebServiceException("BAM!")) .willReturn("Hello world"); // Create a Retry with default configuration final RetryConfig tryAgain = RetryConfig.<String>custom().retryOnResult(s -> s.contains("Hello world")) .maxAttempts(2).build(); Retry retry = Retry.of("id", tryAgain); // Decorate the invocation of the HelloWorldService Callable<String> callable = Retry.decorateCallable(retry, helloWorldService::returnHelloWorldWithException); // When String result = callable.call(); // Then the helloWorldService should be invoked 2 times BDDMockito.then(helloWorldService).should(Mockito.times(2)).returnHelloWorldWithException(); assertThat(result).isEqualTo("Hello world"); assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION); }
@Test public void testExecuteSupplierWithResult() { // Given the HelloWorldService throws an exception BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!")) .willReturn("Hello world"); // Create a Retry with default configuration final RetryConfig tryAgain = RetryConfig.<String>custom().retryOnResult(s -> s.contains("Hello world")) .maxAttempts(2).build(); Retry retry = Retry.of("id", tryAgain); // Decorate the invocation of the HelloWorldService String result = retry.executeSupplier(helloWorldService::returnHelloWorld); // Then the helloWorldService should be invoked 2 times BDDMockito.then(helloWorldService).should(Mockito.times(2)).returnHelloWorld(); assertThat(result).isEqualTo("Hello world"); assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION); }