xref: /aosp_15_r20/external/aws-sdk-java-v2/core/sdk-core/src/main/java/software/amazon/awssdk/core/retry/RetryMode.java (revision 8a52c7834d808308836a99fc2a6e0ed8db339086)
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.core.retry;
17 
18 import java.util.Optional;
19 import java.util.function.Supplier;
20 import software.amazon.awssdk.annotations.SdkPublicApi;
21 import software.amazon.awssdk.core.SdkSystemSetting;
22 import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
23 import software.amazon.awssdk.core.retry.conditions.TokenBucketRetryCondition;
24 import software.amazon.awssdk.profiles.ProfileFile;
25 import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
26 import software.amazon.awssdk.profiles.ProfileProperty;
27 import software.amazon.awssdk.utils.OptionalUtils;
28 import software.amazon.awssdk.utils.StringUtils;
29 
30 /**
31  * A retry mode is a collection of retry behaviors encoded under a single value. For example, the {@link #LEGACY} retry mode will
32  * retry up to three times, and the {@link #STANDARD} will retry up to two times.
33  *
34  * <p>
35  * While the {@link #LEGACY} retry mode is specific to Java, the {@link #STANDARD} retry mode is standardized across all of the
36  * AWS SDKs.
37  *
38  * <p>
39  * The retry mode can be configured:
40  * <ol>
41  *     <li>Directly on a client via {@link ClientOverrideConfiguration.Builder#retryPolicy(RetryMode)}.</li>
42  *     <li>Directly on a client via a combination of {@link RetryPolicy#builder(RetryMode)} or
43  *     {@link RetryPolicy#forRetryMode(RetryMode)}, and {@link ClientOverrideConfiguration.Builder#retryPolicy(RetryPolicy)}</li>
44  *     <li>On a configuration profile via the "retry_mode" profile file property.</li>
45  *     <li>Globally via the "aws.retryMode" system property.</li>
46  *     <li>Globally via the "AWS_RETRY_MODE" environment variable.</li>
47  * </ol>
48  */
49 @SdkPublicApi
50 public enum RetryMode {
51     /**
52      * The LEGACY retry mode, specific to the Java SDK, and characterized by:
53      * <ol>
54      *     <li>Up to 3 retries, or more for services like DynamoDB (which has up to 8).</li>
55      *     <li>Zero token are subtracted from the {@link TokenBucketRetryCondition} when throttling exceptions are encountered.
56      *     </li>
57      * </ol>
58      *
59      * <p>
60      * This is the retry mode that is used when no other mode is configured.
61      */
62     LEGACY,
63 
64 
65     /**
66      * The STANDARD retry mode, shared by all AWS SDK implementations, and characterized by:
67      * <ol>
68      *     <li>Up to 2 retries, regardless of service.</li>
69      *     <li>Throttling exceptions are treated the same as other exceptions for the purposes of the
70      *     {@link TokenBucketRetryCondition}.</li>
71      * </ol>
72      */
73     STANDARD,
74 
75     /**
76      * Adaptive retry mode builds on {@code STANDARD} mode.
77      * <p>
78      * Adaptive retry mode dynamically limits the rate of AWS requests to maximize success rate. This may be at the
79      * expense of request latency. Adaptive retry mode is not recommended when predictable latency is important.
80      * <p>
81      * <b>Warning:</b> Adaptive retry mode assumes that the client is working against a single resource (e.g. one
82      * DynamoDB Table or one S3 Bucket). If you use a single client for multiple resources, throttling or outages
83      * associated with one resource will result in increased latency and failures when accessing all other resources via
84      * the same client. When using adaptive retry mode, we recommend using a single client per resource.
85      *
86      * @see RetryPolicy#isFastFailRateLimiting()
87      */
88     ADAPTIVE,
89 
90     ;
91 
92     /**
93      * Retrieve the default retry mode by consulting the locations described in {@link RetryMode}, or LEGACY if no value is
94      * configured.
95      */
defaultRetryMode()96     public static RetryMode defaultRetryMode() {
97         return resolver().resolve();
98     }
99 
100     /**
101      * Create a {@link Resolver} that allows customizing the variables used during determination of a {@link RetryMode}.
102      */
resolver()103     public static Resolver resolver() {
104         return new Resolver();
105     }
106 
107     /**
108      * Allows customizing the variables used during determination of a {@link RetryMode}. Created via {@link #resolver()}.
109      */
110     public static class Resolver {
111         private static final RetryMode SDK_DEFAULT_RETRY_MODE = LEGACY;
112 
113         private Supplier<ProfileFile> profileFile;
114         private String profileName;
115         private RetryMode defaultRetryMode;
116 
Resolver()117         private Resolver() {
118         }
119 
120         /**
121          * Configure the profile file that should be used when determining the {@link RetryMode}. The supplier is only consulted
122          * if a higher-priority determinant (e.g. environment variables) does not find the setting.
123          */
profileFile(Supplier<ProfileFile> profileFile)124         public Resolver profileFile(Supplier<ProfileFile> profileFile) {
125             this.profileFile = profileFile;
126             return this;
127         }
128 
129         /**
130          * Configure the profile file name should be used when determining the {@link RetryMode}.
131          */
profileName(String profileName)132         public Resolver profileName(String profileName) {
133             this.profileName = profileName;
134             return this;
135         }
136 
137         /**
138          * Configure the {@link RetryMode} that should be used if the mode is not specified anywhere else.
139          */
defaultRetryMode(RetryMode defaultRetryMode)140         public Resolver defaultRetryMode(RetryMode defaultRetryMode) {
141             this.defaultRetryMode = defaultRetryMode;
142             return this;
143         }
144 
145         /**
146          * Resolve which retry mode should be used, based on the configured values.
147          */
resolve()148         public RetryMode resolve() {
149             return OptionalUtils.firstPresent(Resolver.fromSystemSettings(), () -> fromProfileFile(profileFile, profileName))
150                                 .orElseGet(this::fromDefaultMode);
151         }
152 
fromSystemSettings()153         private static Optional<RetryMode> fromSystemSettings() {
154             return SdkSystemSetting.AWS_RETRY_MODE.getStringValue()
155                                                   .flatMap(Resolver::fromString);
156         }
157 
fromProfileFile(Supplier<ProfileFile> profileFile, String profileName)158         private static Optional<RetryMode> fromProfileFile(Supplier<ProfileFile> profileFile, String profileName) {
159             profileFile = profileFile != null ? profileFile : ProfileFile::defaultProfileFile;
160             profileName = profileName != null ? profileName : ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow();
161             return profileFile.get()
162                               .profile(profileName)
163                               .flatMap(p -> p.property(ProfileProperty.RETRY_MODE))
164                               .flatMap(Resolver::fromString);
165         }
166 
fromString(String string)167         private static Optional<RetryMode> fromString(String string) {
168             if (string == null || string.isEmpty()) {
169                 return Optional.empty();
170             }
171 
172             switch (StringUtils.lowerCase(string)) {
173                 case "legacy":
174                     return Optional.of(LEGACY);
175                 case "standard":
176                     return Optional.of(STANDARD);
177                 case "adaptive":
178                     return Optional.of(ADAPTIVE);
179                 default:
180                     throw new IllegalStateException("Unsupported retry policy mode configured: " + string);
181             }
182         }
183 
fromDefaultMode()184         private RetryMode fromDefaultMode() {
185             return defaultRetryMode != null ? defaultRetryMode : SDK_DEFAULT_RETRY_MODE;
186         }
187     }
188 }
189