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