Accept zero for RetryPolicy.Builder.delay()

This aligns the programmatic RetryPolicy configuration option with the
delay support in @⁠Retryable.

See gh-35110
This commit is contained in:
Sam Brannen 2025-07-01 17:40:15 +02:00
parent c9078bfe14
commit cc31bf3c33
5 changed files with 25 additions and 25 deletions

View File

@ -111,7 +111,7 @@ public @interface Retryable {
* this serves as the initial delay to multiply from.
* <p>The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.
* <p>The default is 1000.
* <p>Must be greater than or equal to zero. The default is 1000.
* @see #jitter()
* @see #multiplier()
* @see #maxDelay()

View File

@ -209,14 +209,15 @@ public interface RetryPolicy {
* <p>You should not specify this configuration option if you have
* configured a custom {@link #backOff(BackOff) BackOff} strategy.
* @param delay the base delay, typically in milliseconds or seconds;
* must be positive
* must be greater than or equal to zero
* @return this {@code Builder} instance for chained method invocations
* @see #jitter(Duration)
* @see #multiplier(double)
* @see #maxDelay(Duration)
*/
public Builder delay(Duration delay) {
assertIsPositive("delay", delay);
Assert.isTrue(!delay.isNegative(),
() -> "Invalid delay (%dms): must be >= 0.".formatted(delay.toMillis()));
this.delay = delay;
return this;
}
@ -233,7 +234,8 @@ public interface RetryPolicy {
* <p>The supplied value will override any previously configured value.
* <p>You should not specify this configuration option if you have
* configured a custom {@link #backOff(BackOff) BackOff} strategy.
* @param jitter the jitter value, typically in milliseconds; must be positive
* @param jitter the jitter value, typically in milliseconds; must be
* greater than or equal to zero
* @return this {@code Builder} instance for chained method invocations
* @see #delay(Duration)
* @see #multiplier(double)

View File

@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.springframework.core.retry.RetryPolicy.Builder.DEFAULT_DELAY;
import static org.springframework.util.backoff.BackOffExecution.STOP;
/**
@ -38,14 +39,14 @@ class MaxAttemptsRetryPolicyTests {
@Test
void maxAttempts() {
var retryPolicy = RetryPolicy.builder().maxAttempts(2).delay(Duration.ofMillis(1)).build();
var retryPolicy = RetryPolicy.builder().maxAttempts(2).delay(Duration.ofMillis(0)).build();
var backOffExecution = retryPolicy.getBackOff().start();
var throwable = mock(Throwable.class);
assertThat(retryPolicy.shouldRetry(throwable)).isTrue();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isZero();
assertThat(retryPolicy.shouldRetry(throwable)).isTrue();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isZero();
assertThat(retryPolicy.shouldRetry(throwable)).isTrue();
assertThat(backOffExecution.nextBackOff()).isEqualTo(STOP);
@ -65,13 +66,13 @@ class MaxAttemptsRetryPolicyTests {
// 4 retries
assertThat(retryPolicy.shouldRetry(new NumberFormatException())).isTrue();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isEqualTo(1);
assertThat(retryPolicy.shouldRetry(new IllegalStateException())).isFalse();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isEqualTo(1);
assertThat(retryPolicy.shouldRetry(new IllegalStateException())).isFalse();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isEqualTo(1);
assertThat(retryPolicy.shouldRetry(new CustomNumberFormatException())).isTrue();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isEqualTo(1);
// After policy exhaustion
assertThat(retryPolicy.shouldRetry(new NumberFormatException())).isTrue();
@ -92,17 +93,17 @@ class MaxAttemptsRetryPolicyTests {
// 6 retries
assertThat(retryPolicy.shouldRetry(new IOException())).isTrue();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isEqualTo(DEFAULT_DELAY);
assertThat(retryPolicy.shouldRetry(new RuntimeException())).isTrue();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isEqualTo(DEFAULT_DELAY);
assertThat(retryPolicy.shouldRetry(new FileNotFoundException())).isFalse();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isEqualTo(DEFAULT_DELAY);
assertThat(retryPolicy.shouldRetry(new FileSystemException("file"))).isTrue();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isEqualTo(DEFAULT_DELAY);
assertThat(retryPolicy.shouldRetry(new CustomFileSystemException("file"))).isFalse();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isEqualTo(DEFAULT_DELAY);
assertThat(retryPolicy.shouldRetry(new IOException())).isTrue();
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
assertThat(backOffExecution.nextBackOff()).isEqualTo(DEFAULT_DELAY);
// After policy exhaustion
assertThat(retryPolicy.shouldRetry(new IOException())).isTrue();

View File

@ -162,12 +162,9 @@ class RetryPolicyTests {
@Test
void delayPreconditions() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RetryPolicy.builder().delay(Duration.ofMillis(0)))
.withMessage("Invalid duration (0ms): delay must be positive.");
assertThatIllegalArgumentException()
.isThrownBy(() -> RetryPolicy.builder().delay(Duration.ofMillis(-1)))
.withMessage("Invalid duration (-1ms): delay must be positive.");
.withMessage("Invalid delay (-1ms): must be >= 0.");
}
@Test

View File

@ -51,7 +51,7 @@ class RetryTemplateTests {
void configureRetryTemplate() {
var retryPolicy = RetryPolicy.builder()
.maxAttempts(3)
.delay(Duration.ofMillis(1))
.delay(Duration.ofMillis(0))
.build();
retryTemplate.setRetryPolicy(retryPolicy);
@ -171,7 +171,7 @@ class RetryTemplateTests {
var retryPolicy = RetryPolicy.builder()
.maxAttempts(Integer.MAX_VALUE)
.delay(Duration.ofMillis(1))
.delay(Duration.ofMillis(0))
.includes(IOException.class)
.build();
@ -194,13 +194,13 @@ class RetryTemplateTests {
argumentSet("Excludes",
RetryPolicy.builder()
.maxAttempts(Integer.MAX_VALUE)
.delay(Duration.ofMillis(1))
.delay(Duration.ofMillis(0))
.excludes(FileNotFoundException.class)
.build()),
argumentSet("Includes & Excludes",
RetryPolicy.builder()
.maxAttempts(Integer.MAX_VALUE)
.delay(Duration.ofMillis(1))
.delay(Duration.ofMillis(0))
.includes(IOException.class)
.excludes(FileNotFoundException.class)
.build())