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
    @SuppressWarnings("unchecked")
    public void testExceptionsReachMaxRetry() throws Exception {
        ExponentialBackoff backoff = new ExponentialBackoff(2L);
        CheckedSupplier<Boolean> supplier = Mockito.mock(CheckedSupplier.class);
        Mockito.when(supplier.get()).thenThrow(new IOException("can't write to disk"));
        Assertions.assertThatThrownBy(() -> backoff.retryable(supplier))
                .isInstanceOf(ExponentialBackoff.RetryException.class)
                .hasMessageContaining("max retry");
    }