1
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
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);
49 private static final Duration MAX_BACKOFF_CEILING = Duration.ofMillis(Integer.MAX_VALUE);
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