Skip to content
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 @@ -380,6 +380,13 @@ static Duration maxOf(Duration left, Duration right) {
return right;
}

static Duration minOf(Duration left, Duration right) {
if (left.compareTo(right) <= 0) {
return left;
}
return right;
}

static DefaultRetryToken asDefaultRetryToken(RetryToken token) {
return Validate.isInstanceOf(DefaultRetryToken.class, token,
"RetryToken is of unexpected class (%s), "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package software.amazon.awssdk.retries.internal;

import java.time.Duration;
import java.util.Optional;
import java.util.function.Predicate;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.retries.StandardRetryStrategy;
Expand All @@ -28,6 +29,7 @@
public final class DefaultStandardRetryStrategy
extends BaseRetryStrategy implements StandardRetryStrategy {
private static final Logger LOG = Logger.loggerFor(DefaultStandardRetryStrategy.class);
private static final Duration FIVE_SECONDS = Duration.ofSeconds(5);
private final boolean retries2026Enabled;

DefaultStandardRetryStrategy(Builder builder) {
Expand Down Expand Up @@ -56,6 +58,38 @@ protected Duration computeAcquireFailureBackoff(RefreshRetryTokenRequest request
return computeBackoff(request, attemptIncremented);
}

@Override
protected Duration computeBackoff(RefreshRetryTokenRequest request, DefaultRetryToken token) {
if (!retries2026Enabled) {
return super.computeBackoff(request, token);
}

Duration strategyBackoff;
if (treatAsThrottling.test(request.failure())) {
strategyBackoff = throttlingBackoffStrategy.computeDelay(token.attempt());
} else {
strategyBackoff = backoffStrategy.computeDelay(token.attempt());
}

Optional<Duration> optionalSuggested = request.suggestedDelay();

if (!optionalSuggested.isPresent()) {
return strategyBackoff;
}

// the suggested delay needs to be at least what the strategy computed, OR
// not greater than 5s more than what the strat computed
Duration minBackoff = strategyBackoff;
Duration maxBackoff = strategyBackoff.plus(FIVE_SECONDS);

Duration backoff = optionalSuggested.get();

backoff = maxOf(minBackoff, backoff);
backoff = minOf(maxBackoff, backoff);

return backoff;
}

public static class Builder extends BaseRetryStrategy.Builder implements StandardRetryStrategy.Builder {
private boolean retries2026Enabled;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,17 @@
case RETRY_REQUEST: {
ScenarioTestException scenarioTestException = new ScenarioTestException(response.statusCode,
response.throttling);
RefreshRetryTokenRequest refreshRequest = RefreshRetryTokenRequest.builder()
.failure(scenarioTestException)
.isLongPolling(given.isLongPolling)
.token(token.get())
.build();
RefreshRetryTokenResponse refreshResponse = strategy.refreshRetryToken(refreshRequest);
RefreshRetryTokenRequest.Builder refreshRequest = RefreshRetryTokenRequest.builder();

if (response.xAmzRetryAfter != null) {
refreshRequest.suggestedDelay(response.xAmzRetryAfter);
}

refreshRequest.failure(scenarioTestException)
.isLongPolling(given.isLongPolling)
.token(token.get())
.build();
RefreshRetryTokenResponse refreshResponse = strategy.refreshRetryToken(refreshRequest.build());
DefaultRetryToken refreshedToken = (DefaultRetryToken) refreshResponse.token();
token.set(refreshedToken);

Expand All @@ -120,14 +125,18 @@
case RETRY_QUOTA_EXCEEDED: {
ScenarioTestException scenarioTestException = new ScenarioTestException(response.statusCode,
response.throttling);
RefreshRetryTokenRequest refreshRequest = RefreshRetryTokenRequest.builder()
.failure(scenarioTestException)
.isLongPolling(given.isLongPolling)
.token(token.get())
.build();
RefreshRetryTokenRequest.Builder refreshRequest = RefreshRetryTokenRequest.builder();

if (response.xAmzRetryAfter != null) {
refreshRequest.suggestedDelay(response.xAmzRetryAfter);
}

refreshRequest.failure(scenarioTestException)
.isLongPolling(given.isLongPolling)
.token(token.get())
.build();

assertThatThrownBy(() -> strategy.refreshRetryToken(refreshRequest))
assertThatThrownBy(() -> strategy.refreshRetryToken(refreshRequest.build()))

Check warning on line 139 in core/retries/src/test/java/software/amazon/awssdk/retries/internal/StandardRetryStrategyTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor the code of the lambda to have only one invocation possibly throwing a runtime exception.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZ3XnVJRSOt6ravziSXU&open=AZ3XnVJRSOt6ravziSXU&pullRequest=6915
.isInstanceOf(TokenAcquisitionFailedException.class)
.matches(e -> {
TokenAcquisitionFailedException acquireException = (TokenAcquisitionFailedException) e;
Expand All @@ -149,12 +158,18 @@
case MAX_ATTEMPTS_EXCEEDED: {
ScenarioTestException scenarioTestException = new ScenarioTestException(response.statusCode,
response.throttling);
RefreshRetryTokenRequest refreshRequest = RefreshRetryTokenRequest.builder()
.failure(scenarioTestException)
.isLongPolling(given.isLongPolling)
.token(token.get())
.build();
assertThatThrownBy(() -> strategy.refreshRetryToken(refreshRequest))
RefreshRetryTokenRequest.Builder refreshRequest = RefreshRetryTokenRequest.builder();

if (response.xAmzRetryAfter != null) {
refreshRequest.suggestedDelay(response.xAmzRetryAfter);
}

refreshRequest.failure(scenarioTestException)
.isLongPolling(given.isLongPolling)
.token(token.get())
.build();

assertThatThrownBy(() -> strategy.refreshRetryToken(refreshRequest.build()))

Check warning on line 172 in core/retries/src/test/java/software/amazon/awssdk/retries/internal/StandardRetryStrategyTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor the code of the lambda to have only one invocation possibly throwing a runtime exception.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZ3XnVJRSOt6ravziSXV&open=AZ3XnVJRSOt6ravziSXV&pullRequest=6915
.isInstanceOf(TokenAcquisitionFailedException.class)
.matches(e -> {
TokenAcquisitionFailedException acquireException = (TokenAcquisitionFailedException) e;
Expand Down Expand Up @@ -673,7 +688,52 @@
.expected(e ->
e.outcome(Outcome.MAX_ATTEMPTS_EXCEEDED)
.delay(Duration.ZERO)
.retryQuota(486))),

aScenario("Honor x-amz-retry-after Header")
.newRetries2026(true)
.addResponse(r ->
r.statusCode(500)
.xAmzRetryAfter(Duration.ofMillis(1500))
.expected(e ->
e.outcome(Outcome.RETRY_REQUEST)
.delay(Duration.ofMillis(1500))
.retryQuota(486)))
.addResponse(r ->
r.statusCode(200)
.expected(e ->
e.outcome(Outcome.SUCCESS)
.retryQuota(500))),

aScenario("x-amz-retry-after minimum is exponential backoff duration")
.newRetries2026(true)
.addResponse(r ->
r.statusCode(500)
.xAmzRetryAfter(Duration.ofMillis(0))
.expected(e ->
e.outcome(Outcome.RETRY_REQUEST)
.delay(Duration.ofMillis(50))
.retryQuota(486)))
.addResponse(r ->
r.statusCode(200)
.expected(e ->
e.outcome(Outcome.SUCCESS)
.retryQuota(500))),

aScenario("x-amz-retry-after maximum is 5+exponential backoff duration")
.newRetries2026(true)
.addResponse(r ->
r.statusCode(500)
.xAmzRetryAfter(Duration.ofMillis(10000))
.expected(e ->
e.outcome(Outcome.RETRY_REQUEST)
.delay(Duration.ofMillis(5050))
.retryQuota(486)))
.addResponse(r ->
r.statusCode(200)
.expected(e ->
e.outcome(Outcome.SUCCESS)
.retryQuota(500)))
);
}

Expand Down Expand Up @@ -721,6 +781,7 @@
private static class Response {
private int statusCode;
private boolean throttling;
private Duration xAmzRetryAfter;
private Expected expected;

public Response statusCode(int statusCode) {
Expand All @@ -733,6 +794,11 @@
return this;
}

public Response xAmzRetryAfter(Duration xAmzRetryAfter) {
this.xAmzRetryAfter = xAmzRetryAfter;
return this;
}

public Response expected(Consumer<Expected> acceptor) {
this.expected = new Expected();
acceptor.accept(this.expected);
Expand Down
Loading