Skip to content

Commit 4da5781

Browse files
committed
DEV: make the retry context aware of the policies that have a maximum number of attempts set
1 parent c89b951 commit 4da5781

File tree

9 files changed

+91
-1
lines changed

9 files changed

+91
-1
lines changed

src/main/java/org/springframework/retry/RetryContext.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ public interface RetryContext extends AttributeAccessor {
6161
*/
6262
String NO_RECOVERY = "context.no-recovery";
6363

64+
/**
65+
* Retry context attribute that represent the maximum number of attempts for policies
66+
* that provide a maximum number of attempts before failure. For other policies the
67+
* value returned is {@link RetryPolicy#NO_MAXIMUM_ATTEMPTS_SET}
68+
*/
69+
String MAX_ATTEMPTS = "context.max-attempts";
70+
6471
/**
6572
* Signal to the framework that no more attempts should be made to try or retry the
6673
* current {@link RetryCallback}.

src/main/java/org/springframework/retry/RetryPolicy.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@
3030
*/
3131
public interface RetryPolicy extends Serializable {
3232

33+
/**
34+
* The value returned by {@link RetryPolicy#getMaxAttempts()} when the policy doesn't
35+
* provide a maximum number of attempts before failure
36+
*/
37+
int NO_MAXIMUM_ATTEMPTS_SET = -1;
38+
3339
/**
3440
* @param context the current retry status
3541
* @return true if the operation can proceed
@@ -59,4 +65,14 @@ public interface RetryPolicy extends Serializable {
5965
*/
6066
void registerThrowable(RetryContext context, Throwable throwable);
6167

68+
/**
69+
* Called to understand if the policy has a fixed number of maximum attempts before
70+
* failure
71+
* @return -1 if the policy doesn't provide a fixed number of maximum attempts before
72+
* failure, the number of maximum attempts before failure otherwise
73+
*/
74+
default int getMaxAttempts() {
75+
return NO_MAXIMUM_ATTEMPTS_SET;
76+
}
77+
6278
}

src/main/java/org/springframework/retry/policy/CompositeRetryPolicy.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,22 @@ public void registerThrowable(RetryContext context, Throwable throwable) {
145145
((RetryContextSupport) context).registerThrowable(throwable);
146146
}
147147

148+
/**
149+
* @return the lower 'maximum number of attempts before failure' between all policies
150+
* that have a 'maximum number of attempts before failure' set, if at least one is
151+
* present among the policies, return {@link RetryPolicy#NO_MAXIMUM_ATTEMPTS_SET}
152+
* otherwise
153+
*/
154+
@Override
155+
public int getMaxAttempts() {
156+
return Arrays.stream(policies)
157+
.map(RetryPolicy::getMaxAttempts)
158+
.filter(maxAttempts -> maxAttempts != NO_MAXIMUM_ATTEMPTS_SET)
159+
.sorted()
160+
.findFirst()
161+
.orElse(NO_MAXIMUM_ATTEMPTS_SET);
162+
}
163+
148164
private static class CompositeRetryContext extends RetryContextSupport {
149165

150166
RetryContext[] contexts;

src/main/java/org/springframework/retry/policy/MaxAttemptsRetryPolicy.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public void setMaxAttempts(int maxAttempts) {
7474
* The maximum number of attempts before failure.
7575
* @return the maximum number of attempts
7676
*/
77+
@Override
7778
public int getMaxAttempts() {
7879
return this.maxAttempts;
7980
}

src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ public void maxAttemptsSupplier(Supplier<Integer> maxAttemptsSupplier) {
194194
* The maximum number of attempts before failure.
195195
* @return the maximum number of attempts
196196
*/
197+
@Override
197198
public int getMaxAttempts() {
198199
if (this.maxAttemptsSupplier != null) {
199200
return this.maxAttemptsSupplier.get();

src/main/java/org/springframework/retry/support/RetryTemplate.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,10 @@ protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback
296296
throw new TerminatedRetryException("Retry terminated abnormally by interceptor before first attempt");
297297
}
298298

299+
if (!context.hasAttribute(RetryContext.MAX_ATTEMPTS)) {
300+
context.setAttribute(RetryContext.MAX_ATTEMPTS, retryPolicy.getMaxAttempts());
301+
}
302+
299303
// Get or Start the backoff context...
300304
BackOffContext backOffContext = null;
301305
Object resource = context.getAttribute("backOffContext");

src/test/java/org/springframework/retry/annotation/EnableRetryTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ void runtimeExpressions() throws Exception {
294294
ExpressionService service = context.getBean(ExpressionService.class);
295295
service.service6();
296296
RuntimeConfigs runtime = context.getBean(RuntimeConfigs.class);
297-
verify(runtime, times(5)).getMaxAttempts();
297+
verify(runtime, times(6)).getMaxAttempts();
298298
verify(runtime, times(2)).getInitial();
299299
verify(runtime, times(2)).getMax();
300300
verify(runtime, times(2)).getMult();

src/test/java/org/springframework/retry/policy/CompositeRetryPolicyTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,23 @@ public boolean canRetry(RetryContext context) {
160160
assertThat(policy.canRetry(context)).isTrue();
161161
}
162162

163+
@Test
164+
public void testMaximumAttemptsForNonSuitablePolicies() {
165+
CompositeRetryPolicy policy = new CompositeRetryPolicy();
166+
policy.setOptimistic(true);
167+
policy.setPolicies(new RetryPolicy[] { new NeverRetryPolicy(), new NeverRetryPolicy() });
168+
169+
assertThat(policy.getMaxAttempts()).isEqualTo(RetryPolicy.NO_MAXIMUM_ATTEMPTS_SET);
170+
}
171+
172+
@Test
173+
public void testMaximumAttemptsForSuitablePolicies() {
174+
CompositeRetryPolicy policy = new CompositeRetryPolicy();
175+
policy.setOptimistic(true);
176+
policy.setPolicies(
177+
new RetryPolicy[] { new SimpleRetryPolicy(6), new SimpleRetryPolicy(3), new SimpleRetryPolicy(4) });
178+
179+
assertThat(policy.getMaxAttempts()).isEqualTo(3);
180+
}
181+
163182
}

src/test/java/org/springframework/retry/support/RetryTemplateTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
import org.springframework.retry.RetryCallback;
2727
import org.springframework.retry.RetryContext;
2828
import org.springframework.retry.RetryListener;
29+
import org.springframework.retry.RetryPolicy;
2930
import org.springframework.retry.TerminatedRetryException;
3031
import org.springframework.retry.backoff.BackOffContext;
3132
import org.springframework.retry.backoff.BackOffInterruptedException;
3233
import org.springframework.retry.backoff.BackOffPolicy;
3334
import org.springframework.retry.backoff.StatelessBackOffPolicy;
35+
import org.springframework.retry.policy.AlwaysRetryPolicy;
3436
import org.springframework.retry.policy.NeverRetryPolicy;
3537
import org.springframework.retry.policy.SimpleRetryPolicy;
3638

@@ -333,6 +335,30 @@ public <T, E extends Throwable> void onSuccess(RetryContext context, RetryCallba
333335
assertThat(callCount.get()).isEqualTo(2);
334336
}
335337

338+
@Test
339+
public void testContextForPolicyWithMaximumNumberOfAttempts() throws Throwable {
340+
RetryTemplate retryTemplate = new RetryTemplate();
341+
RetryPolicy retryPolicy = new SimpleRetryPolicy(2);
342+
retryTemplate.setRetryPolicy(retryPolicy);
343+
344+
Integer result = retryTemplate.execute((RetryCallback<Integer, Throwable>) context -> (Integer) context
345+
.getAttribute(RetryContext.MAX_ATTEMPTS), context -> RetryPolicy.NO_MAXIMUM_ATTEMPTS_SET);
346+
347+
assertThat(result).isEqualTo(2);
348+
}
349+
350+
@Test
351+
public void testContextForPolicyWithNoMaximumNumberOfAttempts() throws Throwable {
352+
RetryTemplate retryTemplate = new RetryTemplate();
353+
RetryPolicy retryPolicy = new AlwaysRetryPolicy();
354+
retryTemplate.setRetryPolicy(retryPolicy);
355+
356+
Integer result = retryTemplate.execute((RetryCallback<Integer, Throwable>) context -> (Integer) context
357+
.getAttribute(RetryContext.MAX_ATTEMPTS), context -> RetryPolicy.NO_MAXIMUM_ATTEMPTS_SET);
358+
359+
assertThat(result).isEqualTo(RetryPolicy.NO_MAXIMUM_ATTEMPTS_SET);
360+
}
361+
336362
private static class MockRetryCallback implements RetryCallback<Object, Exception> {
337363

338364
private int attempts;

0 commit comments

Comments
 (0)