public <T> T retryable(CheckedSupplier<T> action) throws RetryException {
        long attempt = 0L;

        do {
            try {
                attempt++;
                return action.get();
            } catch (Exception ex) {
                logger.error("Backoff retry exception", ex);
            }

            if (hasRetry(attempt)) {
                try {
                    int ms = backoffTime(attempt);
                    logger.info("Retry({}) will execute in {} second", attempt, ms/1000.0);
                    Thread.sleep(ms);
                } catch (InterruptedException e) {
                    throw new RetryException("Backoff retry aborted", e);
                }
            }
        } while (hasRetry(attempt));

        throw new RetryException("Reach max retry");
    }
@Test
    public void testWithoutException() throws Exception {
        ExponentialBackoff backoff = new ExponentialBackoff(1L);
        CheckedSupplier<Integer> supplier = () -> 1 + 1;
        Assertions.assertThatCode(() -> backoff.retryable(supplier)).doesNotThrowAnyException();
        Assertions.assertThat(backoff.retryable(supplier)).isEqualTo(2);
    }