Skip to content

Commit ae86852

Browse files
authored
Added CappedExponential Delay (#416)
1 parent 6232ac3 commit ae86852

File tree

2 files changed

+109
-5
lines changed

2 files changed

+109
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package software.amazon.cloudformation.proxy.delay;
2+
3+
import com.google.common.base.Preconditions;
4+
5+
import java.time.Duration;
6+
import java.util.concurrent.TimeUnit;
7+
8+
public class CappedExponential extends MinDelayAbstractBase {
9+
10+
final double powerBy;
11+
12+
final long startTimeInMillis;
13+
14+
final Duration maxDelay;
15+
16+
CappedExponential(Duration timeout, Duration minDelay, Double powerBy, Duration maxDelay) {
17+
super(timeout, minDelay);
18+
Preconditions.checkArgument(powerBy >= 1.0, "powerBy >= 1.0");
19+
Preconditions.checkArgument(maxDelay != null && maxDelay.toMillis() >= 0, "maxDelay must be > 0");
20+
Preconditions.checkArgument(maxDelay.compareTo(minDelay) >= 0, "maxDelay.compareTo(minDelay) >= 0");
21+
this.powerBy = powerBy;
22+
this.maxDelay = maxDelay;
23+
this.startTimeInMillis = System.currentTimeMillis();
24+
}
25+
26+
public static Builder of() {
27+
return new Builder();
28+
}
29+
30+
public static final class Builder extends MinDelayBasedBuilder<CappedExponential, Builder> {
31+
private double powerBy = 2;
32+
private Duration maxDelay =Duration.ofSeconds(20);
33+
34+
public CappedExponential.Builder powerBy(Double powerBy) {
35+
this.powerBy = powerBy;
36+
return this;
37+
}
38+
39+
public CappedExponential.Builder maxDelay(Duration maxDelay) {
40+
this.maxDelay = maxDelay;
41+
return this;
42+
}
43+
44+
@Override
45+
public CappedExponential build() {
46+
return new CappedExponential(timeout, minDelay, powerBy, maxDelay);
47+
}
48+
}
49+
50+
@Override
51+
public Duration nextDelay(int attempt) {
52+
if (attempt == 0) {
53+
return minDelay;
54+
}
55+
if (System.currentTimeMillis() - startTimeInMillis > timeout.toMillis()) {
56+
return Duration.ZERO;
57+
}
58+
long next = Math.round(minDelay.toMillis() * Math.pow(powerBy, attempt - 1));
59+
return Duration.ofSeconds(Math.min(maxDelay.getSeconds(), TimeUnit.MILLISECONDS.toSeconds(next)));
60+
}
61+
62+
}

src/test/java/software/amazon/cloudformation/proxy/DelayTest.java

+47-5
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@
2020
import org.junit.jupiter.api.Assertions;
2121
import org.junit.jupiter.api.Test;
2222
import org.junit.jupiter.api.function.Executable;
23-
import software.amazon.cloudformation.proxy.delay.Blended;
24-
import software.amazon.cloudformation.proxy.delay.Constant;
25-
import software.amazon.cloudformation.proxy.delay.Exponential;
26-
import software.amazon.cloudformation.proxy.delay.MultipleOf;
27-
import software.amazon.cloudformation.proxy.delay.ShiftByMultipleOf;
23+
import software.amazon.cloudformation.proxy.delay.*;
2824

2925
public class DelayTest {
3026

@@ -203,4 +199,50 @@ public void exponentialDelays() {
203199
}
204200
assertThat(3).isEqualTo(attempt);
205201
}
202+
203+
@Test
204+
public void cappedExponentialDelays() {
205+
Duration MAX_DELAY = Duration.ofSeconds(15);
206+
final Delay cappedExponential = CappedExponential.of()
207+
.timeout(Duration.ofMinutes(20))
208+
.maxDelay(MAX_DELAY)
209+
.powerBy(1.3)
210+
.minDelay(Duration.ofSeconds(1))
211+
.build();
212+
int[] results = {1, 1, 1, 1, 2, 2, 3, 4, 6, 8, 10, 13, 15, 15, 15, 15};
213+
for (int tries = 0; tries <= 15; tries++) {
214+
Duration delay = cappedExponential.nextDelay(tries);
215+
assertThat(results[tries]).isEqualTo((int) delay.getSeconds());
216+
if (tries >= 12) {
217+
assertThat(MAX_DELAY.getSeconds()).isEqualTo(delay.getSeconds());
218+
}
219+
}
220+
221+
//If minDelay is not set, the retry is without delay.
222+
final Delay cappedExponentialNoDelay = CappedExponential.of().timeout(Duration.ofSeconds(12)).build();
223+
for (int tries = 0; tries <= 15; tries++) {
224+
Duration delay = cappedExponentialNoDelay.nextDelay(tries);
225+
assertThat(0).isEqualTo((int) delay.getSeconds());
226+
if (tries >= 12) {
227+
assertThat(0).isEqualTo(delay.getSeconds());
228+
}
229+
}
230+
231+
//If powerBy is not passed, it's set to default 2.
232+
final Delay cappedExponentialNoPower = CappedExponential.of()
233+
.timeout(Duration.ofMinutes(20))
234+
.maxDelay(MAX_DELAY)
235+
.minDelay(Duration.ofSeconds(1))
236+
.build();
237+
238+
int[] resultsNoPower = {1, 1, 2, 4, 8, 15, 15, 15, 15, 15};
239+
for (int tries = 0; tries <= 6; tries++) {
240+
Duration delay = cappedExponentialNoPower.nextDelay(tries);
241+
assertThat(resultsNoPower[tries]).isEqualTo((int) delay.getSeconds());
242+
if (tries >= 5) {
243+
assertThat(MAX_DELAY.getSeconds()).isEqualTo(delay.getSeconds());
244+
}
245+
}
246+
247+
}
206248
}

0 commit comments

Comments
 (0)