Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support of Duration in RetryTemplateBuilder #344

Merged
merged 2 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.retry.support;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -83,6 +84,7 @@
* @author Aleksandr Shamukov
* @author Artem Bilan
* @author Kim In Hoi
* @author Andreas Ahlenstorf
* @since 1.3
*/
public class RetryTemplateBuilder {
Expand Down Expand Up @@ -122,16 +124,43 @@ public RetryTemplateBuilder maxAttempts(int maxAttempts) {
* @param timeout whole execution timeout in milliseconds
* @return this
* @see TimeoutRetryPolicy
* @deprecated Use {@link #withTimeout(long)} instead.
*/
@Deprecated(since = "2.0.2", forRemoval = true)
public RetryTemplateBuilder withinMillis(long timeout) {
Assert.isTrue(timeout > 0, "Timeout should be positive");
return withTimeout(timeout);
}

/**
* Retry until {@code timeout} has passed since the initial attempt.
* @param timeoutMillis timeout in milliseconds
* @return this
* @see TimeoutRetryPolicy
* @throws IllegalArgumentException if timeout is {@literal <=} 0 or if another retry
* policy has already been configured.
* @since 2.0.2
*/
public RetryTemplateBuilder withTimeout(long timeoutMillis) {
Assert.isTrue(timeoutMillis > 0, "timeoutMillis should be greater than 0");
Assert.isNull(this.baseRetryPolicy, "You have already selected another retry policy");
TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy();
timeoutRetryPolicy.setTimeout(timeout);
this.baseRetryPolicy = timeoutRetryPolicy;
this.baseRetryPolicy = new TimeoutRetryPolicy(timeoutMillis);
return this;
}

/**
* Retry until {@code timeout} has passed since the initial attempt.
* @param timeout duration for how long retries should be attempted
* @return this
* @see TimeoutRetryPolicy
* @throws IllegalArgumentException if timeout is {@code null} or 0, or if another
* retry policy has already been configured.
* @since 2.0.2
*/
public RetryTemplateBuilder withTimeout(Duration timeout) {
artembilan marked this conversation as resolved.
Show resolved Hide resolved
Assert.notNull(timeout, "timeout must not be null");
return withTimeout(timeout.toMillis());
}

/**
* Allows infinite retry, do not limit attempts by number or time.
* <p>
Expand Down Expand Up @@ -180,6 +209,27 @@ public RetryTemplateBuilder exponentialBackoff(long initialInterval, double mult
return exponentialBackoff(initialInterval, multiplier, maxInterval, false);
}

/**
* Use exponential backoff policy. The formula of backoff period:
* <p>
* {@code currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)}
* <p>
* (for first attempt retryNum = 0)
* @param initialInterval initial sleep duration
* @param multiplier backoff interval multiplier
* @param maxInterval maximum backoff duration
* @return this
* @see ExponentialBackOffPolicy
* @throws IllegalArgumentException if initialInterval is {@code null}, multiplier is
* {@literal <=} 1, or if maxInterval is {@code null}
* @since 2.0.2
*/
public RetryTemplateBuilder exponentialBackoff(Duration initialInterval, double multiplier, Duration maxInterval) {
Assert.notNull(initialInterval, "initialInterval must not be null");
Assert.notNull(maxInterval, "maxInterval must not be null");
return exponentialBackoff(initialInterval.toMillis(), multiplier, maxInterval.toMillis(), false);
}

/**
* Use exponential backoff policy. The formula of backoff period (without randomness):
* <p>
Expand Down Expand Up @@ -210,6 +260,31 @@ public RetryTemplateBuilder exponentialBackoff(long initialInterval, double mult
return this;
}

/**
* Use exponential backoff policy. The formula of backoff period (without randomness):
* <p>
* {@code currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)}
* <p>
* (for first attempt retryNum = 0)
* @param initialInterval initial sleep duration
* @param multiplier backoff interval multiplier
* @param maxInterval maximum backoff duration
* @param withRandom adds some randomness to backoff intervals. For details, see
* {@link ExponentialRandomBackOffPolicy}
* @return this
* @see ExponentialBackOffPolicy
* @see ExponentialRandomBackOffPolicy
* @throws IllegalArgumentException if initialInterval is {@code null}, multiplier is
* {@literal <=} 1, or maxInterval is {@code null}
* @since 2.0.2
*/
public RetryTemplateBuilder exponentialBackoff(Duration initialInterval, double multiplier, Duration maxInterval,
boolean withRandom) {
Assert.notNull(initialInterval, "initialInterval most not be null");
Assert.notNull(maxInterval, "maxInterval must not be null");
return this.exponentialBackoff(initialInterval.toMillis(), multiplier, maxInterval.toMillis(), withRandom);
}

/**
* Perform each retry after fixed amount of time.
* @param interval fixed interval in milliseconds
Expand All @@ -225,6 +300,24 @@ public RetryTemplateBuilder fixedBackoff(long interval) {
return this;
}

/**
* Perform each retry after fixed amount of time.
* @param interval fixed backoff duration
* @return this
* @see FixedBackOffPolicy
* @throws IllegalArgumentException if another backoff policy has already been
* configured, interval is {@code null} or less than 1 millisecond
* @since 2.0.2
*/
public RetryTemplateBuilder fixedBackoff(Duration interval) {
Assert.notNull(interval, "interval must not be null");

long millis = interval.toMillis();
Assert.isTrue(millis >= 1, "interval is less than 1 millisecond");

return this.fixedBackoff(millis);
}

/**
* Use {@link UniformRandomBackOffPolicy}, see it's doc for details.
* @param minInterval in milliseconds
Expand All @@ -244,6 +337,23 @@ public RetryTemplateBuilder uniformRandomBackoff(long minInterval, long maxInter
return this;
}

/**
* Use {@link UniformRandomBackOffPolicy}.
* @param minInterval minimum backoff duration
* @param maxInterval maximum backoff duration
* @return this
* @see UniformRandomBackOffPolicy
* @throws IllegalArgumentException if minInterval is {@code null} or {@literal <} 1,
* maxInterval is {@code null} or {@literal <} 1, maxInterval {@literal >=}
* minInterval or if another backoff policy has already been configured.
* @since 2.0.2
*/
public RetryTemplateBuilder uniformRandomBackoff(Duration minInterval, Duration maxInterval) {
Assert.notNull(minInterval, "minInterval must not be null");
Assert.notNull(maxInterval, "maxInterval must not be null");
return this.uniformRandomBackoff(minInterval.toMillis(), maxInterval.toMillis());
}

/**
* Do not pause between attempts, retry immediately.
* @return this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.io.FileNotFoundException;
import java.io.IOException;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
Expand All @@ -29,6 +30,7 @@
import org.springframework.retry.RetryPolicy;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.backoff.ExponentialRandomBackOffPolicy;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.backoff.UniformRandomBackOffPolicy;
import org.springframework.retry.policy.AlwaysRetryPolicy;
Expand All @@ -53,6 +55,7 @@
* @author Aleksandr Shamukov
* @author Kim In Hoi
* @author Gary Russell
* @author Andreas Ahlenstorf
*/
public class RetryTemplateBuilderTests {

Expand Down Expand Up @@ -113,10 +116,11 @@ public void testBasicCustomization() {
@Test
public void testFailOnRetryPoliciesConflict() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RetryTemplate.builder().maxAttempts(3).withinMillis(1000).build());
.isThrownBy(() -> RetryTemplate.builder().maxAttempts(3).withTimeout(1000).build());
}

@Test
@SuppressWarnings("removal")
public void testTimeoutPolicy() {
RetryTemplate template = RetryTemplate.builder().withinMillis(10000).build();

Expand All @@ -127,6 +131,28 @@ public void testTimeoutPolicy() {
assertThat(((TimeoutRetryPolicy) policyTuple.baseRetryPolicy).getTimeout()).isEqualTo(10000);
}

@Test
public void testTimeoutMillis() {
RetryTemplate template = RetryTemplate.builder().withTimeout(10000).build();

PolicyTuple policyTuple = PolicyTuple.extractWithAsserts(template);
assertDefaultClassifier(policyTuple);

assertThat(policyTuple.baseRetryPolicy).isInstanceOf(TimeoutRetryPolicy.class);
assertThat(((TimeoutRetryPolicy) policyTuple.baseRetryPolicy).getTimeout()).isEqualTo(10000);
}

@Test
public void testTimeoutDuration() {
RetryTemplate template = RetryTemplate.builder().withTimeout(Duration.ofSeconds(3)).build();

PolicyTuple policyTuple = PolicyTuple.extractWithAsserts(template);
assertDefaultClassifier(policyTuple);

assertThat(policyTuple.baseRetryPolicy).isInstanceOf(TimeoutRetryPolicy.class);
assertThat(((TimeoutRetryPolicy) policyTuple.baseRetryPolicy).getTimeout()).isEqualTo(3000);
}

@Test
public void testInfiniteRetry() {
RetryTemplate template = RetryTemplate.builder().infiniteRetry().build();
Expand Down Expand Up @@ -190,24 +216,91 @@ public void testFailOnBackOffPolicyConflict() {
.isThrownBy(() -> RetryTemplate.builder().noBackoff().fixedBackoff(1000).build());
}

@Test
public void testFixedBackoff() {
RetryTemplate template = RetryTemplate.builder().fixedBackoff(200).build();
FixedBackOffPolicy policy = getPropertyValue(template, "backOffPolicy", FixedBackOffPolicy.class);

assertThat(policy.getBackOffPeriod()).isEqualTo(200);
}

@Test
public void testFixedBackoffDuration() {
RetryTemplate template = RetryTemplate.builder().fixedBackoff(Duration.ofSeconds(1)).build();
FixedBackOffPolicy policy = getPropertyValue(template, "backOffPolicy", FixedBackOffPolicy.class);

assertThat(policy.getBackOffPeriod()).isEqualTo(1000);
}

@Test
public void testUniformRandomBackOff() {
RetryTemplate template = RetryTemplate.builder().uniformRandomBackoff(10, 100).build();
assertThat(getPropertyValue(template, "backOffPolicy")).isInstanceOf(UniformRandomBackOffPolicy.class);
}

@Test
public void testUniformRandomBackOffDuration() {
RetryTemplate template = RetryTemplate.builder()
.uniformRandomBackoff(Duration.ofSeconds(1), Duration.ofSeconds(2))
.build();

UniformRandomBackOffPolicy policy = getPropertyValue(template, "backOffPolicy",
UniformRandomBackOffPolicy.class);

assertThat(policy.getMinBackOffPeriod()).isEqualTo(1000);
assertThat(policy.getMaxBackOffPeriod()).isEqualTo(2000);
}

@Test
public void testNoBackOff() {
RetryTemplate template = RetryTemplate.builder().noBackoff().build();
assertThat(getPropertyValue(template, "backOffPolicy")).isInstanceOf(NoBackOffPolicy.class);
}

@Test
public void testExponentialBackoff() {
RetryTemplate template = RetryTemplate.builder().exponentialBackoff(10, 2, 500).build();
ExponentialBackOffPolicy policy = getPropertyValue(template, "backOffPolicy", ExponentialBackOffPolicy.class);

assertThat(policy.getInitialInterval()).isEqualTo(10);
assertThat(policy.getMultiplier()).isEqualTo(2);
assertThat(policy.getMaxInterval()).isEqualTo(500);
}

@Test
public void testExponentialBackoffDuration() {
RetryTemplate template = RetryTemplate.builder()
.exponentialBackoff(Duration.ofSeconds(2), 2, Duration.ofSeconds(3))
.build();

ExponentialBackOffPolicy policy = getPropertyValue(template, "backOffPolicy", ExponentialBackOffPolicy.class);

assertThat(policy.getInitialInterval()).isEqualTo(2000);
assertThat(policy.getMultiplier()).isEqualTo(2);
assertThat(policy.getMaxInterval()).isEqualTo(3000);
assertThat(policy.getMaxInterval()).isEqualTo(3000);
}

@Test
public void testExpBackOffWithRandom() {
RetryTemplate template = RetryTemplate.builder().exponentialBackoff(10, 2, 500, true).build();
assertThat(getPropertyValue(template, "backOffPolicy")).isInstanceOf(ExponentialRandomBackOffPolicy.class);
}

@Test
public void testExponentialRandomBackoffDuration() {
RetryTemplate template = RetryTemplate.builder()
.exponentialBackoff(Duration.ofSeconds(2), 2, Duration.ofSeconds(3), true)
.build();

ExponentialRandomBackOffPolicy policy = getPropertyValue(template, "backOffPolicy",
ExponentialRandomBackOffPolicy.class);

assertThat(policy.getInitialInterval()).isEqualTo(2000);
assertThat(policy.getMultiplier()).isEqualTo(2);
assertThat(policy.getMaxInterval()).isEqualTo(3000);
}

@Test
public void testValidateInitAndMax() {
assertThatIllegalArgumentException()
Expand Down