@SuppressWarnings("unchecked")
    public <V> V run(String callableName, RetryOperation<V> operation)
    {
        int attempt = 1;
        while (true) {
            try {
                return operation.run();
            }
            catch (Exception e) {
                if (attempt >= maxAttempts || !retryPredicate.test(e)) {
                    throwIfUnchecked(e);
                    throw new RuntimeException(e);
                }

                QueryException qe = (QueryException) e;
                attempt++;
                int delayMillis = (int) min(minBackoffDelay.toMillis() * pow(scaleFactor, attempt - 1), maxBackoffDelay.toMillis());
                int jitterMillis = ThreadLocalRandom.current().nextInt(max(1, (int) (delayMillis * 0.1)));
                log.debug(
                        "Failed on executing %s with attempt %d. Retry after %sms. Cause: %s",
                        callableName,
                        attempt - 1,
                        delayMillis,
                        qe.getMessage());

                try {
                    MILLISECONDS.sleep(delayMillis + jitterMillis);
                }
                catch (InterruptedException ie) {
                    currentThread().interrupt();
                    throw new RuntimeException(ie);
                }
            }
        }
    }
@Test(expectedExceptions = QueryException.class)
    public void testNonRetryableFailure()
    {
        retryDriver.run("test", new MockOperation(3, NON_RETRYABLE_EXCEPTION));
    }