code: public static <T> T retry(
        final Task<T> f,
        final Predicate<Throwable> shouldRetry,
        final int quietTries,
        final int maxTries,
        @Nullable final CleanupAfterFailure cleanupAfterFailure,
        @Nullable final String messageOnRetry
    ) throws Exception
    {
      Preconditions.checkArgument(maxTries > 0, "maxTries > 0");
      Preconditions.checkArgument(quietTries >= 0, "quietTries >= 0");
      int nTry = 0;
      final int maxRetries = maxTries - 1;
      while (true) {
        try {
          nTry++;
          return f.perform();
        }
        catch (Throwable e) {
          if (cleanupAfterFailure != null) {
            cleanupAfterFailure.cleanup();
          }
          if (nTry < maxTries && shouldRetry.apply(e)) {
            awaitNextRetry(e, messageOnRetry, nTry, maxRetries, nTry <= quietTries);
          } else {
            Throwables.propagateIfInstanceOf(e, Exception.class);
            throw new RuntimeException(e);
          }
        }
      }
    }
docs: Retry an operation using fuzzy exponentially increasing backoff. The wait time after the nth failed attempt is
  min(60000ms, 1000ms * pow(2, n - 1)), fuzzed by a number drawn from a Gaussian distribution with mean 0 and
  standard deviation 0.2.
  
  If maxTries is exhausted, or if shouldRetry returns false, the last exception thrown by "f" will be thrown
  by this function.
  
  @param f           the operation
  @param shouldRetry predicate determining whether we should retry after a particular exception thrown by "f"
  @param quietTries  first quietTries attempts will log exceptions at DEBUG level rather than WARN
  @param maxTries    maximum number of attempts
  
  @return result of the first successful operation
  
  @throws Exception if maxTries is exhausted, or shouldRetry returns false