package io.github.resilience4j.retry; import io.github.resilience4j.retry.event.RetryEvent; import io.github.resilience4j.retry.internal.AsyncRetryContext; import io.reactivex.Flowable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; public interface AsyncRetry { /** * Returns the ID of this Retry. * * @return the ID of this Retry */ String getName(); /** * Records a successful call. */ void onSuccess(); /** * Records an failed call. * @param throwable the exception to handle * @return delay in milliseconds until the next try */ long onError(Throwable throwable); /** * Returns a reactive stream of RetryEvents. * * @return a reactive stream of RetryEvents */ Flowable<RetryEvent> getEventStream(); /** * Creates a Retry with a custom Retry configuration. * * @param id the ID of the Retry * @param retryConfig a custom Retry configuration * * @return a Retry with a custom Retry configuration. */ static AsyncRetry of(String id, RetryConfig retryConfig){ return new AsyncRetryContext(id, retryConfig); } /** * 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()); } /** * Creates a Retry with default configuration. * * @param id the ID of the Retry * @return a Retry with default configuration */ static AsyncRetry ofDefaults(String id){ return of(id, RetryConfig.ofDefaults()); } /** * Decorates CompletionStageSupplier with Retry * * @param retryContext 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 retryContext, ScheduledExecutorService scheduler, Supplier<CompletionStage<T>> supplier ) { return () -> { final CompletableFuture<T> promise = new CompletableFuture<>(); final Runnable block = new AsyncRetryBlock<>(scheduler, retryContext, supplier, promise); block.run(); return promise; }; } /** * Get the Metrics of this RateLimiter. * * @return the Metrics of this RateLimiter */ Metrics getMetrics(); interface Metrics { /** * Returns how many attempts this have been made by this retry. * * @return how many retries have been attempted, but failed. */ int getNumAttempts(); /** * Returns how many retry attempts are allowed before failure. * * @return how many retries are allowed before failure. */ int getMaxAttempts(); } } class AsyncRetryBlock<T> implements Runnable { private final ScheduledExecutorService scheduler; private final AsyncRetry retryContext; private final Supplier<CompletionStage<T>> supplier; private final CompletableFuture<T> promise; AsyncRetryBlock( ScheduledExecutorService scheduler, AsyncRetry retryContext, Supplier<CompletionStage<T>> supplier, CompletableFuture<T> promise ) { this.scheduler = scheduler; this.retryContext = retryContext; this.supplier = supplier; this.promise = promise; } @Override public void run() { final CompletionStage<T> stage; try { stage = supplier.get(); } catch (Throwable t) { onError(t); return; } stage.whenComplete((result, t) -> { if (t != null) { onError(t); } else { promise.complete(result); retryContext.onSuccess(); } }); } private void onError(Throwable t) { final long delay = retryContext.onError(t); if (delay < 1) { promise.completeExceptionally(t); } else { scheduler.schedule(this, delay, TimeUnit.MILLISECONDS); } } }