public static <T> CompletableFuture<T> retry(final Supplier<CompletableFuture<T>> operation, final int retries,
      Predicate<Throwable> retryPredicate, Consumer<Throwable> retryErrorConsumer,
      Consumer<Throwable> finalErrorConsumer) {
    final CompletableFuture<T> resultFuture = new CompletableFuture<>();
    retryOperation(resultFuture, operation, retries, retryPredicate, retryErrorConsumer);
    resultFuture.whenComplete((t, throwable) -> {
      if (throwable != null) {
        finalErrorConsumer.accept(throwable);
      }
    });
    return resultFuture;
  }
Retry the given operation the given number of times in case of a failure.
@param <T> type of the result
@param operation to executed
@param retries if the operation failed
@param retryPredicate Predicate to test whether an exception is retryable
@param retryErrorConsumer {@link Consumer} of errors during retry operation
@param finalErrorConsumer {@link Consumer} of final error after all retries are completed
@return Future containing either the result of the operation or a {@link Exception} if error occurred.
ambry-commons/src/main/java/com/github/ambry/commons/FutureUtils.java