Skip to content

Commit 3b1b732

Browse files
authored
Add legacy retry strategy (#3988)
* Add legacy retry strategy * Remove public modifiers from test classes to make SonarCloud happy * Fix another SonarCloud code smell * WIP * Address PR comments * Rename all the strategies to use Default prefix instead of Impl suffix * Address PR comments
1 parent 840a2bf commit 3b1b732

13 files changed

+1162
-56
lines changed

core/retries/src/it/java/software/amazon/awssdk/retries/internal/AdaptiveRetryStrategyResourceConstrainedTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static org.assertj.core.api.Assertions.withinPercentage;
2020

2121
import java.time.Duration;
22-
import java.time.Instant;
2322
import java.util.ArrayList;
2423
import java.util.List;
2524
import java.util.Random;
@@ -63,7 +62,7 @@ void seemsToBeCorrectAndThreadSafe() {
6362
ExecutorService executor = Executors.newFixedThreadPool(parallelism);
6463
Server server = new Server(serverWorkers, executor);
6564
RateLimiterTokenBucketStore store = RateLimiterTokenBucketStore.builder().build();
66-
AdaptiveRetryStrategy strategy = AdaptiveRetryStrategyImpl
65+
AdaptiveRetryStrategy strategy = DefaultAdaptiveRetryStrategy
6766
.builder()
6867
// We don't care about how many attempts we allow to, that logic is tested somewhere else.
6968
// so we give the strategy plenty of room for retries.

core/retries/src/main/java/software/amazon/awssdk/retries/AdaptiveRetryStrategy.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import software.amazon.awssdk.retries.api.AcquireInitialTokenRequest;
2222
import software.amazon.awssdk.retries.api.BackoffStrategy;
2323
import software.amazon.awssdk.retries.api.RetryStrategy;
24-
import software.amazon.awssdk.retries.internal.AdaptiveRetryStrategyImpl;
24+
import software.amazon.awssdk.retries.internal.DefaultAdaptiveRetryStrategy;
2525
import software.amazon.awssdk.retries.internal.circuitbreaker.TokenBucketStore;
2626
import software.amazon.awssdk.retries.internal.ratelimiter.RateLimiterTokenBucketStore;
2727

@@ -63,9 +63,9 @@ public interface AdaptiveRetryStrategy extends RetryStrategy<AdaptiveRetryStrate
6363
* </pre>
6464
*/
6565
static AdaptiveRetryStrategy.Builder builder() {
66-
return AdaptiveRetryStrategyImpl
66+
return DefaultAdaptiveRetryStrategy
6767
.builder()
68-
.maxAttempts(DefaultRetryStrategy.Standard.MAX_ATTEMPTS)
68+
.maxAttempts(DefaultRetryStrategy.Adaptive.MAX_ATTEMPTS)
6969
.tokenBucketStore(TokenBucketStore.builder()
7070
.tokenBucketMaxCapacity(DefaultRetryStrategy.Standard.TOKEN_BUCKET_SIZE)
7171
.build())
@@ -84,7 +84,6 @@ interface Builder extends RetryStrategy.Builder<Builder, AdaptiveRetryStrategy>
8484
*/
8585
Builder treatAsThrottling(Predicate<Throwable> treatAsThrottling);
8686

87-
8887
@Override
8988
AdaptiveRetryStrategy build();
9089
}

core/retries/src/main/java/software/amazon/awssdk/retries/DefaultRetryStrategy.java

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@ private DefaultRetryStrategy() {
3030
}
3131

3232
/**
33-
* Create a new builder for a {@code StandardRetryStrategy}.
33+
* Create a new builder for a {@link StandardRetryStrategy}.
3434
*
3535
* <p>Example Usage
36-
* <pre>
36+
* {@snippet
3737
* StandardRetryStrategy retryStrategy =
38-
* RetryStrategies.adaptiveStrategyBuilder()
38+
* DefaultRetryStrategy.standardStrategyBuilder()
3939
* .retryOnExceptionInstanceOf(IllegalArgumentException.class)
4040
* .retryOnExceptionInstanceOf(IllegalStateException.class)
4141
* .build();
42-
* </pre>
42+
* }
4343
*/
4444
public static StandardRetryStrategy.Builder standardStrategyBuilder() {
4545
return StandardRetryStrategy.builder()
@@ -48,18 +48,38 @@ public static StandardRetryStrategy.Builder standardStrategyBuilder() {
4848
}
4949

5050
/**
51-
* Create a new builder for a {@code AdaptiveRetryStrategy}.
51+
* Create a new builder for a {@link LegacyRetryStrategy}.
5252
*
5353
* <p>Example Usage
54-
* <pre>
54+
* {@snippet
55+
* LegacyRetryStrategy retryStrategy =
56+
* DefaultRetryStrategy.legacyStrategyBuilder()
57+
* .retryOnExceptionInstanceOf(IllegalArgumentException.class)
58+
* .retryOnExceptionInstanceOf(IllegalStateException.class)
59+
* .build();
60+
* }
61+
*/
62+
public static LegacyRetryStrategy.Builder legacyStrategyBuilder() {
63+
return LegacyRetryStrategy.builder()
64+
.maxAttempts(Legacy.MAX_ATTEMPTS)
65+
.backoffStrategy(BackoffStrategy.exponentialDelay(Legacy.BASE_DELAY, Legacy.MAX_BACKOFF))
66+
.throttlingBackoffStrategy(BackoffStrategy.exponentialDelay(Legacy.THROTTLED_BASE_DELAY,
67+
Legacy.MAX_BACKOFF));
68+
}
69+
70+
/**
71+
* Create a new builder for a {@link AdaptiveRetryStrategy}.
72+
*
73+
* <p>Example Usage
74+
* {@snippet
5575
* AdaptiveRetryStrategy retryStrategy =
56-
* RetryStrategies.adaptiveStrategyBuilder()
76+
* DefaultRetryStrategy.adaptiveStrategyBuilder()
5777
* .retryOnExceptionInstanceOf(IllegalArgumentException.class)
5878
* .retryOnExceptionInstanceOf(IllegalStateException.class)
5979
* .build();
60-
* </pre>
80+
* }
6181
*/
62-
public static AdaptiveRetryStrategy.Builder adaptiveRetryStrategyBuilder() {
82+
public static AdaptiveRetryStrategy.Builder adaptiveStrategyBuilder() {
6383
return AdaptiveRetryStrategy.builder()
6484
.maxAttempts(Adaptive.MAX_ATTEMPTS);
6585
}
@@ -70,9 +90,29 @@ static final class Standard {
7090
static final Duration MAX_BACKOFF = Duration.ofSeconds(20);
7191
static final int TOKEN_BUCKET_SIZE = 500;
7292
static final int DEFAULT_EXCEPTION_TOKEN_COST = 5;
93+
94+
private Standard() {
95+
}
7396
}
7497

7598
static final class Adaptive {
7699
static final int MAX_ATTEMPTS = 3;
100+
101+
private Adaptive() {
102+
}
103+
}
104+
105+
static final class Legacy {
106+
static final int MAX_ATTEMPTS = 4;
107+
static final Duration BASE_DELAY = Duration.ofMillis(100);
108+
static final Duration THROTTLED_BASE_DELAY = Duration.ofMillis(500);
109+
110+
static final Duration MAX_BACKOFF = Duration.ofSeconds(20);
111+
static final int TOKEN_BUCKET_SIZE = 500;
112+
static final int DEFAULT_EXCEPTION_TOKEN_COST = 5;
113+
static final int THROTTLE_EXCEPTION_TOKEN_COST = 0;
114+
115+
private Legacy() {
116+
}
77117
}
78118
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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;
17+
18+
import java.util.function.Predicate;
19+
import software.amazon.awssdk.annotations.SdkPublicApi;
20+
import software.amazon.awssdk.annotations.ThreadSafe;
21+
import software.amazon.awssdk.retries.api.BackoffStrategy;
22+
import software.amazon.awssdk.retries.api.RetryStrategy;
23+
import software.amazon.awssdk.retries.internal.DefaultLegacyRetryStrategy;
24+
import software.amazon.awssdk.retries.internal.circuitbreaker.TokenBucketStore;
25+
26+
/**
27+
* The legacy retry strategy is a {@link RetryStrategy} for normal use-cases.
28+
* <p>
29+
* The legacy retry strategy by default:
30+
* <ol>
31+
* <li>Retries on the conditions configured in the {@link Builder}.
32+
* <li>Retries 3 times (4 total attempts). Adjust with {@link Builder#maxAttempts(int)}
33+
* <li>For non-throttling exceptions uses the {@link BackoffStrategy#exponentialDelay} backoff strategy, with a base delay
34+
* of 100 milliseconds and max delay of 20 seconds. Adjust with {@link Builder#backoffStrategy}
35+
* <li>For throttling exceptions uses the {@link BackoffStrategy#exponentialDelay} backoff strategy, with a base delay of
36+
* 500 milliseconds and max delay of 20 seconds. Adjust with {@link LegacyRetryStrategy.Builder#throttlingBackoffStrategy}
37+
* <li>Circuit breaking (disabling retries) in the event of high downstream failures across the scope of
38+
* the strategy. The circuit breaking will never prevent a successful first attempt. Adjust with
39+
* {@link Builder#circuitBreakerEnabled}
40+
* <li>The state of the circuit breaker is not affected by throttling exceptions
41+
* </ol>
42+
*
43+
* @see StandardRetryStrategy
44+
* @see AdaptiveRetryStrategy
45+
*/
46+
@SdkPublicApi
47+
@ThreadSafe
48+
public interface LegacyRetryStrategy extends RetryStrategy<LegacyRetryStrategy.Builder, LegacyRetryStrategy> {
49+
/**
50+
* Create a new {@link LegacyRetryStrategy.Builder}.
51+
*
52+
* <p>Example Usage
53+
* <pre>
54+
* LegacyRetryStrategy retryStrategy =
55+
* LegacyRetryStrategy.builder()
56+
* .retryOnExceptionInstanceOf(IllegalArgumentException.class)
57+
* .retryOnExceptionInstanceOf(IllegalStateException.class)
58+
* .build();
59+
* </pre>
60+
*/
61+
static Builder builder() {
62+
return DefaultLegacyRetryStrategy
63+
.builder()
64+
.maxAttempts(DefaultRetryStrategy.Legacy.MAX_ATTEMPTS)
65+
.tokenBucketStore(TokenBucketStore
66+
.builder()
67+
.tokenBucketMaxCapacity(DefaultRetryStrategy.Legacy.TOKEN_BUCKET_SIZE)
68+
.build())
69+
.tokenBucketExceptionCost(DefaultRetryStrategy.Legacy.DEFAULT_EXCEPTION_TOKEN_COST)
70+
.tokenBucketThrottlingExceptionCost(DefaultRetryStrategy.Legacy.THROTTLE_EXCEPTION_TOKEN_COST);
71+
}
72+
73+
@Override
74+
Builder toBuilder();
75+
76+
interface Builder extends RetryStrategy.Builder<Builder, LegacyRetryStrategy> {
77+
/**
78+
* Configure the backoff strategy used by this strategy.
79+
*
80+
* <p>By default, this uses jittered exponential backoff.
81+
*/
82+
Builder backoffStrategy(BackoffStrategy backoffStrategy);
83+
84+
/**
85+
* Configure the backoff strategy used for throttling exceptions by this strategy.
86+
*
87+
* <p>By default, this uses jittered exponential backoff.
88+
*/
89+
Builder throttlingBackoffStrategy(BackoffStrategy throttlingBackoffStrategy);
90+
91+
/**
92+
* Whether circuit breaking is enabled for this strategy.
93+
*
94+
* <p>The circuit breaker will prevent attempts (even below the {@link #maxAttempts(int)}) if a large number of
95+
* failures are observed by this executor.
96+
*
97+
* <p>Note: The circuit breaker scope is local to the created {@link RetryStrategy},
98+
* and will therefore not be effective unless the {@link RetryStrategy} is used for more than one call. It's recommended
99+
* that a {@link RetryStrategy} be reused for all calls to a single unreliable resource. It's also recommended that
100+
* separate {@link RetryStrategy}s be used for calls to unrelated resources.
101+
*
102+
* <p>By default, this is {@code true}.
103+
*/
104+
Builder circuitBreakerEnabled(Boolean circuitBreakerEnabled);
105+
106+
/**
107+
* Configure the predicate to allow the strategy categorize a Throwable as throttling exception.
108+
*/
109+
Builder treatAsThrottling(Predicate<Throwable> treatAsThrottling);
110+
111+
@Override
112+
LegacyRetryStrategy build();
113+
}
114+
}

core/retries/src/main/java/software/amazon/awssdk/retries/StandardRetryStrategy.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import software.amazon.awssdk.annotations.ThreadSafe;
2020
import software.amazon.awssdk.retries.api.BackoffStrategy;
2121
import software.amazon.awssdk.retries.api.RetryStrategy;
22-
import software.amazon.awssdk.retries.internal.StandardRetryStrategyImpl;
22+
import software.amazon.awssdk.retries.internal.DefaultStandardRetryStrategy;
2323
import software.amazon.awssdk.retries.internal.circuitbreaker.TokenBucketStore;
2424

2525
/**
@@ -56,14 +56,14 @@ public interface StandardRetryStrategy extends RetryStrategy<StandardRetryStrate
5656
* </pre>
5757
*/
5858
static Builder builder() {
59-
return StandardRetryStrategyImpl
59+
return DefaultStandardRetryStrategy
6060
.builder()
6161
.maxAttempts(DefaultRetryStrategy.Standard.MAX_ATTEMPTS)
6262
.tokenBucketStore(TokenBucketStore
6363
.builder()
6464
.tokenBucketMaxCapacity(DefaultRetryStrategy.Standard.TOKEN_BUCKET_SIZE)
6565
.build())
66-
.tokenBucketExceptionCost(DefaultRetryStrategy.Standard.TOKEN_BUCKET_SIZE);
66+
.tokenBucketExceptionCost(DefaultRetryStrategy.Standard.DEFAULT_EXCEPTION_TOKEN_COST);
6767
}
6868

6969
@Override

core/retries/src/main/java/software/amazon/awssdk/retries/internal/AdaptiveRetryStrategyImpl.java renamed to core/retries/src/main/java/software/amazon/awssdk/retries/internal/DefaultAdaptiveRetryStrategy.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@
4747
* Implementation of the {@link AdaptiveRetryStrategy} interface.
4848
*/
4949
@SdkInternalApi
50-
public final class AdaptiveRetryStrategyImpl implements AdaptiveRetryStrategy {
51-
private static final Logger LOG = Logger.loggerFor(AdaptiveRetryStrategyImpl.class);
50+
public final class DefaultAdaptiveRetryStrategy implements AdaptiveRetryStrategy {
51+
private static final Logger LOG = Logger.loggerFor(DefaultAdaptiveRetryStrategy.class);
5252
private final List<Predicate<Throwable>> retryPredicates;
5353
private final int maxAttempts;
5454
private final boolean circuitBreakerEnabled;
@@ -59,10 +59,10 @@ public final class AdaptiveRetryStrategyImpl implements AdaptiveRetryStrategy {
5959
private final TokenBucketStore tokenBucketStore;
6060
private final RateLimiterTokenBucketStore rateLimiterTokenBucketStore;
6161

62-
private AdaptiveRetryStrategyImpl(Builder builder) {
62+
private DefaultAdaptiveRetryStrategy(Builder builder) {
6363
this.retryPredicates = Collections.unmodifiableList(Validate.paramNotNull(builder.retryPredicates, "retryPredicates"));
6464
this.maxAttempts = Validate.isPositive(builder.maxAttempts, "maxAttempts");
65-
this.circuitBreakerEnabled = builder.circuitBreakerEnabled;
65+
this.circuitBreakerEnabled = builder.circuitBreakerEnabled == null || builder.circuitBreakerEnabled;
6666
this.backoffStrategy = Validate.paramNotNull(builder.backoffStrategy, "backoffStrategy");
6767
this.exceptionCost = builder.exceptionCost;
6868
this.tokenBucketMaxCapacity = builder.tokenBucketMaxCapacity;
@@ -293,6 +293,9 @@ static DefaultRetryToken asStandardRetryToken(RetryToken token) {
293293

294294
private AcquireResponse requestAcquireCapacity(DefaultRetryToken token) {
295295
TokenBucket tokenBucket = tokenBucketStore.tokenBucketForScope(token.scope());
296+
if (!circuitBreakerEnabled) {
297+
return tokenBucket.tryAcquire(0);
298+
}
296299
return tokenBucket.tryAcquire(exceptionCost);
297300
}
298301

@@ -310,7 +313,7 @@ private DefaultRetryToken refreshToken(RefreshRetryTokenRequest request, Acquire
310313
public static class Builder implements AdaptiveRetryStrategy.Builder {
311314
private List<Predicate<Throwable>> retryPredicates;
312315
private int maxAttempts;
313-
private boolean circuitBreakerEnabled;
316+
private Boolean circuitBreakerEnabled;
314317
private int tokenBucketMaxCapacity;
315318
private int exceptionCost;
316319
private Predicate<Throwable> treatAsThrottling;
@@ -322,7 +325,7 @@ public static class Builder implements AdaptiveRetryStrategy.Builder {
322325
retryPredicates = new ArrayList<>();
323326
}
324327

325-
Builder(AdaptiveRetryStrategyImpl strategy) {
328+
Builder(DefaultAdaptiveRetryStrategy strategy) {
326329
this.retryPredicates = new ArrayList<>(strategy.retryPredicates);
327330
this.maxAttempts = strategy.maxAttempts;
328331
this.circuitBreakerEnabled = strategy.circuitBreakerEnabled;
@@ -372,9 +375,14 @@ public Builder tokenBucketExceptionCost(int exceptionCost) {
372375
return this;
373376
}
374377

378+
public Builder circuitBreakerEnabled(Boolean circuitBreakerEnabled) {
379+
this.circuitBreakerEnabled = circuitBreakerEnabled;
380+
return this;
381+
}
382+
375383
@Override
376-
public AdaptiveRetryStrategyImpl build() {
377-
return new AdaptiveRetryStrategyImpl(this);
384+
public DefaultAdaptiveRetryStrategy build() {
385+
return new DefaultAdaptiveRetryStrategy(this);
378386
}
379387
}
380388
}

0 commit comments

Comments
 (0)