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.backoff;
17
18 import static software.amazon.awssdk.utils.NumericUtils.min;
19 import static software.amazon.awssdk.utils.Validate.isNotNegative;
20
21 import java.time.Duration;
22 import java.util.Random;
23 import software.amazon.awssdk.annotations.SdkPublicApi;
24 import software.amazon.awssdk.core.retry.RetryPolicyContext;
25 import software.amazon.awssdk.utils.ToString;
26 import software.amazon.awssdk.utils.builder.CopyableBuilder;
27 import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
28
29 /**
30  * Backoff strategy that uses a full jitter strategy for computing the next backoff delay. A full jitter
31  * strategy will always compute a new random delay between 0 and the computed exponential backoff for each
32  * subsequent request.
33  *
34  * For example, using a base delay of 100, a max backoff time of 10000 an exponential delay of 400 is computed
35  * for a second retry attempt. The final computed delay before the next retry will then be in the range of 0 to 400.
36  *
37  * This is in contrast to {@link EqualJitterBackoffStrategy} that computes a new random delay where the final
38  * computed delay before the next retry will be at least half of the computed exponential delay.
39  */

40 @SdkPublicApi
41 public final class FullJitterBackoffStrategy implements BackoffStrategy,
42                                                         ToCopyableBuilder<FullJitterBackoffStrategy.Builder,
43                                                             FullJitterBackoffStrategy> {
44     private static final Duration BASE_DELAY_CEILING = Duration.ofMillis(Integer.MAX_VALUE); // Around 24 days
45     private static final Duration MAX_BACKOFF_CEILING = Duration.ofMillis(Integer.MAX_VALUE); // Around 24 days
46
47     private final Duration baseDelay;
48     private final Duration maxBackoffTime;
49     private final Random random;
50
51     private FullJitterBackoffStrategy(BuilderImpl builder) {
52         this(builder.baseDelay, builder.maxBackoffTime, new Random());
53     }
54
55     FullJitterBackoffStrategy(final Duration baseDelay, final Duration maxBackoffTime, final Random random) {
56         this.baseDelay = min(isNotNegative(baseDelay, "baseDelay"), BASE_DELAY_CEILING);
57         this.maxBackoffTime = min(isNotNegative(maxBackoffTime, "maxBackoffTime"), MAX_BACKOFF_CEILING);
58         this.random = random;
59     }
60
61     @Override
62     public Duration computeDelayBeforeNextRetry(RetryPolicyContext context) {
63         int ceil = calculateExponentialDelay(context.retriesAttempted(), baseDelay, maxBackoffTime);
64         return Duration.ofMillis(random.nextInt(ceil));
65     }
66
67     @Override
68     public Builder toBuilder() {
69         return builder().baseDelay(baseDelay).maxBackoffTime(maxBackoffTime);
70     }
71
72     public static Builder builder() {
73         return new BuilderImpl();
74     }
75
76     public interface Builder extends CopyableBuilder<Builder, FullJitterBackoffStrategy> {
77         Builder baseDelay(Duration baseDelay);
78
79         Duration baseDelay();
80
81         Builder maxBackoffTime(Duration maxBackoffTime);
82
83         Duration maxBackoffTime();
84
85         FullJitterBackoffStrategy build();
86     }
87
88     private static final class BuilderImpl implements Builder {
89
90         private Duration baseDelay;
91         private Duration maxBackoffTime;
92
93         private BuilderImpl() {
94         }
95
96         @Override
97         public Builder baseDelay(Duration baseDelay) {
98             this.baseDelay = baseDelay;
99             return this;
100         }
101
102         public void setBaseDelay(Duration baseDelay) {
103             baseDelay(baseDelay);
104         }
105
106         @Override
107         public Duration baseDelay() {
108             return baseDelay;
109         }
110
111         @Override
112         public Builder maxBackoffTime(Duration maxBackoffTime) {
113             this.maxBackoffTime = maxBackoffTime;
114             return this;
115         }
116
117         public void setMaxBackoffTime(Duration maxBackoffTime) {
118             maxBackoffTime(maxBackoffTime);
119         }
120
121         @Override
122         public Duration maxBackoffTime() {
123             return maxBackoffTime;
124         }
125
126         @Override
127         public FullJitterBackoffStrategy build() {
128             return new FullJitterBackoffStrategy(this);
129         }
130     }
131
132     @Override
133     public boolean equals(Object o) {
134         if (this == o) {
135             return true;
136         }
137         if (o == null || getClass() != o.getClass()) {
138             return false;
139         }
140
141         FullJitterBackoffStrategy that = (FullJitterBackoffStrategy) o;
142
143         if (!baseDelay.equals(that.baseDelay)) {
144             return false;
145         }
146         return maxBackoffTime.equals(that.maxBackoffTime);
147     }
148
149     @Override
150     public int hashCode() {
151         int result = baseDelay.hashCode();
152         result = 31 * result + maxBackoffTime.hashCode();
153         return result;
154     }
155
156     @Override
157     public String toString() {
158         return ToString.builder("FullJitterBackoffStrategy")
159                        .add("baseDelay", baseDelay)
160                        .add("maxBackoffTime", maxBackoffTime)
161                        .build();
162     }
163 }
164