Skip to content

Commit 87de0fa

Browse files
committed
Support backoff for long-polling
For operations that are long-polling, support backoffs in the Standard retry strategy even there is insufficient retry quota.
1 parent e528aa4 commit 87de0fa

7 files changed

Lines changed: 344 additions & 2 deletions

File tree

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ public interface RefreshRetryTokenRequest extends ToCopyableBuilder<RefreshRetry
4343
*/
4444
Optional<Duration> suggestedDelay();
4545

46+
/**
47+
* Whether the operation that is being retried is a long polling operation. Returns {@code false} by default.
48+
*/
49+
default boolean isLongPolling() {
50+
return false;
51+
}
4652
/**
4753
* The cause of the last attempt failure.
4854
*/
@@ -66,6 +72,13 @@ interface Builder extends CopyableBuilder<Builder, RefreshRetryTokenRequest> {
6672
*/
6773
Builder suggestedDelay(Duration duration);
6874

75+
/**
76+
* Configures whether this refresh request is for a long polling operation. The default implementation is a no-op.
77+
*/
78+
default Builder isLongPolling(boolean isLongPolling) {
79+
return this;
80+
}
81+
6982
/**
7083
* Configures the latest caught exception.
7184
*/

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package software.amazon.awssdk.retries.api;
1717

18+
import java.time.Duration;
19+
import java.util.Optional;
1820
import software.amazon.awssdk.annotations.SdkPublicApi;
1921

2022
/**
@@ -23,13 +25,15 @@
2325
@SdkPublicApi
2426
public final class TokenAcquisitionFailedException extends RuntimeException {
2527
private final transient RetryToken token;
28+
private final transient Duration delay;
2629

2730
/**
2831
* Exception construction accepting message with no root cause.
2932
*/
3033
public TokenAcquisitionFailedException(String msg) {
3134
super(msg);
3235
token = null;
36+
delay = null;
3337
}
3438

3539
/**
@@ -38,6 +42,7 @@ public TokenAcquisitionFailedException(String msg) {
3842
public TokenAcquisitionFailedException(String msg, Throwable cause) {
3943
super(msg, cause);
4044
token = null;
45+
delay = null;
4146
}
4247

4348
/**
@@ -46,6 +51,23 @@ public TokenAcquisitionFailedException(String msg, Throwable cause) {
4651
public TokenAcquisitionFailedException(String msg, RetryToken token, Throwable cause) {
4752
super(msg, cause);
4853
this.token = token;
54+
this.delay = null;
55+
}
56+
57+
/**
58+
* Exception constructor accepting message, retry token, a root cause, and delay.
59+
*/
60+
public TokenAcquisitionFailedException(String msg, RetryToken token, Throwable cause, Duration delay) {
61+
super(msg, cause);
62+
this.token = token;
63+
this.delay = delay;
64+
}
65+
66+
/**
67+
* The amount of time to wait before returning to the caller.
68+
*/
69+
public Optional<Duration> delay() {
70+
return Optional.ofNullable(delay);
4971
}
5072

5173
/**

core/retries-spi/src/main/java/software/amazon/awssdk/retries/api/internal/RefreshRetryTokenRequestImpl.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@
2929
public final class RefreshRetryTokenRequestImpl implements RefreshRetryTokenRequest {
3030
private final RetryToken token;
3131
private final Duration suggestedDelay;
32+
private final boolean isLongPolling;
3233
private final Throwable failure;
3334

3435
private RefreshRetryTokenRequestImpl(Builder builder) {
3536
this.token = Validate.paramNotNull(builder.token, "token");
3637
this.suggestedDelay = Validate.paramNotNull(builder.suggestedDelay, "suggestedDelay");
3738
Validate.isNotNegative(this.suggestedDelay, "suggestedDelay");
39+
this.isLongPolling = builder.isLongPolling;
3840
this.failure = Validate.paramNotNull(builder.failure, "failure");
3941
}
4042

@@ -48,6 +50,11 @@ public Optional<Duration> suggestedDelay() {
4850
return Optional.of(suggestedDelay);
4951
}
5052

53+
@Override
54+
public boolean isLongPolling() {
55+
return isLongPolling;
56+
}
57+
5158
@Override
5259
public Throwable failure() {
5360
return failure;
@@ -68,11 +75,13 @@ public static Builder builder() {
6875
public static final class Builder implements RefreshRetryTokenRequest.Builder {
6976
private RetryToken token;
7077
private Duration suggestedDelay = Duration.ZERO;
78+
private boolean isLongPolling;
7179
private Throwable failure;
7280

7381
Builder(RefreshRetryTokenRequestImpl refreshRetryTokenRequest) {
7482
this.token = refreshRetryTokenRequest.token;
7583
this.suggestedDelay = refreshRetryTokenRequest.suggestedDelay;
84+
this.isLongPolling = refreshRetryTokenRequest.isLongPolling;
7685
this.failure = refreshRetryTokenRequest.failure;
7786
}
7887

@@ -91,6 +100,12 @@ public Builder suggestedDelay(Duration duration) {
91100
return this;
92101
}
93102

103+
@Override
104+
public RefreshRetryTokenRequest.Builder isLongPolling(boolean isLongPolling) {
105+
this.isLongPolling = isLongPolling;
106+
return this;
107+
}
108+
94109
@Override
95110
public Builder failure(Throwable throwable) {
96111
this.failure = throwable;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ static Builder builder(boolean retries2026Enabled) {
7676
: exceptionCost;
7777
return DefaultStandardRetryStrategy
7878
.builder()
79+
.retries2026Enabled(retries2026Enabled)
7980
.maxAttempts(DefaultRetryStrategy.Standard.MAX_ATTEMPTS)
8081
.tokenBucketStore(TokenBucketStore
8182
.builder()

core/retries/src/main/java/software/amazon/awssdk/retries/internal/BaseRetryStrategy.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ protected Duration computeInitialBackoff(AcquireInitialTokenRequest request) {
166166

167167
/**
168168
* Computes the backoff before a retry using the configured backoff strategy. Extending classes can override this method to
169-
* compute different a different depending on their logic.
169+
* compute different a different duration depending on their logic.
170170
*/
171171
protected Duration computeBackoff(RefreshRetryTokenRequest request, DefaultRetryToken token) {
172172
Duration backoff;
@@ -179,6 +179,17 @@ protected Duration computeBackoff(RefreshRetryTokenRequest request, DefaultRetry
179179
return maxOf(suggested, backoff);
180180
}
181181

182+
/**
183+
* Computes the backoff before exiting the retry loop using the configured backoff strategy. Extending classes can override
184+
* this method to compute different a different duration depending on their logic. The default implementation returns
185+
* 0 delay.
186+
*
187+
* @param request The refresh request that failed to acquire sufficient capacity.
188+
*/
189+
protected Duration computeAcquireFailureBackoff(RefreshRetryTokenRequest request) {
190+
return Duration.ZERO;
191+
}
192+
182193
/**
183194
* Called inside {@link #recordSuccess} to allow extending classes to update their internal state after a successful request.
184195
*/
@@ -299,7 +310,8 @@ private void throwOnAcquisitionFailure(RefreshRetryTokenRequest request, Acquire
299310
.build();
300311
String message = acquisitionFailedMessage(acquireResponse);
301312
log.debug(() -> message, failure);
302-
throw new TokenAcquisitionFailedException(message, refreshedToken, failure);
313+
Duration delay = computeAcquireFailureBackoff(request);
314+
throw new TokenAcquisitionFailedException(message, refreshedToken, failure, delay);
303315
}
304316
}
305317

core/retries/src/main/java/software/amazon/awssdk/retries/internal/DefaultStandardRetryStrategy.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,24 @@
1515

1616
package software.amazon.awssdk.retries.internal;
1717

18+
import java.time.Duration;
1819
import java.util.function.Predicate;
1920
import software.amazon.awssdk.annotations.SdkInternalApi;
2021
import software.amazon.awssdk.retries.StandardRetryStrategy;
2122
import software.amazon.awssdk.retries.api.BackoffStrategy;
23+
import software.amazon.awssdk.retries.api.RefreshRetryTokenRequest;
2224
import software.amazon.awssdk.retries.internal.circuitbreaker.TokenBucketStore;
2325
import software.amazon.awssdk.utils.Logger;
2426

2527
@SdkInternalApi
2628
public final class DefaultStandardRetryStrategy
2729
extends BaseRetryStrategy implements StandardRetryStrategy {
2830
private static final Logger LOG = Logger.loggerFor(DefaultStandardRetryStrategy.class);
31+
private final boolean retries2026Enabled;
2932

3033
DefaultStandardRetryStrategy(Builder builder) {
3134
super(LOG, builder);
35+
this.retries2026Enabled = builder.retries2026Enabled;
3236
}
3337

3438
@Override
@@ -40,7 +44,20 @@ public static Builder builder() {
4044
return new Builder();
4145
}
4246

47+
@Override
48+
protected Duration computeAcquireFailureBackoff(RefreshRetryTokenRequest request) {
49+
if (!retries2026Enabled || !request.isLongPolling()) {
50+
return super.computeAcquireFailureBackoff(request);
51+
}
52+
53+
DefaultRetryToken attemptIncremented = asDefaultRetryToken(request.token()).toBuilder()
54+
.increaseAttempt()
55+
.build();
56+
return computeBackoff(request, attemptIncremented);
57+
}
58+
4359
public static class Builder extends BaseRetryStrategy.Builder implements StandardRetryStrategy.Builder {
60+
private boolean retries2026Enabled;
4461

4562
Builder() {
4663
}
@@ -106,6 +123,14 @@ public Builder useClientDefaults(boolean useClientDefaults) {
106123
return this;
107124
}
108125

126+
/**
127+
* Whether retries 2.1 behavior is enabled.
128+
*/
129+
public Builder retries2026Enabled(boolean retries2026Enabled) {
130+
this.retries2026Enabled = retries2026Enabled;
131+
return this;
132+
}
133+
109134
@Override
110135
public StandardRetryStrategy build() {
111136
return new DefaultStandardRetryStrategy(this);

0 commit comments

Comments
 (0)