Skip to content

Add new BackoffStrategy in waiters #5376

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

Merged
merged 10 commits into from
Jul 18, 2024
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
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSSDKforJavav2-be10b35.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Make Waiters use the new Backoff Strategy"
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,10 @@
import software.amazon.awssdk.codegen.poet.PoetUtils;
import software.amazon.awssdk.core.ApiName;
import software.amazon.awssdk.core.internal.waiters.WaiterAttribute;
import software.amazon.awssdk.core.retry.backoff.BackoffStrategy;
import software.amazon.awssdk.core.retry.backoff.FixedDelayBackoffStrategy;
import software.amazon.awssdk.core.waiters.WaiterAcceptor;
import software.amazon.awssdk.core.waiters.WaiterOverrideConfiguration;
import software.amazon.awssdk.core.waiters.WaiterState;
import software.amazon.awssdk.retries.api.BackoffStrategy;
import software.amazon.awssdk.utils.AttributeMap;
import software.amazon.awssdk.utils.SdkAutoCloseable;

Expand Down Expand Up @@ -203,16 +202,17 @@ private MethodSpec waiterConfigInitializer(String waiterKey, WaiterDefinition wa
+ ".orElse($L)",
waiterDefinition.getMaxAttempts());
configMethod.addStatement("$T backoffStrategy = optionalOverrideConfig."
+ "flatMap(WaiterOverrideConfiguration::backoffStrategy).orElse($T.create($T.ofSeconds($L)))",
+ "flatMap(WaiterOverrideConfiguration::backoffStrategyV2)"
+ ".orElse($T.fixedDelayWithoutJitter($T.ofSeconds($L)))",
BackoffStrategy.class,
BackoffStrategy.class,
FixedDelayBackoffStrategy.class,
Duration.class,
waiterDefinition.getDelay());
configMethod.addStatement("$T waitTimeout = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::waitTimeout)"
+ ".orElse(null)",
Duration.class);

configMethod.addStatement("return WaiterOverrideConfiguration.builder().maxAttempts(maxAttempts).backoffStrategy"
configMethod.addStatement("return WaiterOverrideConfiguration.builder().maxAttempts(maxAttempts).backoffStrategyV2"
+ "(backoffStrategy).waitTimeout(waitTimeout).build()");
return configMethod.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
import software.amazon.awssdk.awscore.exception.AwsServiceException;
import software.amazon.awssdk.core.ApiName;
import software.amazon.awssdk.core.internal.waiters.WaiterAttribute;
import software.amazon.awssdk.core.retry.backoff.BackoffStrategy;
import software.amazon.awssdk.core.retry.backoff.FixedDelayBackoffStrategy;
import software.amazon.awssdk.core.waiters.AsyncWaiter;
import software.amazon.awssdk.core.waiters.WaiterAcceptor;
import software.amazon.awssdk.core.waiters.WaiterOverrideConfiguration;
import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.core.waiters.WaiterState;
import software.amazon.awssdk.retries.api.BackoffStrategy;
import software.amazon.awssdk.services.query.QueryAsyncClient;
import software.amazon.awssdk.services.query.jmespath.internal.JmesPathRuntime;
import software.amazon.awssdk.services.query.model.APostOperationRequest;
Expand Down Expand Up @@ -108,10 +107,10 @@ private static List<WaiterAcceptor<? super APostOperationResponse>> postOperatio
private static WaiterOverrideConfiguration postOperationSuccessWaiterConfig(WaiterOverrideConfiguration overrideConfig) {
Optional<WaiterOverrideConfiguration> optionalOverrideConfig = Optional.ofNullable(overrideConfig);
int maxAttempts = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::maxAttempts).orElse(40);
BackoffStrategy backoffStrategy = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::backoffStrategy).orElse(
FixedDelayBackoffStrategy.create(Duration.ofSeconds(1)));
BackoffStrategy backoffStrategy = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::backoffStrategyV2).orElse(
BackoffStrategy.fixedDelayWithoutJitter(Duration.ofSeconds(1)));
Duration waitTimeout = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::waitTimeout).orElse(null);
return WaiterOverrideConfiguration.builder().maxAttempts(maxAttempts).backoffStrategy(backoffStrategy)
return WaiterOverrideConfiguration.builder().maxAttempts(maxAttempts).backoffStrategyV2(backoffStrategy)
.waitTimeout(waitTimeout).build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
import software.amazon.awssdk.awscore.exception.AwsServiceException;
import software.amazon.awssdk.core.ApiName;
import software.amazon.awssdk.core.internal.waiters.WaiterAttribute;
import software.amazon.awssdk.core.retry.backoff.BackoffStrategy;
import software.amazon.awssdk.core.retry.backoff.FixedDelayBackoffStrategy;
import software.amazon.awssdk.core.waiters.Waiter;
import software.amazon.awssdk.core.waiters.WaiterAcceptor;
import software.amazon.awssdk.core.waiters.WaiterOverrideConfiguration;
import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.core.waiters.WaiterState;
import software.amazon.awssdk.retries.api.BackoffStrategy;
import software.amazon.awssdk.services.query.QueryClient;
import software.amazon.awssdk.services.query.jmespath.internal.JmesPathRuntime;
import software.amazon.awssdk.services.query.model.APostOperationRequest;
Expand Down Expand Up @@ -90,10 +89,10 @@ private static List<WaiterAcceptor<? super APostOperationResponse>> postOperatio
private static WaiterOverrideConfiguration postOperationSuccessWaiterConfig(WaiterOverrideConfiguration overrideConfig) {
Optional<WaiterOverrideConfiguration> optionalOverrideConfig = Optional.ofNullable(overrideConfig);
int maxAttempts = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::maxAttempts).orElse(40);
BackoffStrategy backoffStrategy = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::backoffStrategy).orElse(
FixedDelayBackoffStrategy.create(Duration.ofSeconds(1)));
BackoffStrategy backoffStrategy = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::backoffStrategyV2).orElse(
BackoffStrategy.fixedDelayWithoutJitter(Duration.ofSeconds(1)));
Duration waitTimeout = optionalOverrideConfig.flatMap(WaiterOverrideConfiguration::waitTimeout).orElse(null);
return WaiterOverrideConfiguration.builder().maxAttempts(maxAttempts).backoffStrategy(backoffStrategy)
return WaiterOverrideConfiguration.builder().maxAttempts(maxAttempts).backoffStrategyV2(backoffStrategy)
.waitTimeout(waitTimeout).build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.core.internal.waiters;

import java.time.Duration;
import java.util.Objects;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.retry.RetryPolicyContext;
import software.amazon.awssdk.retries.api.BackoffStrategy;

@SdkInternalApi
public class LegacyToNonLegacyAdapter implements BackoffStrategy {
private final software.amazon.awssdk.core.retry.backoff.BackoffStrategy adaptee;

public LegacyToNonLegacyAdapter(software.amazon.awssdk.core.retry.backoff.BackoffStrategy adaptee) {
this.adaptee = Objects.requireNonNull(adaptee);
}

@Override
public Duration computeDelay(int attempt) {
return adaptee.computeDelayBeforeNextRetry(RetryPolicyContext.builder()
.retriesAttempted(attempt - 2)
.build());
}

public software.amazon.awssdk.core.retry.backoff.BackoffStrategy adaptee() {
return adaptee;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.core.internal.waiters;

import java.time.Duration;
import java.util.Objects;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.retry.RetryPolicyContext;
import software.amazon.awssdk.retries.api.BackoffStrategy;

@SdkInternalApi
public class NonLegacyToLegacyAdapter implements software.amazon.awssdk.core.retry.backoff.BackoffStrategy {
private final BackoffStrategy adaptee;

public NonLegacyToLegacyAdapter(BackoffStrategy adaptee) {
this.adaptee = Objects.requireNonNull(adaptee);
}

@Override
public Duration computeDelayBeforeNextRetry(RetryPolicyContext context) {
return adaptee.computeDelay(context.retriesAttempted());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@
import java.time.Duration;
import java.util.Optional;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.retry.backoff.BackoffStrategy;
import software.amazon.awssdk.core.retry.backoff.FixedDelayBackoffStrategy;
import software.amazon.awssdk.core.waiters.WaiterOverrideConfiguration;
import software.amazon.awssdk.retries.api.BackoffStrategy;

/**
* Internal waiter configuration class that provides default values if not overridden.
*/
@SdkInternalApi
public final class WaiterConfiguration {
private static final int DEFAULT_MAX_ATTEMPTS = 3;
private static final BackoffStrategy DEFAULT_BACKOFF_STRATEGY = FixedDelayBackoffStrategy.create(Duration.ofSeconds(5));
private static final BackoffStrategy DEFAULT_BACKOFF_STRATEGY =
BackoffStrategy.fixedDelayWithoutJitter(Duration.ofSeconds(5));
private final Integer maxAttempts;
private final BackoffStrategy backoffStrategy;
private final Duration waitTimeout;

public WaiterConfiguration(WaiterOverrideConfiguration overrideConfiguration) {
Optional<WaiterOverrideConfiguration> configuration = Optional.ofNullable(overrideConfiguration);
this.backoffStrategy =
configuration.flatMap(WaiterOverrideConfiguration::backoffStrategy).orElse(DEFAULT_BACKOFF_STRATEGY);
configuration.flatMap(WaiterOverrideConfiguration::backoffStrategyV2).orElse(DEFAULT_BACKOFF_STRATEGY);
this.waitTimeout = configuration.flatMap(WaiterOverrideConfiguration::waitTimeout).orElse(null);
this.maxAttempts = configuration.flatMap(WaiterOverrideConfiguration::maxAttempts).orElse(DEFAULT_MAX_ATTEMPTS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@
import java.util.Optional;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.retry.RetryPolicyContext;
import software.amazon.awssdk.core.retry.backoff.BackoffStrategy;
import software.amazon.awssdk.core.waiters.WaiterAcceptor;
import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.retries.api.BackoffStrategy;
import software.amazon.awssdk.utils.Either;

/**
Expand Down Expand Up @@ -57,9 +56,13 @@ public Optional<WaiterAcceptor<? super T>> firstWaiterAcceptorIfMatched(Either<T
}

public long computeNextDelayInMills(int attemptNumber) {
return backoffStrategy.computeDelayBeforeNextRetry(RetryPolicyContext.builder()
.retriesAttempted(attemptNumber)
.build())
// This API used originally the legacy BackoffStrategies.
// The new retries-API backoff strategies work with attempts whereas
// the legacies backoff strategies work with retries, attempts is
// equals to retries + 1. Added to that, the new backoff strategies
// expect the attempt count to be incremented before computing the delay,
// therefore we add here + 2 to account for these differences.
return backoffStrategy.computeDelay(attemptNumber + 2)
.toMillis();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@
import java.util.Objects;
import java.util.Optional;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.core.retry.backoff.BackoffStrategy;
import software.amazon.awssdk.core.internal.waiters.LegacyToNonLegacyAdapter;
import software.amazon.awssdk.core.internal.waiters.NonLegacyToLegacyAdapter;
import software.amazon.awssdk.retries.api.BackoffStrategy;
import software.amazon.awssdk.utils.ToString;
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;

/**
* Configuration values for the {@link Waiter}. All values are optional, and the default values will be used
* if they are not specified.
* Configuration values for the {@link Waiter}. All values are optional, and the default values will be used if they are not
* specified.
*/
@SdkPublicApi
public final class WaiterOverrideConfiguration implements ToCopyableBuilder<WaiterOverrideConfiguration.Builder,
Expand All @@ -39,8 +41,16 @@ public final class WaiterOverrideConfiguration implements ToCopyableBuilder<Wait

public WaiterOverrideConfiguration(Builder builder) {
this.maxAttempts = Validate.isPositiveOrNull(builder.maxAttempts, "maxAttempts");
this.backoffStrategy = builder.backoffStrategy;
this.waitTimeout = Validate.isPositiveOrNull(builder.waitTimeout, "waitTimeout");
Validate.mutuallyExclusive("Only one of backoffStrategy or backoffStrategyV2 may be used, but both where defined",
builder.backoffStrategy, builder.backoffStrategyV2);
if (builder.backoffStrategyV2 != null) {
this.backoffStrategy = builder.backoffStrategyV2;
} else if (builder.backoffStrategy != null) {
this.backoffStrategy = new LegacyToNonLegacyAdapter(builder.backoffStrategy);
} else {
this.backoffStrategy = null;
}
}

public static Builder builder() {
Expand All @@ -54,16 +64,30 @@ public Optional<Integer> maxAttempts() {
return Optional.ofNullable(maxAttempts);
}

/**
* @return the optional {@link software.amazon.awssdk.core.retry.backoff.BackoffStrategy} that should be used when polling the
* resource
* @deprecated Use instead {@link #backoffStrategyV2()}
*/
public Optional<software.amazon.awssdk.core.retry.backoff.BackoffStrategy> backoffStrategy() {
if (backoffStrategy == null) {
return Optional.empty();
}
if (backoffStrategy instanceof LegacyToNonLegacyAdapter) {
return Optional.of(((LegacyToNonLegacyAdapter) backoffStrategy).adaptee());
}
return Optional.of(new NonLegacyToLegacyAdapter(backoffStrategy));
}

/**
* @return the optional {@link BackoffStrategy} that should be used when polling the resource
*/
public Optional<BackoffStrategy> backoffStrategy() {
public Optional<BackoffStrategy> backoffStrategyV2() {
return Optional.ofNullable(backoffStrategy);
}

/**
* @return the optional amount of time to wait that should be used when polling the resource
*
*/
public Optional<Duration> waitTimeout() {
return Optional.ofNullable(waitTimeout);
Expand All @@ -72,7 +96,8 @@ public Optional<Duration> waitTimeout() {
@Override
public Builder toBuilder() {
return new Builder().maxAttempts(maxAttempts)
.backoffStrategy(backoffStrategy)
.backoffStrategyV2(backoffStrategy)
.backoffStrategy(null)
.waitTimeout(waitTimeout);
}

Expand Down Expand Up @@ -115,24 +140,39 @@ public String toString() {

public static final class Builder implements CopyableBuilder<WaiterOverrideConfiguration.Builder,
WaiterOverrideConfiguration> {
private BackoffStrategy backoffStrategy;
private Integer maxAttempts;
private Duration waitTimeout;
private software.amazon.awssdk.core.retry.backoff.BackoffStrategy backoffStrategy;
private BackoffStrategy backoffStrategyV2;

private Builder() {
}

/**
* Define the {@link BackoffStrategy} that computes the delay before the next retry request.
* Define the {@link software.amazon.awssdk.core.retry.backoff.BackoffStrategy} that computes the delay between resource
* polling. Only one of {@link Builder#backoffStrategy()} and {@link Builder#backoffStrategyV2()} may be defined.
*
* @param backoffStrategy The new backoffStrategy value.
* @return This object for method chaining.
* @deprecated Use instead {@link #backoffStrategyV2(BackoffStrategy)}
*/
public Builder backoffStrategy(BackoffStrategy backoffStrategy) {
public Builder backoffStrategy(software.amazon.awssdk.core.retry.backoff.BackoffStrategy backoffStrategy) {
this.backoffStrategy = backoffStrategy;
return this;
}

/**
* Define the {@link BackoffStrategy} that computes the delay between resource polling. Only one of
* {@link Builder#backoffStrategy()} and {@link Builder#backoffStrategyV2()} may be defined.
*
* @param backoffStrategy The new backoffStrategy value.
* @return This object for method chaining.
*/
public Builder backoffStrategyV2(BackoffStrategy backoffStrategy) {
this.backoffStrategyV2 = backoffStrategy;
return this;
}

/**
* Define the maximum number of attempts to try before transitioning the waiter to a failure state.
*
Expand All @@ -145,10 +185,9 @@ public Builder maxAttempts(Integer maxAttempts) {
}

/**
* Define the amount of time to wait for the resource to transition to the desired state before
* timing out. This wait timeout doesn't have strict guarantees on how quickly a request is aborted
* when the timeout is breached. The request can timeout early if it is determined that the next
* retry will breach the max wait time. It's disabled by default.
* Define the amount of time to wait for the resource to transition to the desired state before timing out. This wait
* timeout doesn't have strict guarantees on how quickly a request is aborted when the timeout is breached. The request
* can timeout early if it is determined that the next retry will breach the max wait time. It's disabled by default.
*
* @param waitTimeout The new waitTimeout value.
* @return This object for method chaining.
Expand All @@ -163,4 +202,5 @@ public WaiterOverrideConfiguration build() {
return new WaiterOverrideConfiguration(this);
}
}

}
Loading
Loading