Skip to content

Commit f40dd27

Browse files
authored
Add default backoff strategies (#3906)
* Add default backoff strategies * Moved the backoff strategires to the SPI package * Use AssertJ instead of Hamcrest
1 parent 7ef8830 commit f40dd27

File tree

12 files changed

+794
-6
lines changed

12 files changed

+794
-6
lines changed

core/retries-api/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<parent>
2121
<artifactId>core</artifactId>
2222
<groupId>software.amazon.awssdk</groupId>
23-
<version>2.20.4-SNAPSHOT</version>
23+
<version>2.20.7-SNAPSHOT</version>
2424
</parent>
2525
<modelVersion>4.0.0</modelVersion>
2626

@@ -60,8 +60,8 @@
6060
<scope>test</scope>
6161
</dependency>
6262
<dependency>
63-
<groupId>org.hamcrest</groupId>
64-
<artifactId>hamcrest-all</artifactId>
63+
<groupId>org.assertj</groupId>
64+
<artifactId>assertj-core</artifactId>
6565
<scope>test</scope>
6666
</dependency>
6767
</dependencies>

core/retries-api/src/main/java/software/amazon/awssdk/retries/api/BackoffStrategy.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616
package software.amazon.awssdk.retries.api;
1717

1818
import java.time.Duration;
19+
import java.util.concurrent.ThreadLocalRandom;
1920
import software.amazon.awssdk.annotations.SdkPublicApi;
2021
import software.amazon.awssdk.annotations.ThreadSafe;
22+
import software.amazon.awssdk.retries.api.internal.backoff.ExponentialDelayWithJitter;
23+
import software.amazon.awssdk.retries.api.internal.backoff.ExponentialDelayWithoutJitter;
24+
import software.amazon.awssdk.retries.api.internal.backoff.FixedDelayWithJitter;
25+
import software.amazon.awssdk.retries.api.internal.backoff.FixedDelayWithoutJitter;
26+
import software.amazon.awssdk.retries.api.internal.backoff.Immediately;
2127

2228
/**
2329
* Determines how long to wait before each execution attempt.
@@ -33,4 +39,47 @@ public interface BackoffStrategy {
3339
* @throws IllegalArgumentException If the given attempt is less or equal to zero.
3440
*/
3541
Duration computeDelay(int attempt);
42+
43+
/**
44+
* Do not back off: retry immediately.
45+
*/
46+
static BackoffStrategy retryImmediately() {
47+
return new Immediately();
48+
}
49+
50+
/**
51+
* Wait for a random period of time between 0ms and the provided delay.
52+
*/
53+
static BackoffStrategy fixedDelay(Duration delay) {
54+
return new FixedDelayWithJitter(ThreadLocalRandom::current, delay);
55+
}
56+
57+
/**
58+
* Wait for a period of time equal to the provided delay.
59+
*/
60+
static BackoffStrategy fixedDelayWithoutJitter(Duration delay) {
61+
return new FixedDelayWithoutJitter(delay);
62+
}
63+
64+
/**
65+
* Wait for a random period of time between 0ms and an exponentially increasing amount of time between each subsequent attempt
66+
* of the same call.
67+
*
68+
* <p>Specifically, the first attempt waits 0ms, and each subsequent attempt waits between
69+
* 0ms and {@code min(maxDelay, baseDelay * (1 << (attempt - 2)))}.
70+
*/
71+
static BackoffStrategy exponentialDelay(Duration baseDelay, Duration maxDelay) {
72+
return new ExponentialDelayWithJitter(ThreadLocalRandom::current, baseDelay, maxDelay);
73+
}
74+
75+
/**
76+
* Wait for an exponentially increasing amount of time between each subsequent attempt of the same call.
77+
*
78+
* <p>Specifically, the first attempt waits 0ms, and each subsequent attempt waits for
79+
* {@code min(maxDelay, baseDelay * (1 << (attempt - 2)))}.
80+
*/
81+
static BackoffStrategy exponentialDelayWithoutJitter(Duration baseDelay, Duration maxDelay) {
82+
return new ExponentialDelayWithoutJitter(baseDelay, maxDelay);
83+
}
84+
3685
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.retries.api.internal.backoff;
17+
18+
import java.time.Duration;
19+
import software.amazon.awssdk.annotations.SdkInternalApi;
20+
21+
/**
22+
* Constants and utility functions shared by the BackoffStrategy implementations.
23+
*/
24+
@SdkInternalApi
25+
class BackoffStrategiesConstants {
26+
static final Duration BASE_DELAY_CEILING = Duration.ofMillis(Integer.MAX_VALUE); // Around ~24.8 days
27+
static final Duration MAX_BACKOFF_CEILING = Duration.ofMillis(Integer.MAX_VALUE); // Around ~24.8 days
28+
/**
29+
* Max permitted retry times. To prevent exponentialDelay from overflow, there must be 2 ^ retriesAttempted &lt;= 2 ^ 31 - 1,
30+
* which means retriesAttempted &lt;= 30, so that is the ceil for retriesAttempted.
31+
*/
32+
static final int RETRIES_ATTEMPTED_CEILING = (int) Math.floor(Math.log(Integer.MAX_VALUE) / Math.log(2));
33+
34+
private BackoffStrategiesConstants() {
35+
}
36+
37+
/**
38+
* Returns the computed exponential delay in milliseconds given the retries attempted, the base delay and the max backoff
39+
* time.
40+
*
41+
* <p>Specifically it returns {@code min(maxDelay, baseDelay * (1 << (attempt - 2)))}. To prevent overflowing the attempts
42+
* get capped to 30.
43+
*/
44+
static int calculateExponentialDelay(int retriesAttempted, Duration baseDelay, Duration maxBackoffTime) {
45+
int cappedRetries = Math.min(retriesAttempted, BackoffStrategiesConstants.RETRIES_ATTEMPTED_CEILING);
46+
return (int) Math.min(baseDelay.multipliedBy(1L << (cappedRetries - 2)).toMillis(), maxBackoffTime.toMillis());
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.retries.api.internal.backoff;
17+
18+
import static software.amazon.awssdk.retries.api.internal.backoff.BackoffStrategiesConstants.calculateExponentialDelay;
19+
20+
import java.time.Duration;
21+
import java.util.Random;
22+
import java.util.function.Supplier;
23+
import software.amazon.awssdk.annotations.SdkInternalApi;
24+
import software.amazon.awssdk.retries.api.BackoffStrategy;
25+
import software.amazon.awssdk.utils.NumericUtils;
26+
import software.amazon.awssdk.utils.ToString;
27+
import software.amazon.awssdk.utils.Validate;
28+
29+
/**
30+
* Strategy that waits for a random period of time between 0ms and an exponentially increasing amount of time between each
31+
* subsequent attempt of the same call.
32+
*
33+
* <p>Specifically, the first attempt waits 0ms, and each subsequent attempt waits between
34+
* 0ms and {@code min(maxDelay, baseDelay * (1 << (attempt - 2)))}.
35+
*/
36+
@SdkInternalApi
37+
public final class ExponentialDelayWithJitter implements BackoffStrategy {
38+
private final Supplier<Random> randomSupplier;
39+
private final Duration baseDelay;
40+
private final Duration maxDelay;
41+
42+
public ExponentialDelayWithJitter(Supplier<Random> randomSupplier, Duration baseDelay, Duration maxDelay) {
43+
this.randomSupplier = Validate.paramNotNull(randomSupplier, "random");
44+
this.baseDelay = NumericUtils.min(Validate.isPositive(baseDelay, "baseDelay"),
45+
BackoffStrategiesConstants.BASE_DELAY_CEILING);
46+
this.maxDelay = NumericUtils.min(Validate.isPositive(maxDelay, "maxDelay"),
47+
BackoffStrategiesConstants.MAX_BACKOFF_CEILING);
48+
}
49+
50+
@Override
51+
public Duration computeDelay(int attempt) {
52+
Validate.isPositive(attempt, "attempt");
53+
if (attempt == 1) {
54+
return Duration.ZERO;
55+
}
56+
int delay = calculateExponentialDelay(attempt, baseDelay, maxDelay);
57+
int randInt = randomSupplier.get().nextInt(delay);
58+
return Duration.ofMillis(randInt);
59+
}
60+
61+
@Override
62+
public String toString() {
63+
return ToString.builder("ExponentialDelayWithJitter")
64+
.add("baseDelay", baseDelay)
65+
.add("maxDelay", maxDelay)
66+
.build();
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.retries.api.internal.backoff;
17+
18+
import static software.amazon.awssdk.retries.api.internal.backoff.BackoffStrategiesConstants.calculateExponentialDelay;
19+
20+
import java.time.Duration;
21+
import software.amazon.awssdk.annotations.SdkInternalApi;
22+
import software.amazon.awssdk.retries.api.BackoffStrategy;
23+
import software.amazon.awssdk.utils.NumericUtils;
24+
import software.amazon.awssdk.utils.ToString;
25+
import software.amazon.awssdk.utils.Validate;
26+
27+
/**
28+
* Strategy that waits for an exponentially increasing amount of time between each subsequent attempt of the same call.
29+
*
30+
* <p>Specifically, the first attempt waits 0ms, and each subsequent attempt waits for
31+
* {@code min(maxDelay, baseDelay * (1 << (attempt - 2)))}.
32+
*/
33+
@SdkInternalApi
34+
public final class ExponentialDelayWithoutJitter implements BackoffStrategy {
35+
private final Duration baseDelay;
36+
private final Duration maxDelay;
37+
38+
public ExponentialDelayWithoutJitter(Duration baseDelay, Duration maxDelay) {
39+
this.baseDelay = NumericUtils.min(Validate.isPositive(baseDelay, "baseDelay"),
40+
BackoffStrategiesConstants.BASE_DELAY_CEILING);
41+
this.maxDelay = NumericUtils.min(Validate.isPositive(maxDelay, "maxDelay"),
42+
BackoffStrategiesConstants.MAX_BACKOFF_CEILING);
43+
}
44+
45+
@Override
46+
public Duration computeDelay(int attempt) {
47+
Validate.isPositive(attempt, "attempt");
48+
if (attempt == 1) {
49+
return Duration.ZERO;
50+
}
51+
int delay = calculateExponentialDelay(attempt, baseDelay, maxDelay);
52+
return Duration.ofMillis(delay);
53+
}
54+
55+
@Override
56+
public String toString() {
57+
return ToString.builder("ExponentialDelayWithoutJitter")
58+
.add("baseDelay", baseDelay)
59+
.add("maxDelay", maxDelay)
60+
.build();
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.retries.api.internal.backoff;
17+
18+
import java.time.Duration;
19+
import java.util.Random;
20+
import java.util.function.Supplier;
21+
import software.amazon.awssdk.annotations.SdkInternalApi;
22+
import software.amazon.awssdk.retries.api.BackoffStrategy;
23+
import software.amazon.awssdk.utils.NumericUtils;
24+
import software.amazon.awssdk.utils.ToString;
25+
import software.amazon.awssdk.utils.Validate;
26+
27+
/**
28+
* Strategy that waits for a random period of time between 0ms and the provided delay.
29+
*/
30+
@SdkInternalApi
31+
public final class FixedDelayWithJitter implements BackoffStrategy {
32+
private final Supplier<Random> randomSupplier;
33+
private final Duration delay;
34+
35+
public FixedDelayWithJitter(Supplier<Random> randomSupplier, Duration delay) {
36+
this.randomSupplier = Validate.paramNotNull(randomSupplier, "random");
37+
this.delay = NumericUtils.min(Validate.isPositive(delay, "delay"), BackoffStrategiesConstants.BASE_DELAY_CEILING);
38+
}
39+
40+
@Override
41+
public Duration computeDelay(int attempt) {
42+
Validate.isPositive(attempt, "attempt");
43+
return Duration.ofMillis(randomSupplier.get().nextInt((int) delay.toMillis()));
44+
}
45+
46+
@Override
47+
public String toString() {
48+
return ToString.builder("FixedDelayWithJitter")
49+
.add("delay", delay)
50+
.build();
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.retries.api.internal.backoff;
17+
18+
import java.time.Duration;
19+
import software.amazon.awssdk.annotations.SdkInternalApi;
20+
import software.amazon.awssdk.retries.api.BackoffStrategy;
21+
import software.amazon.awssdk.utils.ToString;
22+
import software.amazon.awssdk.utils.Validate;
23+
24+
/**
25+
* Strategy that waits for a period of time equal to the provided delay.
26+
*/
27+
@SdkInternalApi
28+
public final class FixedDelayWithoutJitter implements BackoffStrategy {
29+
private final Duration delay;
30+
31+
public FixedDelayWithoutJitter(Duration delay) {
32+
this.delay = Validate.isPositive(delay, "delay");
33+
}
34+
35+
@Override
36+
public Duration computeDelay(int attempt) {
37+
Validate.isPositive(attempt, "attempt");
38+
return delay;
39+
}
40+
41+
@Override
42+
public String toString() {
43+
return ToString.builder("FixedDelayWithoutJitter")
44+
.add("delay", delay)
45+
.build();
46+
}
47+
}

0 commit comments

Comments
 (0)