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 software.amazon.awssdk.annotations.Immutable;
19 import software.amazon.awssdk.annotations.SdkPublicApi;
20 import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
21 import software.amazon.awssdk.core.internal.retry.SdkDefaultRetrySetting;
22 import software.amazon.awssdk.core.retry.backoff.BackoffStrategy;
23 import software.amazon.awssdk.core.retry.conditions.AndRetryCondition;
24 import software.amazon.awssdk.core.retry.conditions.MaxNumberOfRetriesCondition;
25 import software.amazon.awssdk.core.retry.conditions.RetryCondition;
26 import software.amazon.awssdk.core.retry.conditions.TokenBucketRetryCondition;
27 import software.amazon.awssdk.utils.ToString;
28 import software.amazon.awssdk.utils.builder.CopyableBuilder;
29 import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
30
31 /**
32  * Interface for specifying a retry policy to use when evaluating whether or not a request should be retried. The
33  * {@link #builder()}} can be used to construct a retry policy from SDK provided policies or policies that directly implement
34  * {@link BackoffStrategy} and/or {@link RetryCondition}. This is configured on a client via
35  * {@link ClientOverrideConfiguration.Builder#retryPolicy}.
36  *
37  * When using the {@link #builder()} the SDK will use default values for fields that are not provided. The default number of
38  * retries and condition is based on the current {@link RetryMode}.
39  *
40  * @see RetryCondition for a list of SDK provided retry condition strategies
41  * @see BackoffStrategy for a list of SDK provided backoff strategies
42  */

43 @Immutable
44 @SdkPublicApi
45 public final class RetryPolicy implements ToCopyableBuilder<RetryPolicy.Builder, RetryPolicy> {
46     private final boolean additionalRetryConditionsAllowed;
47     private final RetryMode retryMode;
48     private final BackoffStrategy backoffStrategy;
49     private final BackoffStrategy throttlingBackoffStrategy;
50     private final Integer numRetries;
51     private final RetryCondition retryCondition;
52     private final RetryCondition retryCapacityCondition;
53
54     private final RetryCondition aggregateRetryCondition;
55
56     private RetryPolicy(BuilderImpl builder) {
57         this.additionalRetryConditionsAllowed = builder.additionalRetryConditionsAllowed;
58         this.retryMode = builder.retryMode;
59         this.backoffStrategy = builder.backoffStrategy;
60         this.throttlingBackoffStrategy = builder.throttlingBackoffStrategy;
61         this.numRetries = builder.numRetries;
62         this.retryCondition = builder.retryCondition;
63         this.retryCapacityCondition = builder.retryCapacityCondition;
64
65         this.aggregateRetryCondition = generateAggregateRetryCondition();
66     }
67
68     /**
69      * Create a {@link RetryPolicy} using the {@link RetryMode#defaultRetryMode()} defaults.
70      */

71     public static RetryPolicy defaultRetryPolicy() {
72         return forRetryMode(RetryMode.defaultRetryMode());
73     }
74
75     /**
76      * Create a {@link RetryPolicy} using the provided {@link RetryMode} defaults.
77      */

78     public static RetryPolicy forRetryMode(RetryMode retryMode) {
79         return RetryPolicy.builder(retryMode).build();
80     }
81
82     /**
83      * Create a {@link RetryPolicy} that will NEVER retry.
84      */

85     public static RetryPolicy none() {
86         return RetryPolicy.builder()
87                           .numRetries(0)
88                           .backoffStrategy(BackoffStrategy.none())
89                           .throttlingBackoffStrategy(BackoffStrategy.none())
90                           .retryCondition(RetryCondition.none())
91                           .additionalRetryConditionsAllowed(false)
92                           .build();
93     }
94
95     /**
96      * Create a {@link RetryPolicy.Builder} populated with the defaults from the {@link RetryMode#defaultRetryMode()}.
97      */

98     public static Builder builder() {
99         return new BuilderImpl(RetryMode.defaultRetryMode());
100     }
101
102     /**
103      * Create a {@link RetryPolicy.Builder} populated with the defaults from the provided {@link RetryMode}.
104      */

105     public static Builder builder(RetryMode retryMode) {
106         return new BuilderImpl(retryMode);
107     }
108
109     /**
110      * Retrieve the {@link RetryMode} that was used to determine the defaults for this retry policy.
111      */

112     public RetryMode retryMode() {
113         return retryMode;
114     }
115
116     /**
117      * Returns true if service-specific conditions are allowed on this policy (e.g. more conditions may be added by the SDK if
118      * they are recommended).
119      */

120     public boolean additionalRetryConditionsAllowed() {
121         return additionalRetryConditionsAllowed;
122     }
123
124     /**
125      * Retrieve the retry condition that aggregates the {@link Builder#retryCondition(RetryCondition)},
126      * {@link Builder#numRetries(Integer)} and {@link Builder#retryCapacityCondition(RetryCondition)} configured on the builder.
127      */

128     public RetryCondition aggregateRetryCondition() {
129         return aggregateRetryCondition;
130     }
131
132     /**
133      * Retrieve the {@link Builder#retryCondition(RetryCondition)} configured on the builder.
134      */

135     public RetryCondition retryCondition() {
136         return retryCondition;
137     }
138
139     /**
140      * Retrieve the {@link Builder#backoffStrategy(BackoffStrategy)} configured on the builder.
141      */

142     public BackoffStrategy backoffStrategy() {
143         return backoffStrategy;
144     }
145
146     /**
147      * Retrieve the {@link Builder#throttlingBackoffStrategy(BackoffStrategy)} configured on the builder.
148      */

149     public BackoffStrategy throttlingBackoffStrategy() {
150         return throttlingBackoffStrategy;
151     }
152
153     /**
154      * Retrieve the {@link Builder#numRetries(Integer)} configured on the builder.
155      */

156     public Integer numRetries() {
157         return numRetries;
158     }
159
160     private RetryCondition generateAggregateRetryCondition() {
161         RetryCondition aggregate = AndRetryCondition.create(MaxNumberOfRetriesCondition.create(numRetries),
162                                                             retryCondition);
163         if (retryCapacityCondition != null) {
164             return AndRetryCondition.create(aggregate, retryCapacityCondition);
165         }
166         return aggregate;
167     }
168
169     public Builder toBuilder() {
170         return builder(retryMode).additionalRetryConditionsAllowed(additionalRetryConditionsAllowed)
171                                  .numRetries(numRetries)
172                                  .retryCondition(retryCondition)
173                                  .backoffStrategy(backoffStrategy)
174                                  .throttlingBackoffStrategy(throttlingBackoffStrategy)
175                                  .retryCapacityCondition(retryCapacityCondition);
176     }
177
178     @Override
179     public String toString() {
180         return ToString.builder("RetryPolicy")
181                        .add("additionalRetryConditionsAllowed", additionalRetryConditionsAllowed)
182                        .add("aggregateRetryCondition", aggregateRetryCondition)
183                        .add("backoffStrategy", backoffStrategy)
184                        .add("throttlingBackoffStrategy", throttlingBackoffStrategy)
185                        .build();
186     }
187
188     @Override
189     public boolean equals(Object o) {
190         if (this == o) {
191             return true;
192         }
193         if (o == null || getClass() != o.getClass()) {
194             return false;
195         }
196
197         RetryPolicy that = (RetryPolicy) o;
198
199         if (additionalRetryConditionsAllowed != that.additionalRetryConditionsAllowed) {
200             return false;
201         }
202         if (!aggregateRetryCondition.equals(that.aggregateRetryCondition)) {
203             return false;
204         }
205         if (!backoffStrategy.equals(that.backoffStrategy)) {
206             return false;
207         }
208         if (!throttlingBackoffStrategy.equals(that.throttlingBackoffStrategy)) {
209             return false;
210         }
211         return true;
212     }
213
214     @Override
215     public int hashCode() {
216         int result = aggregateRetryCondition.hashCode();
217         result = 31 * result + Boolean.hashCode(additionalRetryConditionsAllowed);
218         result = 31 * result + backoffStrategy.hashCode();
219         result = 31 * result + throttlingBackoffStrategy.hashCode();
220         return result;
221     }
222
223     public interface Builder extends CopyableBuilder<Builder, RetryPolicy> {
224         /**
225          * Configure whether further conditions can be added to this policy after it is created. This may include service-
226          * specific retry conditions that may not otherwise be covered by the {@link RetryCondition#defaultRetryCondition()}.
227          *
228          * <p>
229          * By defaultthis is true.
230          */

231         Builder additionalRetryConditionsAllowed(boolean additionalRetryConditionsAllowed);
232
233         /**
234          * @see #additionalRetryConditionsAllowed(boolean)
235          */

236         boolean additionalRetryConditionsAllowed();
237
238         /**
239          * Configure the backoff strategy that should be used for waiting in between retry attempts. If the retry is because of
240          * throttling reasons, the {@link #throttlingBackoffStrategy(BackoffStrategy)} is used instead.
241          */

242         Builder backoffStrategy(BackoffStrategy backoffStrategy);
243
244         /**
245          * @see #backoffStrategy(BackoffStrategy)
246          */

247         BackoffStrategy backoffStrategy();
248
249         /**
250          * Configure the backoff strategy that should be used for waiting in between retry attempts after a throttling error
251          * is encountered. If the retry is not because of throttling reasons, the {@link #backoffStrategy(BackoffStrategy)} is
252          * used instead.
253          */

254         Builder throttlingBackoffStrategy(BackoffStrategy backoffStrategy);
255
256         /**
257          * @see #throttlingBackoffStrategy(BackoffStrategy)
258          */

259         BackoffStrategy throttlingBackoffStrategy();
260
261         /**
262          * Configure the condition under which the request should be retried.
263          *
264          * <p>
265          * While this can be any interface that implements {@link RetryCondition}, it is encouraged to use
266          * {@link #numRetries(Integer)} when attempting to limit the number of times the SDK will retry an attempt or the
267          * {@link #retryCapacityCondition(RetryCondition)} when attempting to configure the throttling of retries. This guidance
268          * is because the SDK uses the {@link #aggregateRetryCondition()} when determining whether or not to retry a request,
269          * and the {@code aggregateRetryCondition} includes the {@code numRetries} and {@code retryCapacityCondition} in its
270          * determination.
271          */

272         Builder retryCondition(RetryCondition retryCondition);
273
274         /**
275          * @see #retryCondition(RetryCondition)
276          */

277         RetryCondition retryCondition();
278
279         /**
280          * Configure the {@link RetryCondition} that should be used to throttle the number of retries attempted by the SDK client
281          * as a whole.
282          *
283          * <p>
284          * While any {@link RetryCondition} (or null) can be used, by convention these conditions are usually stateful and work
285          * globally for the whole client to limit the overall capacity of the client to execute retries.
286          *
287          * <p>
288          * By default the {@link TokenBucketRetryCondition} is used. This can be disabled by setting the value to {@code null}
289          * (not {@code RetryPolicy#none()}, which would completely disable retries).
290          */

291         Builder retryCapacityCondition(RetryCondition retryCapacityCondition);
292
293         /**
294          * @see #retryCapacityCondition(RetryCondition)
295          */

296         RetryCondition retryCapacityCondition();
297
298         /**
299          * Configure the maximum number of times that a single request should be retried, assuming it fails for a retryable error.
300          */

301         Builder numRetries(Integer numRetries);
302
303         /**
304          * @see #numRetries(Integer)
305          */

306         Integer numRetries();
307         
308         RetryPolicy build();
309     }
310
311     /**
312      * Builder for a {@link RetryPolicy}.
313      */

314     private static final class BuilderImpl implements Builder {
315         private final RetryMode retryMode;
316
317         private boolean additionalRetryConditionsAllowed;
318         private Integer numRetries;
319         private BackoffStrategy backoffStrategy;
320         private BackoffStrategy throttlingBackoffStrategy;
321         private RetryCondition retryCondition;
322         private RetryCondition retryCapacityCondition;
323
324         private BuilderImpl(RetryMode retryMode) {
325             this.retryMode = retryMode;
326             this.numRetries = SdkDefaultRetrySetting.maxAttempts(retryMode) - 1;
327             this.additionalRetryConditionsAllowed = true;
328             this.backoffStrategy = BackoffStrategy.defaultStrategy();
329             this.throttlingBackoffStrategy = BackoffStrategy.defaultThrottlingStrategy();
330             this.retryCondition = RetryCondition.defaultRetryCondition();
331             this.retryCapacityCondition = TokenBucketRetryCondition.forRetryMode(retryMode);
332         }
333
334         @Override
335         public Builder additionalRetryConditionsAllowed(boolean additionalRetryConditionsAllowed) {
336             this.additionalRetryConditionsAllowed = additionalRetryConditionsAllowed;
337             return this;
338         }
339
340         public void setadditionalRetryConditionsAllowed(boolean additionalRetryConditionsAllowed) {
341             additionalRetryConditionsAllowed(additionalRetryConditionsAllowed);
342         }
343
344         @Override
345         public boolean additionalRetryConditionsAllowed() {
346             return additionalRetryConditionsAllowed;
347         }
348
349         @Override
350         public Builder numRetries(Integer numRetries) {
351             this.numRetries = numRetries;
352             return this;
353         }
354
355         public void setNumRetries(Integer numRetries) {
356             numRetries(numRetries);
357         }
358
359         @Override
360         public Integer numRetries() {
361             return numRetries;
362         }
363
364         @Override
365         public Builder backoffStrategy(BackoffStrategy backoffStrategy) {
366             this.backoffStrategy = backoffStrategy;
367             return this;
368         }
369
370         public void setBackoffStrategy(BackoffStrategy backoffStrategy) {
371             backoffStrategy(backoffStrategy);
372         }
373
374         @Override
375         public BackoffStrategy backoffStrategy() {
376             return backoffStrategy;
377         }
378
379         @Override
380         public Builder throttlingBackoffStrategy(BackoffStrategy throttlingBackoffStrategy) {
381             this.throttlingBackoffStrategy = throttlingBackoffStrategy;
382             return this;
383         }
384
385         @Override
386         public BackoffStrategy throttlingBackoffStrategy() {
387             return throttlingBackoffStrategy;
388         }
389
390         public void setThrottlingBackoffStrategy(BackoffStrategy throttlingBackoffStrategy) {
391             this.throttlingBackoffStrategy = throttlingBackoffStrategy;
392         }
393
394         @Override
395         public Builder retryCondition(RetryCondition retryCondition) {
396             this.retryCondition = retryCondition;
397             return this;
398         }
399
400         public void setRetryCondition(RetryCondition retryCondition) {
401             retryCondition(retryCondition);
402         }
403
404         @Override
405         public RetryCondition retryCondition() {
406             return retryCondition;
407         }
408
409         @Override
410         public Builder retryCapacityCondition(RetryCondition retryCapacityCondition) {
411             this.retryCapacityCondition = retryCapacityCondition;
412             return this;
413         }
414
415         public void setRetryCapacityCondition(RetryCondition retryCapacityCondition) {
416             retryCapacityCondition(retryCapacityCondition);
417         }
418
419         @Override
420         public RetryCondition retryCapacityCondition() {
421             return this.retryCapacityCondition;
422         }
423
424         @Override
425         public RetryPolicy build() {
426             return new RetryPolicy(this);
427         }
428     }
429 }
430