code: public static <V> V callWithRetries(
        int nAttempts,
        int initialRetrySec,
        Callable<V> action,
        IPredicate<Exception> retryableException)
        throws Exception {
      int retryDelaySec = initialRetrySec;
      for (int attemptsLeftAfterThis = nAttempts - 1;
          attemptsLeftAfterThis >= 0;
          attemptsLeftAfterThis--) {
        try {
          return action.call();
        } catch (Exception e) {
          if (!retryableException.test(e)) {
            throw e;
          }
          if (attemptsLeftAfterThis <= 0) {
            throw new RuntimeException("Ultimately failed after " + nAttempts + " attempts.", e);
          }
          log.warn("Failed; {} attempts left: {}", e.toString(), attemptsLeftAfterThis);
        }
        retryDelaySec = pauseAndIncrease(retryDelaySec);
      }
      throw new RuntimeException("Failed; total attempts allowed: " + nAttempts);
    }
docs: Allows a client to attempt a call and retry a finite amount of times if the exception thrown is
  the right kind. The retries back off exponentially.
  
  @param nAttempts Number of attempts before giving up
  @param initialRetrySec Number of seconds to wait before trying again on the first retry.
  @param action A callable or lambda expression that contains the code that will be tried and
  retried, if necessary.
  @param retryableException An instance of {@link org.knowm.xchange.utils.retries.IPredicate}
  that will be used to check if the exception caught is retryable, which can be any complex
  criteria that the user defines.
  @return
  @throws Exception If the exception isn't retryable, it's immediately thrown again. If it is
  retryable, then a RunTimeException is thrown after the allowed number of retries is
  exhausted.
  @author Matija Mazi and Bryan Hernandez