public DecorateCompletionStage<T> withRetry(AsyncRetry retryContext, ScheduledExecutorService scheduler) { stageSupplier = AsyncRetry.decorateCompletionStage(retryContext, scheduler, stageSupplier); return this; }
private AsyncRetryMetrics(String prefix, Iterable<AsyncRetry> 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()); }); }
/** * Creates a Retry with a custom Retry configuration. * * @param id the ID of the Retry * @param retryConfigSupplier a supplier of a custom Retry configuration * * @return a Retry with a custom Retry configuration. */ static AsyncRetry of(String id, Supplier<RetryConfig> retryConfigSupplier){ return of(id, retryConfigSupplier.get()); }
private void shouldCompleteFutureAfterAttemptsInCaseOfExceptionAtAsyncStage(int noOfAttempts) { CompletableFuture<String> failedFuture = new CompletableFuture<>(); failedFuture.completeExceptionally(new WebServiceException("BAM!")); // Given the HelloWorldService throws an exception BDDMockito.given(helloWorldService.returnHelloWorld()) .willReturn(failedFuture); // Create a Retry with default configuration AsyncRetry retryContext = AsyncRetry.of( "id", RetryConfig .custom() .maxAttempts(noOfAttempts) .build()); // Decorate the invocation of the HelloWorldService Supplier<CompletionStage<String>> supplier = AsyncRetry.decorateCompletionStage( retryContext, scheduler, () -> helloWorldService.returnHelloWorld()); // When Try<String> resultTry = Try.of(() -> awaitResult(supplier.get())); // Then the helloWorldService should be invoked n + 1 times BDDMockito.then(helloWorldService).should(Mockito.times(noOfAttempts)).returnHelloWorld(); Assertions.assertThat(resultTry.isFailure()).isTrue(); Assertions.assertThat(resultTry.getCause().getCause()).isInstanceOf(WebServiceException.class); }
@Test public void shouldNotRetry() throws InterruptedException, ExecutionException, TimeoutException { // Given the HelloWorldService returns Hello world BDDMockito.given(helloWorldService.returnHelloWorld()) .willReturn(completedFuture("Hello world")); // Create a Retry with default configuration AsyncRetry retryContext = AsyncRetry.ofDefaults("id"); // Decorate the invocation of the HelloWorldService Supplier<CompletionStage<String>> supplier = AsyncRetry.decorateCompletionStage( retryContext, scheduler, () -> helloWorldService.returnHelloWorld()); // When String result = awaitResult(supplier); // Then the helloWorldService should be invoked 1 time BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorld(); Assertions.assertThat(result).isEqualTo("Hello world"); }
@Test public void shouldRegisterMetricsWithRetry() { //Given AsyncRetryRegistry retryRegistry = AsyncRetryRegistry.ofDefaults(); AsyncRetry retry = retryRegistry.retry("testName"); metricRegistry.registerAll(AsyncRetryMetrics.ofAsyncRetryRegistry(retryRegistry)); // Given the HelloWorldService returns Hello world BDDMockito.given(helloWorldService.returnHelloWorld()) .willReturn(failedFuture(new WebServiceException("BAM!"))) .willReturn(CompletableFuture.completedFuture("Hello world")) .willReturn(failedFuture(new WebServiceException("BAM!"))) .willReturn(failedFuture(new WebServiceException("BAM!"))) .willReturn(failedFuture(new WebServiceException("BAM!"))); // Setup circuitbreaker with retry String value1 = awaitResult(retry.executeCompletionStage(scheduler, helloWorldService::returnHelloWorld)); Try.ofCallable(() -> awaitResult(AsyncRetry.decorateCompletionStage(retry, scheduler, helloWorldService::returnHelloWorld).get())); //Then assertThat(value1).isEqualTo("Hello world"); // Then the helloWorldService should be invoked 5 times 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 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 = AsyncRetry.of("testName", retryConfig); retry.getEventPublisher() .onIgnoredError(event -> logger.info(event.getEventType().toString())); Try.of(() -> awaitResult(retry.executeCompletionStage(scheduler, () -> helloWorldService.returnHelloWorld()))); then(logger).should(times(1)).info("IGNORED_ERROR"); then(helloWorldService).should(times(1)).returnHelloWorld(); }
@Test public void shouldConsumeOnSuccessEvent() throws Exception { CompletableFuture<String> failedFuture = new CompletableFuture<>(); failedFuture.completeExceptionally(new WebServiceException("BAM!")); CountDownLatch latch = new CountDownLatch(1); // Given the HelloWorldService returns Hello world given(helloWorldService.returnHelloWorld()) .willReturn(failedFuture) .willReturn(completedFuture("Hello world")); retry.getEventPublisher() .onSuccess(event -> { logger.info(event.getEventType().toString()); latch.countDown(); }); String result = awaitResult(retry.executeCompletionStage(scheduler, () -> helloWorldService.returnHelloWorld())); assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); assertThat(result).isEqualTo("Hello world"); then(helloWorldService).should(times(2)).returnHelloWorld(); then(logger).should(times(1)).info("SUCCESS"); }
@Before public void setUp(){ helloWorldService = mock(AsyncHelloWorldService.class); logger = mock(Logger.class); retry = AsyncRetry.ofDefaults("testName"); }
@Test public void shouldUseCustomPrefix() { //Given AsyncRetryRegistry retryRegistry = AsyncRetryRegistry.ofDefaults(); AsyncRetry retry = retryRegistry.retry("testName"); metricRegistry.registerAll(AsyncRetryMetrics.ofAsyncRetryRegistry("testPrefix",retryRegistry)); // Given the HelloWorldService returns Hello world BDDMockito.given(helloWorldService.returnHelloWorld()).willReturn(CompletableFuture.completedFuture("Hello world")); String value = awaitResult(retry.executeCompletionStage(scheduler, helloWorldService::returnHelloWorld)); //Then assertThat(value).isEqualTo("Hello world"); // Then the helloWorldService should be invoked 1 time BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld(); assertThat(metricRegistry.getMetrics()).hasSize(4); assertThat(metricRegistry.getGauges().get("testPrefix.testName." + SUCCESSFUL_CALLS_WITH_RETRY).getValue()).isEqualTo(0L); assertThat(metricRegistry.getGauges().get("testPrefix.testName." + SUCCESSFUL_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(1L); assertThat(metricRegistry.getGauges().get("testPrefix.testName." + FAILED_CALLS_WITH_RETRY).getValue()).isEqualTo(0L); assertThat(metricRegistry.getGauges().get("testPrefix.testName." + FAILED_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L); }
@Test public void shouldReturnTheCorrectName() { AsyncRetry retry = retryRegistry.retry("testName"); Assertions.assertThat(retry).isNotNull(); Assertions.assertThat(retry.getName()).isEqualTo("testName"); }
/** * Decorates CompletionStageSupplier with Retry * * @param retry the retry context * @param scheduler execution service to use to schedule retries * @param supplier completion stage supplier * @param <T> type of completion stage result * @return decorated supplier */ static <T> Supplier<CompletionStage<T>> decorateCompletionStage( AsyncRetry retry, ScheduledExecutorService scheduler, Supplier<CompletionStage<T>> supplier ) { return () -> { final CompletableFuture<T> promise = new CompletableFuture<>(); @SuppressWarnings("unchecked") final Runnable block = new AsyncRetryBlock<>(scheduler, retry.context(), supplier, promise); block.run(); return promise; }; }
@Test public void shouldReturnTheSameConsumer() { AsyncRetry.EventPublisher eventPublisher = retry.getEventPublisher(); AsyncRetry.EventPublisher eventPublisher2 = retry.getEventPublisher(); assertThat(eventPublisher).isEqualTo(eventPublisher2); }
private void shouldCompleteFutureAfterAttemptsInCaseOfExceptionAtSyncStage(int noOfAttempts) { // Given the HelloWorldService throws an exception BDDMockito.given(helloWorldService.returnHelloWorld()) .willThrow(new WebServiceException("BAM!")); // Create a Retry with default configuration AsyncRetry retryContext = AsyncRetry.of( "id", RetryConfig .custom() .maxAttempts(noOfAttempts) .build()); // Decorate the invocation of the HelloWorldService Supplier<CompletionStage<String>> supplier = AsyncRetry.decorateCompletionStage( retryContext, scheduler, () -> helloWorldService.returnHelloWorld()); // When Try<String> resultTry = Try.of(() -> awaitResult(supplier.get())); // Then the helloWorldService should be invoked n + 1 times BDDMockito.then(helloWorldService).should(Mockito.times(noOfAttempts)).returnHelloWorld(); Assertions.assertThat(resultTry.isFailure()).isTrue(); Assertions.assertThat(resultTry.getCause().getCause()).isInstanceOf(WebServiceException.class); }
@Test public void shouldRetryInCaseOfAnExceptionAtAsyncStage() { CompletableFuture<String> failedFuture = new CompletableFuture<>(); failedFuture.completeExceptionally(new WebServiceException("BAM!")); // Given the HelloWorldService throws an exception BDDMockito.given(helloWorldService.returnHelloWorld()) .willReturn(failedFuture) .willReturn(completedFuture("Hello world")); // Create a Retry with default configuration AsyncRetry retryContext = AsyncRetry.ofDefaults("id"); // Decorate the invocation of the HelloWorldService Supplier<CompletionStage<String>> supplier = AsyncRetry.decorateCompletionStage( retryContext, scheduler, () -> helloWorldService.returnHelloWorld()); // When String result = awaitResult(supplier.get()); // Then the helloWorldService should be invoked 2 times BDDMockito.then(helloWorldService).should(Mockito.times(2)).returnHelloWorld(); Assertions.assertThat(result).isEqualTo("Hello world"); }
@Test public void shouldConsumeOnErrorEvent() { CompletableFuture<String> failedFuture = new CompletableFuture<>(); failedFuture.completeExceptionally(new WebServiceException("BAM!")); given(helloWorldService.returnHelloWorld()) .willReturn(failedFuture); retry.getEventPublisher() .onError(event -> logger.info(event.getEventType().toString())); Try.of(() -> awaitResult(retry.executeCompletionStage(scheduler, () -> helloWorldService.returnHelloWorld()))); then(logger).should(times(1)).info("ERROR"); then(helloWorldService).should(times(3)).returnHelloWorld(); }
@Test public void testDecorateCompletionStage() throws ExecutionException, InterruptedException { // Given the HelloWorldService returns Hello world given(helloWorldService.returnHelloWorld()).willReturn("Hello world"); CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("helloBackend"); Supplier<CompletionStage<String>> completionStageSupplier = () -> CompletableFuture.supplyAsync(helloWorldService::returnHelloWorld); CompletionStage<String> completionStage = Decorators.ofCompletionStage(completionStageSupplier) .withCircuitBreaker(circuitBreaker) .withRetry(AsyncRetry.ofDefaults("id"), Executors.newSingleThreadScheduledExecutor()) .withBulkhead(Bulkhead.ofDefaults("testName")) .get(); String value = completionStage.toCompletableFuture().get(); assertThat(value).isEqualTo("Hello world"); CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(1); assertThat(metrics.getNumberOfSuccessfulCalls()).isEqualTo(1); // Then the helloWorldService should be invoked 1 time BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld(); }
@Test public void shouldRegisterMetricsWithoutRetry() { //Given AsyncRetryRegistry retryRegistry = AsyncRetryRegistry.ofDefaults(); AsyncRetry retry = retryRegistry.retry("testName"); metricRegistry.registerAll(AsyncRetryMetrics.ofAsyncRetryRegistry(retryRegistry)); // Given the HelloWorldService returns Hello world BDDMockito.given(helloWorldService.returnHelloWorld()) .willReturn(CompletableFuture.completedFuture("Hello world")); // Setup circuitbreaker with retry String value = awaitResult(retry.executeCompletionStage(scheduler, helloWorldService::returnHelloWorld)); //Then assertThat(value).isEqualTo("Hello world"); // Then the helloWorldService should be invoked 1 time BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld(); assertThat(metricRegistry.getMetrics()).hasSize(4); assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + SUCCESSFUL_CALLS_WITH_RETRY).getValue()).isEqualTo(0L); assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + SUCCESSFUL_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(1L); assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + FAILED_CALLS_WITH_RETRY).getValue()).isEqualTo(0L); assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + FAILED_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L); }
@Test public void shouldNotRetryWithThatResult() throws InterruptedException, ExecutionException, TimeoutException { // Given the HelloWorldService returns Hello world BDDMockito.given(helloWorldService.returnHelloWorld()) .willReturn(completedFuture("Hello world")); // Create a Retry with default configuration final RetryConfig retryConfig = RetryConfig.<String>custom().retryOnResult(s -> s.contains("NoRetry")) .maxAttempts(1) .build(); AsyncRetry retryContext = AsyncRetry.of("id", retryConfig); // Decorate the invocation of the HelloWorldService Supplier<CompletionStage<String>> supplier = AsyncRetry.decorateCompletionStage( retryContext, scheduler, () -> helloWorldService.returnHelloWorld()); // When String result = awaitResult(supplier); // Then the helloWorldService should be invoked 1 time BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorld(); Assertions.assertThat(result).isEqualTo("Hello world"); // for code quality scan , it does not not recognize assertJ do not why Assert.assertEquals(result, "Hello world"); }
@Test public void shouldRetryInCaseOfExceptionAtSyncStage() { // Given the HelloWorldService throws an exception BDDMockito.given(helloWorldService.returnHelloWorld()) .willThrow(new WebServiceException("BAM!")) .willReturn(completedFuture("Hello world")); // Create a Retry with default configuration AsyncRetry retryContext = AsyncRetry.ofDefaults("id"); // Decorate the invocation of the HelloWorldService Supplier<CompletionStage<String>> supplier = AsyncRetry.decorateCompletionStage( retryContext, scheduler, () -> helloWorldService.returnHelloWorld()); // When String result = awaitResult(supplier.get()); // Then the helloWorldService should be invoked 2 times BDDMockito.then(helloWorldService).should(Mockito.times(2)).returnHelloWorld(); Assertions.assertThat(result).isEqualTo("Hello world"); }