package com.scopely.infrastructure.kinesis; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Optional; import java.util.function.Predicate; /** * Made (with love?) by Erik Price */ public class ExponentialBackoffRunner { private static final Logger LOGGER = LoggerFactory.getLogger(ExponentialBackoffRunner.class); @FunctionalInterface public interface CheckedExceptionTask<U> { U run() throws Throwable; } /** * Repeatedly run a task until it succeeds, or the specified timeout period * has been hit, whichever comes first. * * @param task Task to be run with retry * @param retryPredicate Predicate that evaluates true on throwables that we should retry * @param timeoutMillis Maximum time to wait for success */ public static <T> Optional<T> run(CheckedExceptionTask<T> task, Predicate<Throwable> retryPredicate, long timeoutMillis) throws Throwable { final long endTime = System.currentTimeMillis() + timeoutMillis; for (int n = 0; System.currentTimeMillis() < endTime; ++n) { try { return Optional.of(task.run()); } catch (Throwable e) { if (!retryPredicate.test(e)) { // If we get some unknown kind of error, just rethrow throw e; } long sleepTime = 1000 * (1 << n); // Make sure we don't oversleep too much if (System.currentTimeMillis() + sleepTime >= endTime) { sleepTime = Math.max(endTime - System.currentTimeMillis(), 1); } try { Thread.sleep(sleepTime); } catch (InterruptedException ex) { LOGGER.info("Thread.sleep() interrupted", ex); } } } LOGGER.warn("No success after retry timeout expired"); return Optional.empty(); } }