Skip to content

Commit 5cae769

Browse files
committed
Remove maxDuration/maxElapsedTime support from RetryPolicy
When the core retry functionality was introduced, it had a built-in MaxRetryDurationPolicy. In #35058, that was migrated to a withMaxDuration() factory method, and in #35110 that was renamed to withMaxElapsedTime() (with a corresponding maxElapsedTime() method on the builder) in order to align with the maxElapsedTime feature of ExponentialBackOff. The latter also changed the semantics of the feature in the context of the RetryPolicy. However, @⁠Retryable does not provide maxElapsedTime support. In addition, the maxElapsedTime feature is a bit misleading, since it does not actually track CPU time or wall-clock time but rather only the sum of individual, accumulated back-off intervals/delays, which is likely not very useful. Furthermore, the maxElapsedTime will never apply to a zero-valued delay/interval. In light of the above, this commit removes the maxElapsedTime support from the built-in RetryPolicy. Users can still implement a custom BackOff strategy if they find they need some form of "max elapsed time" or "max duration". See gh-34716 See gh-35058 See gh-34529 See gh-35110 Closes gh-35144
1 parent b256bab commit 5cae769

File tree

2 files changed

+20
-99
lines changed

2 files changed

+20
-99
lines changed

spring-core/src/main/java/org/springframework/core/retry/RetryPolicy.java

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
*
3636
* <p>Also provides factory methods and a fluent builder API for creating retry
3737
* policies with common configurations. See {@link #withDefaults()},
38-
* {@link #withMaxAttempts(long)}, {@link #withMaxElapsedTime(Duration)},
39-
* {@link #builder()}, and the configuration options in {@link Builder} for details.
38+
* {@link #withMaxAttempts(long)}, {@link #builder()}, and the configuration
39+
* options in {@link Builder} for details.
4040
*
4141
* @author Sam Brannen
4242
* @author Mahmoud Ben Hassine
@@ -91,18 +91,6 @@ static RetryPolicy withMaxAttempts(long maxAttempts) {
9191
return builder().backOff(new FixedBackOff(Builder.DEFAULT_DELAY, maxAttempts)).build();
9292
}
9393

94-
/**
95-
* Create a {@link RetryPolicy} configured with a maximum elapsed time.
96-
* <p>The returned policy uses a fixed backoff of {@value Builder#DEFAULT_DELAY}
97-
* milliseconds.
98-
* @param maxElapsedTime the maximum elapsed time; must be positive
99-
* @see Builder#maxElapsedTime(Duration)
100-
* @see FixedBackOff
101-
*/
102-
static RetryPolicy withMaxElapsedTime(Duration maxElapsedTime) {
103-
return builder().maxElapsedTime(maxElapsedTime).build();
104-
}
105-
10694
/**
10795
* Create a {@link Builder} to configure a {@link RetryPolicy} with common
10896
* configuration options.
@@ -152,8 +140,6 @@ final class Builder {
152140

153141
private @Nullable Duration maxDelay;
154142

155-
private @Nullable Duration maxElapsedTime;
156-
157143
private final Set<Class<? extends Throwable>> includes = new LinkedHashSet<>();
158144

159145
private final Set<Class<? extends Throwable>> excludes = new LinkedHashSet<>();
@@ -173,8 +159,7 @@ private Builder() {
173159
* strategy, you should not configure any of the following:
174160
* {@link #maxAttempts(long) maxAttempts}, {@link #delay(Duration) delay},
175161
* {@link #jitter(Duration) jitter}, {@link #multiplier(double) multiplier},
176-
* {@link #maxDelay(Duration) maxDelay}, or {@link #maxElapsedTime(Duration)
177-
* maxElapsedTime}.
162+
* or {@link #maxDelay(Duration) maxDelay}.
178163
* @param backOff the {@code BackOff} strategy
179164
* @return this {@code Builder} instance for chained method invocations
180165
*/
@@ -291,21 +276,6 @@ public Builder maxDelay(Duration maxDelay) {
291276
return this;
292277
}
293278

294-
/**
295-
* Specify the maximum elapsed time.
296-
* <p>The default is unlimited.
297-
* <p>The supplied value will override any previously configured value.
298-
* <p>You should not specify this configuration option if you have
299-
* configured a custom {@link #backOff(BackOff) BackOff} strategy.
300-
* @param maxElapsedTime the maximum elapsed time; must be positive
301-
* @return this {@code Builder} instance for chained method invocations
302-
*/
303-
public Builder maxElapsedTime(Duration maxElapsedTime) {
304-
assertIsPositive("maxElapsedTime", maxElapsedTime);
305-
this.maxElapsedTime = maxElapsedTime;
306-
return this;
307-
}
308-
309279
/**
310280
* Specify the types of exceptions for which the {@link RetryPolicy}
311281
* should retry a failed operation.
@@ -415,10 +385,10 @@ public RetryPolicy build() {
415385
BackOff backOff = this.backOff;
416386
if (backOff != null) {
417387
boolean misconfigured = (this.maxAttempts != 0) || (this.delay != null) || (this.jitter != null) ||
418-
(this.multiplier != 0) || (this.maxDelay != null) || (this.maxElapsedTime != null);
388+
(this.multiplier != 0) || (this.maxDelay != null);
419389
Assert.state(!misconfigured, """
420390
The following configuration options are not supported with a custom BackOff strategy: \
421-
maxAttempts, delay, jitter, multiplier, maxDelay, or maxElapsedTime.""");
391+
maxAttempts, delay, jitter, multiplier, or maxDelay.""");
422392
}
423393
else {
424394
ExponentialBackOff exponentialBackOff = new ExponentialBackOff();
@@ -429,9 +399,6 @@ public RetryPolicy build() {
429399
if (this.jitter != null) {
430400
exponentialBackOff.setJitter(this.jitter.toMillis());
431401
}
432-
if (this.maxElapsedTime != null) {
433-
exponentialBackOff.setMaxElapsedTime(this.maxElapsedTime.toMillis());
434-
}
435402
backOff = exponentialBackOff;
436403
}
437404
return new DefaultRetryPolicy(this.includes, this.excludes, this.predicate, backOff);

spring-core/src/test/java/org/springframework/core/retry/RetryPolicyTests.java

Lines changed: 15 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -87,29 +87,6 @@ void withMaxAttempts() {
8787
assertThat(backOff.getInterval()).isEqualTo(1000);
8888
});
8989
}
90-
91-
@Test
92-
void withMaxElapsedTimePreconditions() {
93-
assertThatIllegalArgumentException()
94-
.isThrownBy(() -> RetryPolicy.withMaxElapsedTime(Duration.ofMillis(0)))
95-
.withMessage("Invalid duration (0ms): maxElapsedTime must be positive.");
96-
assertThatIllegalArgumentException()
97-
.isThrownBy(() -> RetryPolicy.withMaxElapsedTime(Duration.ofMillis(-1)))
98-
.withMessage("Invalid duration (-1ms): maxElapsedTime must be positive.");
99-
}
100-
101-
@Test
102-
void withMaxElapsedTime() {
103-
var policy = RetryPolicy.withMaxElapsedTime(Duration.ofMillis(42));
104-
105-
assertThat(policy.shouldRetry(new AssertionError())).isTrue();
106-
assertThat(policy.shouldRetry(new IOException())).isTrue();
107-
108-
assertThat(policy.getBackOff())
109-
.asInstanceOf(type(ExponentialBackOff.class))
110-
.satisfies(hasDefaultMaxAttemptsAndDelay())
111-
.extracting(ExponentialBackOff::getMaxElapsedTime).isEqualTo(42L);
112-
}
11390
}
11491

11592

@@ -122,7 +99,7 @@ void backOffPlusConflictingConfig() {
12299
.isThrownBy(() -> RetryPolicy.builder().backOff(mock()).delay(Duration.ofMillis(10)).build())
123100
.withMessage("""
124101
The following configuration options are not supported with a custom BackOff strategy: \
125-
maxAttempts, delay, jitter, multiplier, maxDelay, or maxElapsedTime.""");
102+
maxAttempts, delay, jitter, multiplier, or maxDelay.""");
126103
}
127104

128105
@Test
@@ -157,7 +134,7 @@ void maxAttempts() {
157134
assertThat(backOff.getInitialInterval()).isEqualTo(1000);
158135
});
159136

160-
assertToString(policy, 1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 5);
137+
assertToString(policy, 1000, 0, 1.0, Long.MAX_VALUE, 5);
161138
}
162139

163140
@Test
@@ -178,7 +155,7 @@ void delay() {
178155
assertThat(backOff.getMaxAttempts()).isEqualTo(3);
179156
});
180157

181-
assertToString(policy, 42, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
158+
assertToString(policy, 42, 0, 1.0, Long.MAX_VALUE, 3);
182159
}
183160

184161
@Test
@@ -197,7 +174,7 @@ void jitter() {
197174
.satisfies(hasDefaultMaxAttemptsAndDelay())
198175
.extracting(ExponentialBackOff::getJitter).isEqualTo(42L);
199176

200-
assertToString(policy, 1000, 42, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
177+
assertToString(policy, 1000, 42, 1.0, Long.MAX_VALUE, 3);
201178
}
202179

203180
@Test
@@ -226,7 +203,7 @@ void multiplier() {
226203
.satisfies(hasDefaultMaxAttemptsAndDelay())
227204
.extracting(ExponentialBackOff::getMultiplier).isEqualTo(1.5);
228205

229-
assertToString(policy, 1000, 0, 1.5, Long.MAX_VALUE, Long.MAX_VALUE, 3);
206+
assertToString(policy, 1000, 0, 1.5, Long.MAX_VALUE, 3);
230207
}
231208

232209
@Test
@@ -248,29 +225,7 @@ void maxDelay() {
248225
.satisfies(hasDefaultMaxAttemptsAndDelay())
249226
.extracting(ExponentialBackOff::getMaxInterval).isEqualTo(42L);
250227

251-
assertToString(policy, 1000, 0, 1, 42, Long.MAX_VALUE, 3);
252-
}
253-
254-
@Test
255-
void maxElapsedTimePreconditions() {
256-
assertThatIllegalArgumentException()
257-
.isThrownBy(() -> RetryPolicy.builder().maxElapsedTime(Duration.ofMillis(0)))
258-
.withMessage("Invalid duration (0ms): maxElapsedTime must be positive.");
259-
assertThatIllegalArgumentException()
260-
.isThrownBy(() -> RetryPolicy.builder().maxElapsedTime(Duration.ofMillis(-1)))
261-
.withMessage("Invalid duration (-1ms): maxElapsedTime must be positive.");
262-
}
263-
264-
@Test
265-
void maxElapsedTime() {
266-
var policy = RetryPolicy.builder().maxElapsedTime(Duration.ofMillis(42)).build();
267-
268-
assertThat(policy.getBackOff())
269-
.asInstanceOf(type(ExponentialBackOff.class))
270-
.satisfies(hasDefaultMaxAttemptsAndDelay())
271-
.extracting(ExponentialBackOff::getMaxElapsedTime).isEqualTo(42L);
272-
273-
assertToString(policy, 1000, 0, 1, Long.MAX_VALUE, 42, 3);
228+
assertToString(policy, 1000, 0, 1.0, 42, 3);
274229
}
275230

276231
@Test
@@ -294,7 +249,7 @@ void includes() {
294249

295250
String filters = "includes=" + names(FileNotFoundException.class, IllegalArgumentException.class,
296251
NumberFormatException.class, AssertionError.class) + ", ";
297-
assertToString(policy, filters, 1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
252+
assertToString(policy, filters, 1000, 0, 1.0, Long.MAX_VALUE, 3);
298253
}
299254

300255
@Test
@@ -311,7 +266,7 @@ void includesSubtypeMatching() {
311266
.asInstanceOf(type(ExponentialBackOff.class))
312267
.satisfies(hasDefaultMaxAttemptsAndDelay());
313268

314-
assertToString(policy, "includes=[java.io.IOException], ", 1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
269+
assertToString(policy, "includes=[java.io.IOException], ", 1000, 0, 1.0, Long.MAX_VALUE, 3);
315270
}
316271

317272
@Test
@@ -335,7 +290,7 @@ void excludes() {
335290

336291
String filters = "excludes=" + names(FileNotFoundException.class, IllegalArgumentException.class,
337292
NumberFormatException.class, AssertionError.class) + ", ";
338-
assertToString(policy, filters, 1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
293+
assertToString(policy, filters, 1000, 0, 1.0, Long.MAX_VALUE, 3);
339294
}
340295

341296
@Test
@@ -349,7 +304,7 @@ void excludesSubtypeMatching() {
349304
assertThat(policy.shouldRetry(new Throwable())).isTrue();
350305
assertThat(policy.shouldRetry(new AssertionError())).isTrue();
351306

352-
assertToString(policy, "excludes=[java.io.IOException], ", 1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
307+
assertToString(policy, "excludes=[java.io.IOException], ", 1000, 0, 1.0, Long.MAX_VALUE, 3);
353308
}
354309

355310
@Test
@@ -368,8 +323,7 @@ void predicate() {
368323
.asInstanceOf(type(ExponentialBackOff.class))
369324
.satisfies(hasDefaultMaxAttemptsAndDelay());
370325

371-
assertToString(policy, "predicate=NumberFormatExceptionMatcher, ",
372-
1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
326+
assertToString(policy, "predicate=NumberFormatExceptionMatcher, ", 1000, 0, 1.0, Long.MAX_VALUE, 3);
373327
}
374328

375329
@Test
@@ -398,13 +352,13 @@ void predicatesCombined() {
398352

399353

400354
private static void assertToString(RetryPolicy policy, long initialInterval, long jitter,
401-
double multiplier, long maxInterval, long maxElapsedTime, int maxAttempts) {
355+
double multiplier, long maxInterval, int maxAttempts) {
402356

403-
assertToString(policy, "", initialInterval, jitter, multiplier, maxInterval, maxElapsedTime, maxAttempts);
357+
assertToString(policy, "", initialInterval, jitter, multiplier, maxInterval, maxAttempts);
404358
}
405359

406360
private static void assertToString(RetryPolicy policy, String filters, long initialInterval, long jitter,
407-
double multiplier, long maxInterval, long maxElapsedTime, int maxAttempts) {
361+
double multiplier, long maxInterval, int maxAttempts) {
408362

409363
assertThat(policy).asString()
410364
.isEqualTo("""
@@ -416,7 +370,7 @@ private static void assertToString(RetryPolicy policy, String filters, long init
416370
maxElapsedTime=%d, \
417371
maxAttempts=%d\
418372
]]""",
419-
filters, initialInterval, jitter, multiplier, maxInterval, maxElapsedTime, maxAttempts);
373+
filters, initialInterval, jitter, multiplier, maxInterval, Long.MAX_VALUE, maxAttempts);
420374
}
421375

422376
@SafeVarargs

0 commit comments

Comments
 (0)