private static <T> void retryOperation(final CompletableFuture<T> resultFuture,
      final Supplier<CompletableFuture<T>> operation, final int retries, Predicate<Throwable> retryPredicate,
      Consumer<Throwable> retryErrorConsumer) {

    if (!resultFuture.isDone()) {
      final CompletableFuture<T> operationFuture = operation.get();

      operationFuture.whenCompleteAsync((t, throwable) -> {
        if (throwable != null) {
          if (throwable instanceof CancellationException) {
            resultFuture.completeExceptionally(throwable);
          } else {
            throwable = extractFutureExceptionCause(throwable);
            if (!retryPredicate.test(throwable)) {
              resultFuture.completeExceptionally(throwable);
            } else if (retries > 0) {
              retryErrorConsumer.accept(throwable);
              retryOperation(resultFuture, operation, retries - 1, retryPredicate, retryErrorConsumer);
            } else {
              resultFuture.completeExceptionally(throwable);
            }
          }
        } else {
          resultFuture.complete(t);
        }
      });
    }
  }
Helper method which retries the provided operation in case of a failure.
@param <T> type of the future's result
@param resultFuture to complete
@param operation to retry
@param retries until giving up
@param retryPredicate Predicate to test whether an exception is retryable
@param retryErrorConsumer {@link Consumer} of errors during retry operation
ambry-commons/src/main/java/com/github/ambry/commons/FutureUtils.java