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 equal jitter for computing the delay before the next retry. An equal jitter
31  * backoff strategy will first compute an exponential delay based on the current number of retries, base delay
32  * and max delay. The final computed delay before the next retry will keep half of this computed delay plus
33  * a random delay computed as a random number between 0 and half of the exponential delay plus one.
34  *
35  * For example, using a base delay of 100, a max backoff time of 10000 an exponential delay of 400 is computed
36  * for a second retry attempt. The final computed delay before the next retry will be half of the computed exponential
37  * delay, in this case 200, plus a random number between 0 and 201. Therefore the range for delay would be between
38  * 200 and 401.
39  *
40  * This is in contrast to {@link FullJitterBackoffStrategy} where the final computed delay before the next retry will be
41  * between 0 and the computed exponential delay.
42  */

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