Skip to content

Add standard retry strategy #3931

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
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
1 change: 1 addition & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<module>endpoints-spi</module>
<module>imds</module>
<module>retries-api</module>
<module>retries</module>
</modules>

<dependencyManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.retries.api.internal.AcquireInitialTokenRequestImpl;

/**
* Encapsulates the abstract scope to start the attempts about to be executed using a retry strategy.
Expand All @@ -33,4 +34,11 @@ public interface AcquireInitialTokenRequest {
* requests against one resource do not result in throttling for requests against other, unrelated resources.
*/
String scope();

/**
* Creates a new {@link AcquireInitialTokenRequest} instance with the given scope.
*/
static AcquireInitialTokenRequest create(String scope) {
return AcquireInitialTokenRequestImpl.create(scope);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.time.Duration;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.retries.api.internal.AcquireInitialTokenResponseImpl;

/**
* Encapsulates the response from the {@link RetryStrategy} to the request to start the attempts to be executed.
Expand All @@ -35,4 +36,11 @@ public interface AcquireInitialTokenResponse {
* The amount of time to wait before performing the first attempt.
*/
Duration delay();

/**
* Creates a new {@link AcquireInitialTokenRequest} instance with the given scope.
*/
static AcquireInitialTokenResponse create(RetryToken token, Duration delay) {
return AcquireInitialTokenResponseImpl.create(token, delay);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.retries.api.internal.RecordSuccessRequestImpl;

/**
* Request that the calling code makes to the {@link RetryStrategy} using
Expand All @@ -30,4 +31,12 @@ public interface RecordSuccessRequest {
* {@link RetryStrategy#refreshRetryToken} call.
*/
RetryToken token();

/**
* Creates a new {@link RecordSuccessRequest} instance with the given token.
*/
static RecordSuccessRequest create(RetryToken token) {
return RecordSuccessRequestImpl.create(token);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.retries.api.internal.RecordSuccessResponseImpl;

/**
* Response given to the calling code by the {@link RetryStrategy} after calling
Expand All @@ -30,4 +31,12 @@ public interface RecordSuccessResponse {
* {@link RetryStrategy#refreshRetryToken} call.
*/
RetryToken token();

/**
* Creates a new {@link RecordSuccessResponseImpl} with the given token.
*/
static RecordSuccessResponse create(RetryToken token) {
return RecordSuccessResponseImpl.create(token);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import java.util.Optional;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.retries.api.internal.RefreshRetryTokenRequestImpl;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;

/**
* Request that the calling code makes to the {@link RetryStrategy} using
Expand All @@ -27,7 +30,7 @@
*/
@SdkPublicApi
@ThreadSafe
public interface RefreshRetryTokenRequest {
public interface RefreshRetryTokenRequest extends ToCopyableBuilder<RefreshRetryTokenRequest.Builder, RefreshRetryTokenRequest> {
/**
* A {@link RetryToken} acquired a previous {@link RetryStrategy#acquireInitialToken} or
* {@link RetryStrategy#refreshRetryToken} call.
Expand All @@ -44,4 +47,33 @@ public interface RefreshRetryTokenRequest {
* The cause of the last attempt failure.
*/
Throwable failure();

/**
* Returns a new builder to configure the {@link RefreshRetryTokenRequest} instance.
*/
static Builder builder() {
return RefreshRetryTokenRequestImpl.builder();
}

interface Builder extends CopyableBuilder<Builder, RefreshRetryTokenRequest> {
/**
* Configures the {@link RetryToken} to be refreshed.
*/
Builder token(RetryToken token);

/**
* Configures the suggested delay to used when refreshing the token.
*/
Builder suggestedDelay(Duration duration);

/**
* Configures the latest caught exception.
*/
Builder failure(Throwable throwable);

/**
* Builds and returns a new instance of {@linke RefreshRetryTokenRequest}.
*/
RefreshRetryTokenRequest build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.time.Duration;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.retries.api.internal.RefreshRetryTokenResponseImpl;

/**
* Response from the {@link RetryStrategy} after calling {@link RetryStrategy#refreshRetryToken(RefreshRetryTokenRequest)}.
Expand All @@ -35,4 +36,11 @@ public interface RefreshRetryTokenResponse {
* The amount of time to wait before performing the next attempt.
*/
Duration delay();

/**
* Creates a new {@link RefreshRetryTokenResponse} with the given token and delay.
*/
static RefreshRetryTokenResponse create(RetryToken token, Duration delay) {
return RefreshRetryTokenResponseImpl.create(token, delay);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@
*/
@ThreadSafe
@SdkPublicApi
public interface RetryStrategy extends ToCopyableBuilder<RetryStrategy.Builder, RetryStrategy> {
public interface RetryStrategy<
B extends CopyableBuilder<B, T> & RetryStrategy.Builder<B, T>,
T extends ToCopyableBuilder<B, T> & RetryStrategy<B, T>>
extends ToCopyableBuilder<B, T> {
Comment on lines +44 to +47
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this to be able to have to different classes extending from the builder and keep the ability to chain the builder calls.

/**
* Invoked before the first request attempt.
*
Expand Down Expand Up @@ -86,36 +89,39 @@ public interface RetryStrategy extends ToCopyableBuilder<RetryStrategy.Builder,
* <p>This is useful for modifying the strategy's behavior, like conditions or max retries.
*/
@Override
Builder toBuilder();
B toBuilder();

/**
* Builder to create immutable instances of {@link RetryStrategy}.
*/
interface Builder extends CopyableBuilder<Builder, RetryStrategy> {
interface Builder<
B extends Builder<B, T> & CopyableBuilder<B, T>,
T extends ToCopyableBuilder<B, T> & RetryStrategy<B, T>>
extends CopyableBuilder<B, T> {
/**
* Configure the strategy to retry when the provided predicate returns true, given a failure exception.
*/
Builder retryOnException(Predicate<Throwable> shouldRetry);
B retryOnException(Predicate<Throwable> shouldRetry);

/**
* Configure the strategy to retry when a failure exception class is equal to the provided class.
*/
default Builder retryOnException(Class<? extends Throwable> throwable) {
default B retryOnException(Class<? extends Throwable> throwable) {
return retryOnException(t -> t.getClass() == throwable);
}

/**
* Configure the strategy to retry when a failure exception class is an instance of the provided class (includes
* subtypes).
*/
default Builder retryOnExceptionInstanceOf(Class<? extends Throwable> throwable) {
default B retryOnExceptionInstanceOf(Class<? extends Throwable> throwable) {
return retryOnException(t -> throwable.isAssignableFrom(t.getClass()));
}

/**
* Configure the strategy to retry when a failure exception or one of its cause classes is equal to the provided class.
*/
default Builder retryOnExceptionOrCause(Class<? extends Throwable> throwable) {
default B retryOnExceptionOrCause(Class<? extends Throwable> throwable) {
return retryOnException(t -> {
if (t.getClass() == throwable) {
return true;
Expand All @@ -135,7 +141,7 @@ default Builder retryOnExceptionOrCause(Class<? extends Throwable> throwable) {
* Configure the strategy to retry when a failure exception or one of its cause classes is an instance of the provided
* class (includes subtypes).
*/
default Builder retryOnExceptionOrCauseInstanceOf(Class<? extends Throwable> throwable) {
default B retryOnExceptionOrCauseInstanceOf(Class<? extends Throwable> throwable) {
return retryOnException(t -> {
if (throwable.isAssignableFrom(t.getClass())) {
return true;
Expand All @@ -155,7 +161,7 @@ default Builder retryOnExceptionOrCauseInstanceOf(Class<? extends Throwable> thr
* Configure the strategy to retry the root cause of a failure (the final cause) a failure exception is equal to the
* provided class.
*/
default Builder retryOnRootCause(Class<? extends Throwable> throwable) {
default B retryOnRootCause(Class<? extends Throwable> throwable) {
return retryOnException(t -> {
boolean shouldRetry = false;
Throwable cause = t.getCause();
Expand All @@ -171,7 +177,7 @@ default Builder retryOnRootCause(Class<? extends Throwable> throwable) {
* Configure the strategy to retry the root cause of a failure (the final cause) a failure exception is an instance of to
* the provided class (includes subtypes).
*/
default Builder retryOnRootCauseInstanceOf(Class<? extends Throwable> throwable) {
default B retryOnRootCauseInstanceOf(Class<? extends Throwable> throwable) {
return retryOnException(t -> {
boolean shouldRetry = false;
Throwable cause = t.getCause();
Expand All @@ -191,17 +197,12 @@ default Builder retryOnRootCauseInstanceOf(Class<? extends Throwable> throwable)
*
* <p>The default value for the standard and adaptive retry strategies is 3.
*/
Builder maxAttempts(int maxAttempts);

/**
* Configure the predicate to allow the strategy categorize a Throwable as throttling exception.
*/
Builder treatAsThrottling(Predicate<Throwable> treatAsThrottling);
Comment on lines -194 to -199
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only needed for AdaptiveRetryStrategy and LegacyRetryStrategy. I will move to the corresponding interfaces instead.

B maxAttempts(int maxAttempts);

/**
* Build a new {@link RetryStrategy} with the current configuration on this builder.
*/
@Override
RetryStrategy build();
T build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.retries.api.internal;

import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.retries.api.AcquireInitialTokenRequest;
import software.amazon.awssdk.utils.Validate;

/**
* Implementation of the {@link AcquireInitialTokenRequest} interface.
*/
@SdkInternalApi
public final class AcquireInitialTokenRequestImpl implements AcquireInitialTokenRequest {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this implementation here in the SPI package as decided in a previous review. See the decision log here.


private final String scope;

private AcquireInitialTokenRequestImpl(String scope) {
this.scope = Validate.paramNotNull(scope, "scope");
}

@Override
public String scope() {
return scope;
}

/**
* Creates a new {@link AcquireInitialTokenRequestImpl} instance with the given scope.
*/
public static AcquireInitialTokenRequest create(String scope) {
return new AcquireInitialTokenRequestImpl(scope);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.retries.api.internal;

import java.time.Duration;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.retries.api.AcquireInitialTokenResponse;
import software.amazon.awssdk.retries.api.RetryToken;
import software.amazon.awssdk.utils.Validate;

/**
* Implementation of the {@link AcquireInitialTokenResponse} interface.
*/
@SdkInternalApi
public final class AcquireInitialTokenResponseImpl implements AcquireInitialTokenResponse {
private final RetryToken token;
private final Duration delay;

private AcquireInitialTokenResponseImpl(RetryToken token, Duration delay) {
this.token = Validate.paramNotNull(token, "token");
this.delay = Validate.paramNotNull(delay, "delay");
}

@Override
public RetryToken token() {
return token;
}

@Override
public Duration delay() {
return delay;
}

/**
* Creates a new {@link AcquireInitialTokenResponseImpl} instance with the given token and suggested delay values.
*/
public static AcquireInitialTokenResponse create(RetryToken token, Duration delay) {
return new AcquireInitialTokenResponseImpl(token, delay);
}
}
Loading