public <T> T run(Operation<T> f) { TaskState retryState = state.nestedState(stateKey); TaskState operationState = retryState.nestedState(OPERATION); T result; try { result = f.perform(operationState); } catch (TaskExecutionException e) { throw e; } catch (Exception e) { String formattedErrorMessage = String.format(errorMessage, errorMessageParameters); if (!retry(e)) { logger.warn("{}: giving up", formattedErrorMessage, e); throw new TaskExecutionException(e, TaskExecutionException.buildExceptionErrorConfig(e)); } int retryIteration = retryState.params().get(RETRY, int.class, 0); retryState.params().set(RETRY, retryIteration + 1); int interval = (int) Math.min( retryInterval.min().getSeconds() * Math.pow(2, retryIteration), retryInterval.max().getSeconds()); logger.warn("{}: retrying in {} seconds", formattedErrorMessage, interval, e); throw state.pollingTaskExecutionException(interval); } // Clear retry state retryState.params().remove(RETRY); return result; }
public class PollingRetryExecutor { private static Logger logger = LoggerFactory.getLogger(PollingRetryExecutor.class); private static final DurationInterval DEFAULT_RETRY_INTERVAL = DurationInterval.of(Duration.ofSeconds(1), Duration.ofSeconds(30)); private static final String DEFAULT_ERROR_MESSAGE = "Operation failed"; private static final Object[] DEFAULT_ERROR_MESSAGE_PARAMETERS = {}; private static final List<Predicate<Exception>> DEAFULT_RETRY_PREDICATES = ImmutableList.of(); private static final String RESULT = "result"; private static final String DONE = "done"; private static final String RETRY = "retry"; private static final String OPERATION = "operation"; private final TaskState state; private final String stateKey; private final DurationInterval retryInterval; private final List<Predicate<Exception>> retryPredicates; private final String errorMessage; private final Object[] errorMessageParameters; private PollingRetryExecutor( TaskState state, String stateKey, DurationInterval retryInterval, List<Predicate<Exception>> retryPredicates, String errorMessage, Object... errorMessageParameters) { this.state = Objects.requireNonNull(state, "state"); this.stateKey = Objects.requireNonNull(stateKey, "stateKey"); this.retryInterval = Objects.requireNonNull(retryInterval, "retryInterval"); this.retryPredicates = Objects.requireNonNull(retryPredicates, "retryPredicates"); this.errorMessage = Objects.requireNonNull(errorMessage, "errorMessage"); this.errorMessageParameters = Objects.requireNonNull(errorMessageParameters, "errorMessageParameters"); } public static PollingRetryExecutor pollingRetryExecutor(TaskState state, String stateKey) { return new PollingRetryExecutor( state, stateKey, DEFAULT_RETRY_INTERVAL, DEAFULT_RETRY_PREDICATES, DEFAULT_ERROR_MESSAGE, DEFAULT_ERROR_MESSAGE_PARAMETERS); } public PollingRetryExecutor withErrorMessage( String errorMessage, Object... errorMessageParameters) { return new PollingRetryExecutor( state, stateKey, retryInterval, retryPredicates, errorMessage, errorMessageParameters); } public PollingRetryExecutor retryUnless(Predicate<Exception> predicate) { return retryIf(e -> !predicate.test(e)); } public <T extends Exception> PollingRetryExecutor retryUnless( Class<T> exceptionClass, Predicate<T> retryPredicate) { return retryUnless( e -> (exceptionClass.isInstance(e) && retryPredicate.test(exceptionClass.cast(e)))); } public <T extends Exception> PollingRetryExecutor retryIf( Class<T> exceptionClass, Predicate<T> retryPredicate) { return retryIf( e -> (exceptionClass.isInstance(e) && retryPredicate.test(exceptionClass.cast(e)))); } public PollingRetryExecutor retryIf(Predicate<Exception> retryPredicate) { List<Predicate<Exception>> newRetryPredicates = ImmutableList.<Predicate<Exception>>builder() .addAll(retryPredicates) .add(retryPredicate) .build(); return new PollingRetryExecutor( state, stateKey, retryInterval, newRetryPredicates, errorMessage, errorMessageParameters); } public PollingRetryExecutor withRetryInterval(DurationInterval retryInterval) { return new PollingRetryExecutor( state, stateKey, retryInterval, retryPredicates, errorMessage, errorMessageParameters); } public void runOnce(Action f) { runOnce( Void.class, taskState -> { f.perform(taskState); return null; }); } public <T> T runOnce(TypeReference<T> type, Operation<T> f) { return runOnce(TypeFactory.defaultInstance().constructType(type), f); } public <T> T runOnce(Class<T> type, Operation<T> f) { return runOnce(TypeFactory.defaultInstance().constructType(type), f); } private <T> T runOnce(JavaType type, Operation<T> f) { TaskState retryState = state.nestedState(stateKey); boolean done = retryState.params().get(DONE, boolean.class, false); T result = get(type, retryState.params()); if (done) { return result; } result = run(f); retryState.params().set(RESULT, result); retryState.params().set(DONE, true); return result; } public void runAction(Action f) { run( operationState -> { f.perform(operationState); return null; }); } public <T> T run(Operation<T> f) { TaskState retryState = state.nestedState(stateKey); TaskState operationState = retryState.nestedState(OPERATION); T result; try { result = f.perform(operationState); } catch (TaskExecutionException e) { throw e; } catch (Exception e) { String formattedErrorMessage = String.format(errorMessage, errorMessageParameters); if (!retry(e)) { logger.warn("{}: giving up", formattedErrorMessage, e); throw new TaskExecutionException(e, TaskExecutionException.buildExceptionErrorConfig(e)); } int retryIteration = retryState.params().get(RETRY, int.class, 0); retryState.params().set(RETRY, retryIteration + 1); int interval = (int) Math.min( retryInterval.min().getSeconds() * Math.pow(2, retryIteration), retryInterval.max().getSeconds()); logger.warn("{}: retrying in {} seconds", formattedErrorMessage, interval, e); throw state.pollingTaskExecutionException(interval); } // Clear retry state retryState.params().remove(RETRY); return result; } private boolean retry(Exception e) { if (retryPredicates.isEmpty()) { return true; } for (Predicate<Exception> retryPredicate : retryPredicates) { if (retryPredicate.test(e)) { return true; } } return false; } @SuppressWarnings("unchecked") private static <T> T get(JavaType type, Config config) { return (T) config.get(RESULT, type, null); } }