diff --git a/src/main/java/org/springframework/retry/backoff/LastBackoffPeriodSupplier.java b/src/main/java/org/springframework/retry/backoff/BackoffPeriodSupplier.java similarity index 54% rename from src/main/java/org/springframework/retry/backoff/LastBackoffPeriodSupplier.java rename to src/main/java/org/springframework/retry/backoff/BackoffPeriodSupplier.java index 0487dd61..bd97cc3e 100644 --- a/src/main/java/org/springframework/retry/backoff/LastBackoffPeriodSupplier.java +++ b/src/main/java/org/springframework/retry/backoff/BackoffPeriodSupplier.java @@ -2,5 +2,6 @@ import java.util.function.Supplier; -public interface LastBackoffPeriodSupplier extends Supplier { +public interface BackoffPeriodSupplier extends Supplier { + } diff --git a/src/main/java/org/springframework/retry/backoff/RememberPeriodSleeper.java b/src/main/java/org/springframework/retry/backoff/RememberPeriodSleeper.java index 4a61d369..10a7feae 100644 --- a/src/main/java/org/springframework/retry/backoff/RememberPeriodSleeper.java +++ b/src/main/java/org/springframework/retry/backoff/RememberPeriodSleeper.java @@ -3,20 +3,21 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -public class RememberPeriodSleeper implements Sleeper, LastBackoffPeriodSupplier { +public class RememberPeriodSleeper implements Sleeper, BackoffPeriodSupplier { - private static final Log logger = LogFactory.getLog(RememberPeriodSleeper.class); + private static final Log logger = LogFactory.getLog(RememberPeriodSleeper.class); - private volatile Long lastBackoffPeriod; + private volatile Long lastBackoffPeriod; - @Override - public void sleep(long backOffPeriod) { - logger.debug("Remembering a sleeping period instead of sleeping: " + backOffPeriod); - lastBackoffPeriod = backOffPeriod; - } + @Override + public void sleep(long backOffPeriod) { + logger.debug("Remembering a sleeping period instead of sleeping: " + backOffPeriod); + lastBackoffPeriod = backOffPeriod; + } + + @Override + public Long get() { + return lastBackoffPeriod; + } - @Override - public Long get() { - return lastBackoffPeriod; - } } diff --git a/src/main/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptor.java b/src/main/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptor.java index f769742a..4a540ced 100644 --- a/src/main/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptor.java +++ b/src/main/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptor.java @@ -25,7 +25,6 @@ import org.springframework.classify.Classifier; import org.springframework.retry.RecoveryCallback; -import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; import org.springframework.retry.RetryOperations; import org.springframework.retry.RetryState; diff --git a/src/main/java/org/springframework/retry/support/AsyncRetryResultProcessor.java b/src/main/java/org/springframework/retry/support/AsyncRetryResultProcessor.java index ab0b1b1a..c9b7e60a 100644 --- a/src/main/java/org/springframework/retry/support/AsyncRetryResultProcessor.java +++ b/src/main/java/org/springframework/retry/support/AsyncRetryResultProcessor.java @@ -16,69 +16,64 @@ package org.springframework.retry.support; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.retry.RetryContext; import org.springframework.retry.RetryException; -import org.springframework.retry.backoff.LastBackoffPeriodSupplier; +import org.springframework.retry.backoff.BackoffPeriodSupplier; /** * @author Dave Syer + * @param The result type */ public abstract class AsyncRetryResultProcessor implements RetryResultProcessor { - private static final Log logger = LogFactory.getLog(AsyncRetryResultProcessor.class); - protected T doNewAttempt(Supplier> supplier) throws Throwable { - logger.debug("Performing the next async callback invocation..."); - return supplier.get().getOrThrow(); - } + private static final Log logger = LogFactory.getLog(AsyncRetryResultProcessor.class); + + protected T doNewAttempt(Supplier> supplier) throws Throwable { + logger.debug("Performing the next async callback invocation..."); + return supplier.get().getOrThrow(); + } + + protected abstract T scheduleNewAttemptAfterDelay(Supplier> supplier, + ScheduledExecutorService reschedulingExecutor, long rescheduleAfterMillis, RetryContext ctx) + throws Throwable; - protected abstract T scheduleNewAttemptAfterDelay( - Supplier> supplier, - ScheduledExecutorService reschedulingExecutor, - long rescheduleAfterMillis, - RetryContext ctx - ) throws Throwable; + protected T handleException(Supplier> supplier, Consumer handler, Throwable throwable, + ScheduledExecutorService reschedulingExecutor, BackoffPeriodSupplier lastBackoffPeriodSupplier, + RetryContext ctx) { + try { + handler.accept(unwrapIfNeed(throwable)); - protected T handleException(Supplier> supplier, - Consumer handler, - Throwable throwable, - ScheduledExecutorService reschedulingExecutor, - LastBackoffPeriodSupplier lastBackoffPeriodSupplier, - RetryContext ctx) { - try { - handler.accept(unwrapIfNeed(throwable)); + if (reschedulingExecutor == null || lastBackoffPeriodSupplier == null) { + return doNewAttempt(supplier); + } + else { + long rescheduleAfterMillis = lastBackoffPeriodSupplier.get(); + logger.debug("Scheduling a next retry with a delay = " + rescheduleAfterMillis + " millis..."); + return scheduleNewAttemptAfterDelay(supplier, reschedulingExecutor, rescheduleAfterMillis, ctx); + } + } + catch (Throwable t) { + throw RetryTemplate.runtimeException(unwrapIfNeed(t)); + } + } - if (reschedulingExecutor == null || lastBackoffPeriodSupplier == null) { - return doNewAttempt(supplier); - } else { - long rescheduleAfterMillis = lastBackoffPeriodSupplier.get(); - logger.debug("Scheduling a next retry with a delay = " + rescheduleAfterMillis + " millis..."); - return scheduleNewAttemptAfterDelay(supplier, reschedulingExecutor, rescheduleAfterMillis, ctx); - } - } - catch (Throwable t) { - throw RetryTemplate.runtimeException(unwrapIfNeed(t)); - } - } + static Throwable unwrapIfNeed(Throwable throwable) { + if (throwable instanceof ExecutionException || throwable instanceof CompletionException + || throwable instanceof RetryException) { + return throwable.getCause(); + } + else { + return throwable; + } + } - static Throwable unwrapIfNeed(Throwable throwable) { - if (throwable instanceof ExecutionException - || throwable instanceof CompletionException - || throwable instanceof RetryException) { - return throwable.getCause(); - } else { - return throwable; - } - } } diff --git a/src/main/java/org/springframework/retry/support/CompletableFutureRetryResultProcessor.java b/src/main/java/org/springframework/retry/support/CompletableFutureRetryResultProcessor.java index a444f8d6..b5658bc0 100644 --- a/src/main/java/org/springframework/retry/support/CompletableFutureRetryResultProcessor.java +++ b/src/main/java/org/springframework/retry/support/CompletableFutureRetryResultProcessor.java @@ -17,8 +17,6 @@ package org.springframework.retry.support; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -27,10 +25,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryException; -import org.springframework.retry.backoff.LastBackoffPeriodSupplier; +import org.springframework.retry.backoff.BackoffPeriodSupplier; /** * A {@link RetryResultProcessor} for a {@link CompletableFuture}. If a @@ -38,48 +36,45 @@ * used internally by the {@link RetryTemplate} to wrap it and process the result. * * @author Dave Syer + * @param The result type */ -public class CompletableFutureRetryResultProcessor - extends AsyncRetryResultProcessor> { +public class CompletableFutureRetryResultProcessor extends AsyncRetryResultProcessor> { protected final Log logger = LogFactory.getLog(getClass()); @Override public Result> process(CompletableFuture completable, - Supplier>> supplier, - Consumer handler, ScheduledExecutorService reschedulingExecutor, - LastBackoffPeriodSupplier lastBackoffPeriodSupplier, + Supplier>> supplier, Consumer handler, + ScheduledExecutorService reschedulingExecutor, BackoffPeriodSupplier lastBackoffPeriodSupplier, RetryContext ctx) { CompletableFuture handle = completable - .thenApply(CompletableFuture::completedFuture) - .exceptionally(throwable -> handleException( - supplier, handler, throwable, reschedulingExecutor, lastBackoffPeriodSupplier, ctx) - ) + .thenApply(CompletableFuture::completedFuture).exceptionally(throwable -> handleException(supplier, + handler, throwable, reschedulingExecutor, lastBackoffPeriodSupplier, ctx)) .thenCompose(Function.identity()); return new Result<>(handle); } - protected CompletableFuture scheduleNewAttemptAfterDelay( - Supplier>> supplier, - ScheduledExecutorService reschedulingExecutor, long rescheduleAfterMillis, - RetryContext ctx) - { + protected CompletableFuture scheduleNewAttemptAfterDelay(Supplier>> supplier, + ScheduledExecutorService reschedulingExecutor, long rescheduleAfterMillis, RetryContext ctx) { CompletableFuture> futureOfFurtherScheduling = new CompletableFuture<>(); reschedulingExecutor.schedule(() -> { try { RetrySynchronizationManager.register(ctx); futureOfFurtherScheduling.complete(doNewAttempt(supplier)); - } catch (Throwable t) { + } + catch (Throwable t) { futureOfFurtherScheduling.completeExceptionally(t); throw RetryTemplate.runtimeException(t); - } finally { + } + finally { RetrySynchronizationManager.clear(); } }, rescheduleAfterMillis, TimeUnit.MILLISECONDS); return futureOfFurtherScheduling.thenCompose(Function.identity()); } + } \ No newline at end of file diff --git a/src/main/java/org/springframework/retry/support/FutureRetryResultProcessor.java b/src/main/java/org/springframework/retry/support/FutureRetryResultProcessor.java index 3a9534ea..5c4f52be 100644 --- a/src/main/java/org/springframework/retry/support/FutureRetryResultProcessor.java +++ b/src/main/java/org/springframework/retry/support/FutureRetryResultProcessor.java @@ -23,12 +23,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; -import org.springframework.retry.backoff.LastBackoffPeriodSupplier; +import org.springframework.retry.backoff.BackoffPeriodSupplier; /** * todo: check or remove after discussion @@ -36,16 +35,16 @@ * A {@link RetryResultProcessor} for a plain {@link Future}. If a {@link RetryCallback} * returns a Future this processor can be used internally by the * {@link RetryTemplate} to wrap it and process the result. - * + * * @author Dave Syer + * @param The result type */ public class FutureRetryResultProcessor extends AsyncRetryResultProcessor> { @Override - public Result> process(Future future, - Supplier>> supplier, Consumer handler, - ScheduledExecutorService reschedulingExecutor, LastBackoffPeriodSupplier lastBackoffPeriodSupplier, - RetryContext ctx) { + public Result> process(Future future, Supplier>> supplier, + Consumer handler, ScheduledExecutorService reschedulingExecutor, + BackoffPeriodSupplier lastBackoffPeriodSupplier, RetryContext ctx) { return new Result<>(new FutureWrapper(future, supplier, handler, this, reschedulingExecutor, lastBackoffPeriodSupplier, ctx)); } @@ -53,12 +52,12 @@ public Result> process(Future future, @Override protected Future scheduleNewAttemptAfterDelay(Supplier>> supplier, ScheduledExecutorService reschedulingExecutor, long rescheduleAfterMillis, RetryContext ctx) - throws Throwable - { + throws Throwable { ScheduledFuture> scheduledFuture = reschedulingExecutor.schedule(() -> { try { return doNewAttempt(supplier); - } catch (Throwable t) { + } + catch (Throwable t) { throw RetryTemplate.runtimeException(t); } }, rescheduleAfterMillis, TimeUnit.MILLISECONDS); @@ -73,15 +72,18 @@ private class FutureWrapper implements Future { private Supplier>> supplier; private Consumer handler; + private AsyncRetryResultProcessor> processor; + private final ScheduledExecutorService reschedulingExecutor; - private final LastBackoffPeriodSupplier lastBackoffPeriodSupplier; + + private final BackoffPeriodSupplier lastBackoffPeriodSupplier; + private RetryContext ctx; - FutureWrapper(Future delegate, Supplier>> supplier, - Consumer handler, AsyncRetryResultProcessor> processor, - ScheduledExecutorService reschedulingExecutor, LastBackoffPeriodSupplier lastBackoffPeriodSupplier, - RetryContext ctx) { + FutureWrapper(Future delegate, Supplier>> supplier, Consumer handler, + AsyncRetryResultProcessor> processor, ScheduledExecutorService reschedulingExecutor, + BackoffPeriodSupplier lastBackoffPeriodSupplier, RetryContext ctx) { this.delegate = delegate; this.supplier = supplier; this.handler = handler; @@ -112,19 +114,20 @@ public V get() throws InterruptedException, ExecutionException { return this.delegate.get(); } catch (Throwable e) { - return processor.handleException(supplier, handler, e, reschedulingExecutor, lastBackoffPeriodSupplier, ctx) + return processor + .handleException(supplier, handler, e, reschedulingExecutor, lastBackoffPeriodSupplier, ctx) .get(); } } @Override - public V get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { try { return this.delegate.get(timeout, unit); } catch (Throwable e) { - return processor.handleException(supplier, handler, e, reschedulingExecutor, lastBackoffPeriodSupplier, ctx) + return processor + .handleException(supplier, handler, e, reschedulingExecutor, lastBackoffPeriodSupplier, ctx) .get(timeout, unit); } } @@ -142,22 +145,25 @@ private class FutureFlatter implements Future { @Override public boolean cancel(boolean mayInterruptIfRunning) { try { - if (this.nestedFuture.isDone()) { - return this.nestedFuture.get().cancel(mayInterruptIfRunning); - } else { - return this.nestedFuture.cancel(mayInterruptIfRunning); + if (this.nestedFuture.isDone()) { + return this.nestedFuture.get().cancel(mayInterruptIfRunning); + } + else { + return this.nestedFuture.cancel(mayInterruptIfRunning); + } } - } catch (Throwable t) { + catch (Throwable t) { throw RetryTemplate.runtimeException(t); } - } + } @Override public boolean isCancelled() { try { return this.nestedFuture.isCancelled() || (this.nestedFuture.isDone() && this.nestedFuture.get().isCancelled()); - } catch (Throwable t) { + } + catch (Throwable t) { throw RetryTemplate.runtimeException(t); } } @@ -166,7 +172,8 @@ public boolean isCancelled() { public boolean isDone() { try { return this.nestedFuture.isDone() && this.nestedFuture.get().isDone(); - } catch (Throwable t) { + } + catch (Throwable t) { throw RetryTemplate.runtimeException(t); } } @@ -177,8 +184,7 @@ public V get() throws InterruptedException, ExecutionException { } @Override - public V get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return this.nestedFuture.get(timeout, unit).get(timeout, unit); } diff --git a/src/main/java/org/springframework/retry/support/RetryResultProcessor.java b/src/main/java/org/springframework/retry/support/RetryResultProcessor.java index dce60354..eb4e44b0 100644 --- a/src/main/java/org/springframework/retry/support/RetryResultProcessor.java +++ b/src/main/java/org/springframework/retry/support/RetryResultProcessor.java @@ -21,7 +21,7 @@ import java.util.function.Supplier; import org.springframework.retry.RetryContext; -import org.springframework.retry.backoff.LastBackoffPeriodSupplier; +import org.springframework.retry.backoff.BackoffPeriodSupplier; /** * @author Dave Syer @@ -30,8 +30,7 @@ public interface RetryResultProcessor { Result process(T input, Supplier> supplier, Consumer handler, - ScheduledExecutorService reschedulingExecutor, - LastBackoffPeriodSupplier lastBackoffPeriodSupplier, + ScheduledExecutorService reschedulingExecutor, BackoffPeriodSupplier lastBackoffPeriodSupplier, RetryContext ctx); public static class Result { @@ -70,6 +69,7 @@ public T getOrThrow() throws Throwable { } throw exception; } + } } diff --git a/src/main/java/org/springframework/retry/support/RetryTemplate.java b/src/main/java/org/springframework/retry/support/RetryTemplate.java index 025040e1..e3f206fa 100644 --- a/src/main/java/org/springframework/retry/support/RetryTemplate.java +++ b/src/main/java/org/springframework/retry/support/RetryTemplate.java @@ -19,9 +19,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -40,7 +38,7 @@ import org.springframework.retry.backoff.BackOffContext; import org.springframework.retry.backoff.BackOffInterruptedException; import org.springframework.retry.backoff.BackOffPolicy; -import org.springframework.retry.backoff.LastBackoffPeriodSupplier; +import org.springframework.retry.backoff.BackoffPeriodSupplier; import org.springframework.retry.backoff.NoBackOffPolicy; import org.springframework.retry.backoff.RememberPeriodSleeper; import org.springframework.retry.backoff.SleepingBackOffPolicy; @@ -48,7 +46,6 @@ import org.springframework.retry.policy.RetryContextCache; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.support.RetryResultProcessor.Result; -import org.springframework.util.Assert; /** * Template class that simplifies the execution of operations with retry semantics. @@ -97,7 +94,7 @@ public class RetryTemplate implements RetryOperations { private volatile BackOffPolicy backOffPolicy = new NoBackOffPolicy(); - private volatile LastBackoffPeriodSupplier lastBackoffPeriodSupplier = null; + private volatile BackoffPeriodSupplier lastBackoffPeriodSupplier = null; private volatile RetryPolicy retryPolicy = new SimpleRetryPolicy(3); @@ -152,8 +149,7 @@ public void setRetryContextCache(RetryContextCache retryContextCache) { * empty). * @param processors the processors to set */ - public void setRetryResultProcessors( - Classifier> processors) { + public void setRetryResultProcessors(Classifier> processors) { this.processors = processors; } @@ -193,11 +189,13 @@ public void setBackOffPolicy(BackOffPolicy backOffPolicy) { private BackOffPolicy replaceSleeperIfNeed(BackOffPolicy backOffPolicy) { if (reschedulingExecutor != null && backOffPolicy instanceof SleepingBackOffPolicy) { - this.logger.debug("Replacing the default sleeper by RememberPeriodSleeper to enable scheduler-based backoff."); + this.logger + .debug("Replacing the default sleeper by RememberPeriodSleeper to enable scheduler-based backoff."); RememberPeriodSleeper rememberPeriodSleeper = new RememberPeriodSleeper(); lastBackoffPeriodSupplier = rememberPeriodSleeper; - return ((SleepingBackOffPolicy) backOffPolicy).withSleeper(rememberPeriodSleeper); - } else { + return ((SleepingBackOffPolicy) backOffPolicy).withSleeper(rememberPeriodSleeper); + } + else { return backOffPolicy; } } @@ -352,8 +350,8 @@ protected T doExecute(RetryCallback retryCallback } - private Result safeLoop(RetryCallback retryCallback, - RetryState state, RetryContext context, BackOffContext backOffContext) { + private Result safeLoop(RetryCallback retryCallback, RetryState state, + RetryContext context, BackOffContext backOffContext) { try { return loop(retryCallback, state, context, backOffContext); } @@ -362,9 +360,8 @@ private Result safeLoop(RetryCallback retryCal } } - private Result loop(RetryCallback retryCallback, - RetryState state, RetryContext context, BackOffContext backOffContext) - throws E { + private Result loop(RetryCallback retryCallback, RetryState state, + RetryContext context, BackOffContext backOffContext) throws E { Throwable lastException = null; @@ -384,16 +381,11 @@ private Result loop(RetryCallback retryCallbac T result = retryCallback.doWithRetry(context); if (result != null && this.processors != null) { @SuppressWarnings("unchecked") - RetryResultProcessor processor = (RetryResultProcessor) this.processors - .classify(result); + RetryResultProcessor processor = (RetryResultProcessor) this.processors.classify(result); if (processor != null) { - return processor.process(result, - () -> safeLoop(retryCallback, state, context, - backOffContext), - error -> safeHandleLoopException(retryCallback, state, - context, backOffContext, error), reschedulingExecutor, - lastBackoffPeriodSupplier, - context); + return processor.process(result, () -> safeLoop(retryCallback, state, context, backOffContext), + error -> safeHandleLoopException(retryCallback, state, context, backOffContext, error), + reschedulingExecutor, lastBackoffPeriodSupplier, context); } } return new Result<>(result); @@ -414,13 +406,11 @@ private Result loop(RetryCallback retryCallbac break; } } - return new Result<>( - lastException == null ? context.getLastThrowable() : lastException); + return new Result<>(lastException == null ? context.getLastThrowable() : lastException); } - private void safeHandleLoopException( - RetryCallback retryCallback, RetryState state, RetryContext context, - BackOffContext backOffContext, Throwable e) { + private void safeHandleLoopException(RetryCallback retryCallback, RetryState state, + RetryContext context, BackOffContext backOffContext, Throwable e) { try { handleLoopException(retryCallback, state, context, backOffContext, e); } @@ -429,9 +419,8 @@ private void safeHandleLoopException( } } - private void handleLoopException( - RetryCallback retryCallback, RetryState state, RetryContext context, - BackOffContext backOffContext, Throwable e) throws E { + private void handleLoopException(RetryCallback retryCallback, RetryState state, + RetryContext context, BackOffContext backOffContext, Throwable e) throws E { try { registerThrowable(this.retryPolicy, state, context, e); } @@ -449,8 +438,7 @@ private void handleLoopException( catch (BackOffInterruptedException ex) { // back off was prevented by another thread - fail the retry if (this.logger.isDebugEnabled()) { - this.logger.debug("Abort retry because interrupted: count=" - + context.getRetryCount()); + this.logger.debug("Abort retry because interrupted: count=" + context.getRetryCount()); } throw ex; } @@ -462,8 +450,7 @@ private void handleLoopException( if (shouldRethrow(this.retryPolicy, context, state)) { if (this.logger.isDebugEnabled()) { - this.logger.debug( - "Rethrow in retry for policy: count=" + context.getRetryCount()); + this.logger.debug("Rethrow in retry for policy: count=" + context.getRetryCount()); } throw RetryTemplate.wrapIfNecessary(e); } diff --git a/src/main/java/org/springframework/retry/support/RetryTemplateBuilder.java b/src/main/java/org/springframework/retry/support/RetryTemplateBuilder.java index cfcc5902..10e40fbc 100644 --- a/src/main/java/org/springframework/retry/support/RetryTemplateBuilder.java +++ b/src/main/java/org/springframework/retry/support/RetryTemplateBuilder.java @@ -376,17 +376,17 @@ public RetryTemplateBuilder asyncRetry(ScheduledExecutorService reschedulingExec } /** - * Enable async retry feature. - * Due to no rescheduling executor is provided, a potential backoff will be performed - * by Thread.sleep(). + * Enable async retry feature. Due to no rescheduling executor is provided, a + * potential backoff will be performed by Thread.sleep(). + * @return A new RetryTemplateBuilder for an async retry */ public RetryTemplateBuilder asyncRetry() { // todo: support interface classification (does not work yet) this.processors.put(Future.class, new FutureRetryResultProcessor<>()); this.processors.put(FutureTask.class, new FutureRetryResultProcessor<>()); - this.processors.put(CompletableFuture.class, new CompletableFutureRetryResultProcessor()); + this.processors.put(CompletableFuture.class, new CompletableFutureRetryResultProcessor()); - //todo + // todo return this; } @@ -438,13 +438,10 @@ public RetryTemplate build() { retryTemplate.setReschedulingExecutor(executorService); Assert.isTrue(backOffPolicy instanceof SleepingBackOffPolicy, - "Usage of a rescheduling executor makes sense " - + "only with an instance of SleepingBackOffPolicy" - ); + "Usage of a rescheduling executor makes sense " + "only with an instance of SleepingBackOffPolicy"); } - SubclassClassifier> classifier = - new SubclassClassifier<>(processors, null); + SubclassClassifier> classifier = new SubclassClassifier<>(processors, null); retryTemplate.setRetryResultProcessors(classifier); return retryTemplate; diff --git a/src/test/java/org/springframework/retry/listener/MethodInvocationRetryListenerSupportTests.java b/src/test/java/org/springframework/retry/listener/MethodInvocationRetryListenerSupportTests.java index 8f36e62d..13bf65bf 100644 --- a/src/test/java/org/springframework/retry/listener/MethodInvocationRetryListenerSupportTests.java +++ b/src/test/java/org/springframework/retry/listener/MethodInvocationRetryListenerSupportTests.java @@ -51,7 +51,7 @@ protected void doClose(RetryContext context, } }; RetryContext context = mock(RetryContext.class); - MethodInvocationRetryCallback callback = mock(MethodInvocationRetryCallback.class); + MethodInvocationRetryCallback callback = mock(MethodInvocationRetryCallback.class); support.close(context, callback, null); assertEquals(1, callsOnDoCloseMethod.get()); @@ -68,7 +68,7 @@ protected void doClose(RetryContext context, } }; RetryContext context = mock(RetryContext.class); - RetryCallback callback = mock(RetryCallback.class); + RetryCallback callback = mock(RetryCallback.class); support.close(context, callback, null); assertEquals(0, callsOnDoCloseMethod.get()); @@ -96,7 +96,7 @@ protected void doOnError(RetryContext context, } }; RetryContext context = mock(RetryContext.class); - MethodInvocationRetryCallback callback = mock(MethodInvocationRetryCallback.class); + MethodInvocationRetryCallback callback = mock(MethodInvocationRetryCallback.class); support.onError(context, callback, null); assertEquals(1, callsOnDoOnErrorMethod.get()); @@ -120,7 +120,7 @@ protected boolean doOpen(RetryContext context, } }; RetryContext context = mock(RetryContext.class); - MethodInvocationRetryCallback callback = mock(MethodInvocationRetryCallback.class); + MethodInvocationRetryCallback callback = mock(MethodInvocationRetryCallback.class); assertTrue(support.open(context, callback)); assertEquals(1, callsOnDoOpenMethod.get()); diff --git a/src/test/java/org/springframework/retry/support/AbstractAsyncRetryTest.java b/src/test/java/org/springframework/retry/support/AbstractAsyncRetryTest.java index ae5bb34e..5f3c5a51 100644 --- a/src/test/java/org/springframework/retry/support/AbstractAsyncRetryTest.java +++ b/src/test/java/org/springframework/retry/support/AbstractAsyncRetryTest.java @@ -46,11 +46,12 @@ * @author Dave Syer */ public class AbstractAsyncRetryTest { - - /* ---------------- Async callbacks implementations for different types -------------- */ - static class CompletableFutureRetryCallback - extends AbstractRetryCallback> { + /* + * ---------------- Async callbacks implementations for different types -------------- + */ + + static class CompletableFutureRetryCallback extends AbstractRetryCallback> { @Override public CompletableFuture schedule(Supplier callback, ExecutorService workerExecutor) { @@ -61,11 +62,11 @@ public CompletableFuture schedule(Supplier callback, ExecutorSer Object awaitItself(CompletableFuture asyncType) { return asyncType.join(); } + } - static class FutureRetryCallback - extends AbstractRetryCallback> { - + static class FutureRetryCallback extends AbstractRetryCallback> { + @Override public Future schedule(Supplier callback, ExecutorService executor) { return executor.submit(callback::get); @@ -75,39 +76,43 @@ public Future schedule(Supplier callback, ExecutorService execut Object awaitItself(Future asyncType) throws Throwable { return asyncType.get(); } + } - static abstract class AbstractRetryCallback - implements RetryCallback { + static abstract class AbstractRetryCallback implements RetryCallback { final Object defaultResult = new Object(); + final Log logger = LogFactory.getLog(getClass()); final AtomicInteger jobAttempts = new AtomicInteger(); + final AtomicInteger schedulingAttempts = new AtomicInteger(); volatile int attemptsBeforeSchedulingSuccess; + volatile int attemptsBeforeJobSuccess; volatile RuntimeException exceptionToThrow = new RuntimeException(); volatile Function resultSupplier = ctx -> defaultResult; - volatile Consumer customCodeBeforeScheduling = ctx -> {}; + + volatile Consumer customCodeBeforeScheduling = ctx -> { + }; final List schedulerThreadNames = new CopyOnWriteArrayList<>(); + final List invocationMoments = new CopyOnWriteArrayList<>(); - final ExecutorService workerExecutor = Executors.newSingleThreadExecutor( - getNamedThreadFactory(WORKER_THREAD_NAME) - ); + final ExecutorService workerExecutor = Executors + .newSingleThreadExecutor(getNamedThreadFactory(WORKER_THREAD_NAME)); public abstract A schedule(Supplier callback, ExecutorService executor); abstract Object awaitItself(A asyncType) throws Throwable; @Override - public A doWithRetry(RetryContext ctx) - throws Exception { + public A doWithRetry(RetryContext ctx) throws Exception { rememberThreadName(); rememberInvocationMoment(); @@ -117,9 +122,11 @@ public A doWithRetry(RetryContext ctx) return schedule(() -> { try { - // a hack to avoid running CompletableFuture#thenApplyAsync in the caller thread + // a hack to avoid running CompletableFuture#thenApplyAsync in the + // caller thread Thread.sleep(100L); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { e.printStackTrace(); } throwIfJobTooEarly(); @@ -127,7 +134,7 @@ public A doWithRetry(RetryContext ctx) return resultSupplier.apply(ctx); }, workerExecutor); } - + void rememberInvocationMoment() { invocationMoments.add(System.currentTimeMillis()); } @@ -169,8 +176,8 @@ void setResultSupplier(Function resultSupplier) { void setCustomCodeBeforeScheduling(Consumer customCodeBeforeScheduling) { this.customCodeBeforeScheduling = customCodeBeforeScheduling; } - } + } static class MockBackOffStrategy implements BackOffPolicy { @@ -188,8 +195,7 @@ public BackOffContext start(RetryContext status) { } @Override - public void backOff(BackOffContext backOffContext) - throws BackOffInterruptedException { + public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException { this.backOffCalls++; } @@ -198,30 +204,29 @@ public void backOff(BackOffContext backOffContext) /* ---------------- Utilities -------------- */ static final String SCHEDULER_THREAD_NAME = "scheduler"; - static final String WORKER_THREAD_NAME = "worker"; + static final String WORKER_THREAD_NAME = "worker"; static ScheduledExecutorService getNamedScheduledExecutor() { - return Executors.newScheduledThreadPool( - 1, - getNamedThreadFactory(AbstractAsyncRetryTest.SCHEDULER_THREAD_NAME) - ); + return Executors.newScheduledThreadPool(1, getNamedThreadFactory(AbstractAsyncRetryTest.SCHEDULER_THREAD_NAME)); + } + + static ThreadFactory getNamedThreadFactory(String threadName) { + return new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setName(threadName); + return thread; + } + }; + } + + void assertRememberingSleeper(RetryTemplate template) { + // The sleeper of the backoff policy should be an instance of + // RememberPeriodSleeper, means not Thread.sleep() + BackOffPolicy backOffPolicy = getPropertyValue(template, "backOffPolicy", BackOffPolicy.class); + Sleeper sleeper = getPropertyValue(backOffPolicy, "sleeper", Sleeper.class); + assertTrue(sleeper instanceof RememberPeriodSleeper); } - static ThreadFactory getNamedThreadFactory(String threadName) { - return new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread thread = new Thread(r); - thread.setName(threadName); - return thread; - } - }; - } - - void assertRememberingSleeper(RetryTemplate template) { - // The sleeper of the backoff policy should be an instance of RememberPeriodSleeper, means not Thread.sleep() - BackOffPolicy backOffPolicy = getPropertyValue(template, "backOffPolicy", BackOffPolicy.class); - Sleeper sleeper = getPropertyValue(backOffPolicy, "sleeper", Sleeper.class); - assertTrue(sleeper instanceof RememberPeriodSleeper); - } } diff --git a/src/test/java/org/springframework/retry/support/AsyncReschedulingTests.java b/src/test/java/org/springframework/retry/support/AsyncReschedulingTests.java index a5c01195..ff77f171 100644 --- a/src/test/java/org/springframework/retry/support/AsyncReschedulingTests.java +++ b/src/test/java/org/springframework/retry/support/AsyncReschedulingTests.java @@ -36,14 +36,12 @@ public class AsyncReschedulingTests extends AbstractAsyncRetryTest { - /** + /* * Scheduling retry + job immediate success. * - * - async callback succeeds at 3rd attempt - * - actual job succeeds on 1st attempt - * - no backoff + * - async callback succeeds at 3rd attempt - actual job succeeds on 1st attempt - no + * backoff */ - @Test public void testInitialSchedulingEventualSuccessCF() throws Throwable { doTestInitialSchedulingEventualSuccess(new CompletableFutureRetryCallback()); @@ -55,30 +53,25 @@ public void testInitialSchedulingEventualSuccessF() throws Throwable { } private void doTestInitialSchedulingEventualSuccess(AbstractRetryCallback callback) throws Throwable { - RetryTemplate template = RetryTemplate.builder() - .maxAttempts(5) - .noBackoff() - .asyncRetry() - .build(); + RetryTemplate template = RetryTemplate.builder().maxAttempts(5).noBackoff().asyncRetry().build(); callback.setAttemptsBeforeSchedulingSuccess(3); callback.setAttemptsBeforeJobSuccess(1); assertEquals(callback.defaultResult, callback.awaitItself(template.execute(callback))); - // All invocations before first successful scheduling should be performed by the caller thread + // All invocations before first successful scheduling should be performed by the + // caller thread assertEquals(Collections.nCopies(3, Thread.currentThread().getName()), callback.schedulerThreadNames); assertEquals(1, callback.jobAttempts.get()); } - /** + /* * Immediate success of both scheduling and job. * - * - async callback, that does not fail itself - * - actual job succeeds on 1st attempt - * - backoff is not necessary - */ - + * - async callback, that does not fail itself - actual job succeeds on 1st attempt - + * backoff is not necessary + */ @Test public void testImmediateSuccessCF() throws Throwable { doTestImmediateSuccess(new CompletableFutureRetryCallback()); @@ -92,10 +85,7 @@ public void testImmediateSuccessF() throws Throwable { private void doTestImmediateSuccess(AbstractRetryCallback callback) throws Throwable { ScheduledExecutorService executor = mock(ScheduledExecutorService.class); - RetryTemplate template = RetryTemplate.builder() - .fixedBackoff(10000) - .asyncRetry(executor) - .build(); + RetryTemplate template = RetryTemplate.builder().fixedBackoff(10000).asyncRetry(executor).build(); callback.setAttemptsBeforeSchedulingSuccess(1); callback.setAttemptsBeforeJobSuccess(1); @@ -106,18 +96,17 @@ private void doTestImmediateSuccess(AbstractRetryCallback callback) throw assertEquals(1, callback.jobAttempts.get()); - // No interaction with the rescheduling executor should be performed if the first execution of the job succeeds. + // No interaction with the rescheduling executor should be performed if the first + // execution of the job succeeds. verifyZeroInteractions(executor); } - /** + /* * Async retry with rescheduler. - * - * - async callback, that does not fail itself - * - actual job succeeds on 3rd attempt - * - backoff is performed using executor, without Thread.sleep() - */ - + * + * - async callback, that does not fail itself - actual job succeeds on 3rd attempt - + * backoff is performed using executor, without Thread.sleep() + */ @Test public void testAsyncRetryWithReschedulerCF() throws Throwable { doTestAsyncRetryWithRescheduler(new CompletableFutureRetryCallback()); @@ -127,87 +116,74 @@ public void testAsyncRetryWithReschedulerCF() throws Throwable { public void testAsyncRetryWithReschedulerF() throws Throwable { doTestAsyncRetryWithRescheduler(new FutureRetryCallback()); } - + private void doTestAsyncRetryWithRescheduler(AbstractRetryCallback callback) throws Throwable { - int targetFixedBackoff = 150; + int targetFixedBackoff = 150; ScheduledExecutorService executor = getNamedScheduledExecutor(); - RetryTemplate template = RetryTemplate.builder() - .maxAttempts(4) - .fixedBackoff(targetFixedBackoff) - .asyncRetry(executor) - .build(); - - callback.setAttemptsBeforeSchedulingSuccess(1); + RetryTemplate template = RetryTemplate.builder().maxAttempts(4).fixedBackoff(targetFixedBackoff) + .asyncRetry(executor).build(); + + callback.setAttemptsBeforeSchedulingSuccess(1); callback.setAttemptsBeforeJobSuccess(3); assertEquals(callback.defaultResult, callback.awaitItself(template.execute(callback))); assertEquals(3, callback.jobAttempts.get()); - // All invocations after the first successful scheduling should be performed by the the rescheduler thread - assertEquals(Arrays.asList( - Thread.currentThread().getName(), - SCHEDULER_THREAD_NAME, - SCHEDULER_THREAD_NAME - ), callback.schedulerThreadNames); + // All invocations after the first successful scheduling should be performed by + // the the rescheduler thread + assertEquals(Arrays.asList(Thread.currentThread().getName(), SCHEDULER_THREAD_NAME, SCHEDULER_THREAD_NAME), + callback.schedulerThreadNames); assertRememberingSleeper(template); - // Expected backoff should be performed - List moments = callback.invocationMoments; - for (int i = 0; i < moments.size() - 1; i++) { - long approxBackoff = moments.get(i + 1) - moments.get(i); - assertTrue(approxBackoff > targetFixedBackoff); - } - } + // Expected backoff should be performed + List moments = callback.invocationMoments; + for (int i = 0; i < moments.size() - 1; i++) { + long approxBackoff = moments.get(i + 1) - moments.get(i); + assertTrue(approxBackoff > targetFixedBackoff); + } + } - /** + /* * Async retry without backoff - * - * - async callback succeeds on 2nd attempt - * - actual job succeeds on 3nd attempt - * - default zero backoff is used (which has no sleeper at all), - * and therefore rescheduler executor is not used at all - */ - + * + * - async callback succeeds on 2nd attempt - actual job succeeds on 3nd attempt - + * default zero backoff is used (which has no sleeper at all), and therefore + * rescheduler executor is not used at all + */ @Test public void testAsyncRetryWithoutBackoffCF() throws Throwable { doTestAsyncRetryWithoutBackoff(new CompletableFutureRetryCallback()); } - // todo: problem: a Future can start retrying only when user calls get(). Consider to not support Future at all. - /*@Test - public void testAsyncRetryWithoutBackoffF() throws Throwable { - doTestAsyncRetryWithoutBackoff(new FutureRetryCallback()); - }*/ - - private void doTestAsyncRetryWithoutBackoff(AbstractRetryCallback callback) throws Throwable { - RetryTemplate template = RetryTemplate.builder() - .maxAttempts(4) - .asyncRetry() - .build(); - - callback.setAttemptsBeforeSchedulingSuccess(2); - callback.setAttemptsBeforeJobSuccess(3); - assertEquals(callback.defaultResult, callback.awaitItself(template.execute(callback))); - assertEquals(4, callback.schedulingAttempts.get()); - assertEquals(3, callback.jobAttempts.get()); - - // All invocations after the first successful scheduling should be performed by the + // todo: problem: a Future can start retrying only when user calls get(). Consider to + // not support Future at all. + /* + * @Test public void testAsyncRetryWithoutBackoffF() throws Throwable { + * doTestAsyncRetryWithoutBackoff(new FutureRetryCallback()); } + */ + + private void doTestAsyncRetryWithoutBackoff(AbstractRetryCallback callback) throws Throwable { + RetryTemplate template = RetryTemplate.builder().maxAttempts(4).asyncRetry().build(); + + callback.setAttemptsBeforeSchedulingSuccess(2); + callback.setAttemptsBeforeJobSuccess(3); + assertEquals(callback.defaultResult, callback.awaitItself(template.execute(callback))); + assertEquals(4, callback.schedulingAttempts.get()); + assertEquals(3, callback.jobAttempts.get()); + + // All invocations after the first successful scheduling should be performed by + // the // the worker thread (because not backoff and no rescheduler thread) - assertEquals(Arrays.asList( - Thread.currentThread().getName(), - Thread.currentThread().getName(), - WORKER_THREAD_NAME, - WORKER_THREAD_NAME - ), callback.schedulerThreadNames); - } - - /** + assertEquals(Arrays.asList(Thread.currentThread().getName(), Thread.currentThread().getName(), + WORKER_THREAD_NAME, WORKER_THREAD_NAME), callback.schedulerThreadNames); + } + + /* * Exhausted on scheduling retries */ - @Test public void testExhaustOnSchedulingCF() throws Throwable { doTestExhaustOnScheduling(new CompletableFutureRetryCallback()); @@ -217,13 +193,9 @@ public void testExhaustOnSchedulingCF() throws Throwable { public void testExhaustOnSchedulingF() throws Throwable { doTestExhaustOnScheduling(new FutureRetryCallback()); } - + private void doTestExhaustOnScheduling(AbstractRetryCallback callback) throws Throwable { - RetryTemplate template = RetryTemplate.builder() - .maxAttempts(2) - .asyncRetry() - .fixedBackoff(100) - .build(); + RetryTemplate template = RetryTemplate.builder().maxAttempts(2).asyncRetry().fixedBackoff(100).build(); callback.setAttemptsBeforeSchedulingSuccess(5); callback.setAttemptsBeforeJobSuccess(5); @@ -231,20 +203,18 @@ private void doTestExhaustOnScheduling(AbstractRetryCallback callback) th try { callback.awaitItself(template.execute(callback)); fail("An exception should be thrown above"); - } catch (Exception e) { + } + catch (Exception e) { assertSame(e, callback.exceptionToThrow); } - assertEquals(Arrays.asList( - Thread.currentThread().getName(), - Thread.currentThread().getName() - ), callback.schedulerThreadNames); + assertEquals(Arrays.asList(Thread.currentThread().getName(), Thread.currentThread().getName()), + callback.schedulerThreadNames); } - /** + /* * Exhausted on job retries */ - @Test public void testExhaustOnJobWithReschedulerCF() throws Throwable { doTestExhaustOnJobWithRescheduler(new CompletableFutureRetryCallback()); @@ -256,37 +226,31 @@ public void testExhaustOnJobWithReschedulerF() throws Throwable { } private void doTestExhaustOnJobWithRescheduler(AbstractRetryCallback callback) throws Throwable { - RetryTemplate template = RetryTemplate.builder() - .maxAttempts(5) - .asyncRetry(getNamedScheduledExecutor()) - .exponentialBackoff(10, 2, 100) - .build(); + RetryTemplate template = RetryTemplate.builder().maxAttempts(5).asyncRetry(getNamedScheduledExecutor()) + .exponentialBackoff(10, 2, 100).build(); callback.setAttemptsBeforeSchedulingSuccess(1); callback.setAttemptsBeforeJobSuccess(6); try { + @SuppressWarnings("unused") Object v = callback.awaitItself(template.execute(callback)); fail("An exception should be thrown above"); - // Single wrapping by CompletionException is expected by CompletableFuture contract - } catch (Exception ce) { + // Single wrapping by CompletionException is expected by CompletableFuture + // contract + } + catch (Exception ce) { assertSame(ce.getCause(), callback.exceptionToThrow); } - assertEquals(Arrays.asList( - Thread.currentThread().getName(), - SCHEDULER_THREAD_NAME, - SCHEDULER_THREAD_NAME, - SCHEDULER_THREAD_NAME, - SCHEDULER_THREAD_NAME - ), callback.schedulerThreadNames); + assertEquals(Arrays.asList(Thread.currentThread().getName(), SCHEDULER_THREAD_NAME, SCHEDULER_THREAD_NAME, + SCHEDULER_THREAD_NAME, SCHEDULER_THREAD_NAME), callback.schedulerThreadNames); } // todo: rejected execution // todo: interrupt executor // rethrow not too late - /* * Nested rescheduling */ @@ -295,33 +259,31 @@ private void doTestExhaustOnJobWithRescheduler(AbstractRetryCallback call public void testNested() throws Throwable { ScheduledExecutorService executor = getNamedScheduledExecutor(); - RetryTemplate outerTemplate = RetryTemplate.builder() - .infiniteRetry() - .asyncRetry(executor) - .fixedBackoff(10) + RetryTemplate outerTemplate = RetryTemplate.builder().infiniteRetry().asyncRetry(executor).fixedBackoff(10) .build(); - RetryTemplate innerTemplate = RetryTemplate.builder() - .infiniteRetry() - .asyncRetry(executor) - .fixedBackoff(10) + RetryTemplate innerTemplate = RetryTemplate.builder().infiniteRetry().asyncRetry(executor).fixedBackoff(10) .build(); CompletableFutureRetryCallback innerCallback = new CompletableFutureRetryCallback(); innerCallback.setAttemptsBeforeSchedulingSuccess(3); innerCallback.setAttemptsBeforeJobSuccess(3); innerCallback.setCustomCodeBeforeScheduling(ctx -> { - // The current context should be available via RetrySynchronizationManager while scheduling + // The current context should be available via RetrySynchronizationManager + // while scheduling // (withing user's async callback itself) assertEquals(ctx, RetrySynchronizationManager.getContext()); - // We have no control over user's worker thread, so we can not implicitly set/get the parent + // We have no control over user's worker thread, so we can not implicitly + // set/get the parent // context via RetrySynchronizationManager. assertNull(ctx.getParent()); }); innerCallback.setResultSupplier(ctx -> { - // There is no way to implicitly pass the context into the worker thread, because the worker executor, - // thread and callback are fully controlled by the user. The retry engine deals with only + // There is no way to implicitly pass the context into the worker thread, + // because the worker executor, + // thread and callback are fully controlled by the user. The retry engine + // deals with only // scheduling/rescheduling and their result (e.g. CompletableFuture) assertNull(RetrySynchronizationManager.getContext()); @@ -332,7 +294,8 @@ public void testNested() throws Throwable { outerCallback.setAttemptsBeforeSchedulingSuccess(3); outerCallback.setAttemptsBeforeJobSuccess(3); outerCallback.setCustomCodeBeforeScheduling(ctx -> { - // The current context should be available via RetrySynchronizationManager while scheduling + // The current context should be available via RetrySynchronizationManager + // while scheduling // (withing user's async callback itself) assertEquals(ctx, RetrySynchronizationManager.getContext()); }); @@ -347,41 +310,31 @@ public void testNested() throws Throwable { // Return inner result as outer result return innerResult; - } catch (Exception e) { + } + catch (Exception e) { throw new RuntimeException(e); } }); - Object outerResult = outerCallback.awaitItself(outerTemplate.execute(outerCallback)); assertEquals(innerCallback.defaultResult, outerResult); assertEquals(Arrays.asList( // initial scheduling of the outer callback - Thread.currentThread().getName(), - Thread.currentThread().getName(), - Thread.currentThread().getName(), + Thread.currentThread().getName(), Thread.currentThread().getName(), Thread.currentThread().getName(), // rescheduling of the outer callback - SCHEDULER_THREAD_NAME, - SCHEDULER_THREAD_NAME - ), outerCallback.schedulerThreadNames); + SCHEDULER_THREAD_NAME, SCHEDULER_THREAD_NAME), outerCallback.schedulerThreadNames); assertEquals(Arrays.asList( // initial scheduling of the inner callback - WORKER_THREAD_NAME, - WORKER_THREAD_NAME, - WORKER_THREAD_NAME, + WORKER_THREAD_NAME, WORKER_THREAD_NAME, WORKER_THREAD_NAME, // rescheduling of the inner callback - SCHEDULER_THREAD_NAME, - SCHEDULER_THREAD_NAME - ), innerCallback.schedulerThreadNames); + SCHEDULER_THREAD_NAME, SCHEDULER_THREAD_NAME), innerCallback.schedulerThreadNames); } - - /** + /* * Test with additional chained completable futures. */ - @Test public void testAdditionalChainedCF() throws Throwable { @@ -397,10 +350,7 @@ public CompletableFuture schedule(Supplier callback, ExecutorSer }); } }; - RetryTemplate template = RetryTemplate.builder() - .maxAttempts(4) - .asyncRetry() - .build(); + RetryTemplate template = RetryTemplate.builder().maxAttempts(4).asyncRetry().build(); callback.setAttemptsBeforeSchedulingSuccess(2); callback.setAttemptsBeforeJobSuccess(3); @@ -417,19 +367,16 @@ public CompletableFuture schedule(Supplier callback, ExecutorSer assertEquals(4, callback.schedulingAttempts.get()); assertEquals(3, callback.jobAttempts.get()); - // All invocations after the first successful scheduling should be performed by the + // All invocations after the first successful scheduling should be performed by + // the // the worker thread (because not backoff and no rescheduler thread) - assertEquals(Arrays.asList( - Thread.currentThread().getName(), - Thread.currentThread().getName(), - WORKER_THREAD_NAME, - WORKER_THREAD_NAME - ), callback.schedulerThreadNames); + assertEquals(Arrays.asList(Thread.currentThread().getName(), Thread.currentThread().getName(), + WORKER_THREAD_NAME, WORKER_THREAD_NAME), callback.schedulerThreadNames); } - // todo: test stateful rescheduling // todo: test RejectedExecutionException on rescheduler // todo: test InterruptedException // todo: support declarative async + } diff --git a/src/test/java/org/springframework/retry/support/AsyncRetryTemplateTests.java b/src/test/java/org/springframework/retry/support/AsyncRetryTemplateTests.java index eab47805..4a8fc057 100644 --- a/src/test/java/org/springframework/retry/support/AsyncRetryTemplateTests.java +++ b/src/test/java/org/springframework/retry/support/AsyncRetryTemplateTests.java @@ -23,8 +23,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; @@ -36,12 +34,8 @@ import org.springframework.retry.policy.SimpleRetryPolicy; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.retry.util.test.TestUtils.getPropertyValue; /** * @author Dave Syer @@ -49,23 +43,22 @@ public class AsyncRetryTemplateTests extends AbstractAsyncRetryTest { private RetryTemplate retryTemplate; - + @Before @SuppressWarnings({ "unchecked", "rawtypes" }) public void init() { -// org.apache.log4j.BasicConfigurator.configure(); - + // org.apache.log4j.BasicConfigurator.configure(); + Logger root = Logger.getRootLogger(); root.removeAllAppenders(); root.addAppender(new ConsoleAppender(new PatternLayout("%r [%t] %p %c{1} %x - %m%n"))); Logger.getRootLogger().setLevel(Level.TRACE); - + this.retryTemplate = new RetryTemplate(); Map, RetryResultProcessor> map = new HashMap<>(); map.put(Future.class, new FutureRetryResultProcessor()); map.put(CompletableFuture.class, new CompletableFutureRetryResultProcessor()); - SubclassClassifier processors = new SubclassClassifier(map, - (RetryResultProcessor) null); + SubclassClassifier processors = new SubclassClassifier(map, (RetryResultProcessor) null); this.retryTemplate.setRetryResultProcessors(processors); } @@ -78,27 +71,22 @@ public void testSuccessfulRetryCompletable() throws Throwable { SimpleRetryPolicy policy = new SimpleRetryPolicy(x); this.retryTemplate.setRetryPolicy(policy); CompletableFuture result = this.retryTemplate.execute(callback); - assertEquals(callback.defaultResult, - result.get(10000L, TimeUnit.MILLISECONDS)); + assertEquals(callback.defaultResult, result.get(10000L, TimeUnit.MILLISECONDS)); assertEquals(x, callback.jobAttempts.get()); } } // todo: remove of fix after discussion - /*@Test - public void testSuccessfulRetryFuture() throws Throwable { - for (int x = 1; x <= 10; x++) { - FutureRetryCallback callback = new FutureRetryCallback(); - callback.setAttemptsBeforeSchedulingSuccess(1); - callback.setAttemptsBeforeJobSuccess(x); - SimpleRetryPolicy policy = new SimpleRetryPolicy(x + 1); - this.retryTemplate.setRetryPolicy(policy); - Future result = this.retryTemplate.execute(callback); - assertEquals(callback.defaultResult, - result.get(10000L, TimeUnit.MILLISECONDS)); - assertEquals(x, callback.jobAttempts.get()); - } - }*/ + /* + * @Test public void testSuccessfulRetryFuture() throws Throwable { for (int x = 1; x + * <= 10; x++) { FutureRetryCallback callback = new FutureRetryCallback(); + * callback.setAttemptsBeforeSchedulingSuccess(1); + * callback.setAttemptsBeforeJobSuccess(x); SimpleRetryPolicy policy = new + * SimpleRetryPolicy(x + 1); this.retryTemplate.setRetryPolicy(policy); Future + * result = this.retryTemplate.execute(callback); assertEquals(callback.defaultResult, + * result.get(10000L, TimeUnit.MILLISECONDS)); assertEquals(x, + * callback.jobAttempts.get()); } } + */ @Test public void testBackOffInvoked() throws Throwable { @@ -111,8 +99,7 @@ public void testBackOffInvoked() throws Throwable { this.retryTemplate.setRetryPolicy(policy); this.retryTemplate.setBackOffPolicy(backOff); CompletableFuture result = this.retryTemplate.execute(callback); - assertEquals(callback.defaultResult, - result.get(10000L, TimeUnit.MILLISECONDS)); + assertEquals(callback.defaultResult, result.get(10000L, TimeUnit.MILLISECONDS)); assertEquals(x, callback.jobAttempts.get()); assertEquals(1, backOff.startCalls); assertEquals(x - 1, backOff.backOffCalls); @@ -133,11 +120,11 @@ public void testNoSuccessRetry() throws Throwable { fail("Expected IllegalArgumentException"); } catch (ExecutionException e) { - assertTrue("Expected IllegalArgumentException", - e.getCause() instanceof IllegalArgumentException); + assertTrue("Expected IllegalArgumentException", e.getCause() instanceof IllegalArgumentException); assertEquals(retryAttempts, callback.jobAttempts.get()); return; } fail("Expected IllegalArgumentException"); } + }