package org.embulk.spi.util; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; public class RetryExecutor { public static RetryExecutor retryExecutor() { // TODO default configuration return new RetryExecutor(3, 500, 30*60*1000); } public static class RetryGiveupException extends ExecutionException { public RetryGiveupException(String message, Exception cause) { super(cause); } public RetryGiveupException(Exception cause) { super(cause); } public Exception getCause() { return (Exception) super.getCause(); } } public static interface Retryable extends Callable { public T call() throws Exception; public boolean isRetryableException(Exception exception); public void onRetry(Exception exception, int retryCount, int retryLimit, int retryWait) throws RetryGiveupException; public void onGiveup(Exception firstException, Exception lastException) throws RetryGiveupException; } private final int retryLimit; private final int initialRetryWait; private final int maxRetryWait; private RetryExecutor(int retryLimit, int initialRetryWait, int maxRetryWait) { this.retryLimit = retryLimit; this.initialRetryWait = initialRetryWait; this.maxRetryWait = maxRetryWait; } public RetryExecutor withRetryLimit(int count) { return new RetryExecutor(count, initialRetryWait, maxRetryWait); } public RetryExecutor withInitialRetryWait(int msec) { return new RetryExecutor(retryLimit, msec, maxRetryWait); } public RetryExecutor withMaxRetryWait(int msec) { return new RetryExecutor(retryLimit, initialRetryWait, msec); } public T runInterruptible(Retryable op) throws InterruptedException, RetryGiveupException { return run(op, true); } public T run(Retryable op) throws RetryGiveupException { try { return run(op, false); } catch (InterruptedException ex) { throw new RetryGiveupException("Unexpected interruption", ex); } } private T run(Retryable op, boolean interruptible) throws InterruptedException, RetryGiveupException { int retryWait = initialRetryWait; int retryCount = 0; Exception firstException = null; while(true) { try { return op.call(); } catch (Exception exception) { if (firstException == null) { firstException = exception; } if (!op.isRetryableException(exception) || retryCount >= retryLimit) { op.onGiveup(firstException, exception); throw new RetryGiveupException(firstException); } retryCount++; op.onRetry(exception, retryCount, retryLimit, retryWait); try { Thread.sleep(retryWait); } catch (InterruptedException ex) { if (interruptible) { throw ex; } } // exponential back-off with hard limit retryWait *= 2; if (retryWait > maxRetryWait) { retryWait = maxRetryWait; } } } } }