Skip to content

Commit b00764e

Browse files
authored
Updated CappedExponentialDelay timeout logic (#424)
1 parent 7011f0a commit b00764e

File tree

2 files changed

+66
-28
lines changed

2 files changed

+66
-28
lines changed

src/main/java/software/amazon/cloudformation/proxy/delay/CappedExponential.java

+21-13
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,25 @@
1616

1717
import com.google.common.base.Preconditions;
1818
import java.time.Duration;
19-
import java.util.concurrent.TimeUnit;
2019

2120
public class CappedExponential extends MinDelayAbstractBase {
2221

2322
final double powerBy;
2423

25-
final long startTimeInMillis;
26-
2724
final Duration maxDelay;
2825

26+
private Duration accrued = Duration.ZERO;
27+
2928
CappedExponential(Duration timeout,
3029
Duration minDelay,
3130
Double powerBy,
3231
Duration maxDelay) {
3332
super(timeout, minDelay);
3433
Preconditions.checkArgument(powerBy >= 1.0, "powerBy >= 1.0");
35-
Preconditions.checkArgument(maxDelay != null && maxDelay.toMillis() >= 0, "maxDelay must be > 0");
34+
Preconditions.checkArgument(maxDelay != null && maxDelay.toMillis() > 0, "maxDelay must be > 0");
3635
Preconditions.checkArgument(maxDelay.compareTo(minDelay) >= 0, "maxDelay.compareTo(minDelay) >= 0");
3736
this.powerBy = powerBy;
3837
this.maxDelay = maxDelay;
39-
this.startTimeInMillis = System.currentTimeMillis();
4038
}
4139

4240
public static Builder of() {
@@ -47,6 +45,8 @@ public static final class Builder extends MinDelayBasedBuilder<CappedExponential
4745
private double powerBy = 2;
4846
private Duration maxDelay = Duration.ofSeconds(20);
4947

48+
private Duration minDelay = Duration.ofSeconds(1);
49+
5050
public CappedExponential.Builder powerBy(Double powerBy) {
5151
this.powerBy = powerBy;
5252
return this;
@@ -57,6 +57,11 @@ public CappedExponential.Builder maxDelay(Duration maxDelay) {
5757
return this;
5858
}
5959

60+
public CappedExponential.Builder minDelay(Duration minDelay) {
61+
this.minDelay = minDelay;
62+
return this;
63+
}
64+
6065
@Override
6166
public CappedExponential build() {
6267
return new CappedExponential(timeout, minDelay, powerBy, maxDelay);
@@ -65,14 +70,17 @@ public CappedExponential build() {
6570

6671
@Override
6772
public Duration nextDelay(int attempt) {
68-
if (attempt == 0) {
69-
return minDelay;
70-
}
71-
if (System.currentTimeMillis() - startTimeInMillis > timeout.toMillis()) {
72-
return Duration.ZERO;
73-
}
74-
long next = Math.round(minDelay.toMillis() * Math.pow(powerBy, attempt - 1));
75-
return Duration.ofSeconds(Math.min(maxDelay.getSeconds(), TimeUnit.MILLISECONDS.toSeconds(next)));
73+
Duration next = Duration.ofSeconds(Math.round(Math.pow(powerBy, attempt)));
74+
Duration nextDelay = Duration.ofSeconds(Math.min(maxDelay.getSeconds(), next.getSeconds()));
75+
accrued = accrued.plus(nextDelay);
76+
return enforceBounds(accrued, nextDelay);
77+
78+
}
79+
80+
@Override
81+
public String toString() {
82+
return "CappedExponential{" + "powerBy=" + powerBy + ", maxDelay=" + maxDelay + ", accrued=" + accrued + ", minDelay="
83+
+ minDelay + ", timeout=" + timeout + '}';
7684
}
7785

7886
}

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

+45-15
Original file line numberDiff line numberDiff line change
@@ -205,30 +205,19 @@ public void cappedExponentialDelays() {
205205
Duration MAX_DELAY = Duration.ofSeconds(15);
206206
final Delay cappedExponential = CappedExponential.of().timeout(Duration.ofMinutes(20)).maxDelay(MAX_DELAY).powerBy(1.3)
207207
.minDelay(Duration.ofSeconds(1)).build();
208-
int[] results = { 1, 1, 1, 1, 2, 2, 3, 4, 6, 8, 10, 13, 15, 15, 15, 15 };
208+
int[] results = { 1, 1, 2, 2, 3, 4, 5, 6, 8, 11, 14, 15, 15, 15, 15, 15, 15 };
209209
for (int tries = 0; tries <= 15; tries++) {
210210
Duration delay = cappedExponential.nextDelay(tries);
211211
assertThat(results[tries]).isEqualTo((int) delay.getSeconds());
212-
if (tries >= 12) {
212+
if (tries >= 11) {
213213
assertThat(MAX_DELAY.getSeconds()).isEqualTo(delay.getSeconds());
214214
}
215215
}
216216

217-
// If minDelay is not set, the retry is without delay.
218-
final Delay cappedExponentialNoDelay = CappedExponential.of().timeout(Duration.ofSeconds(12)).build();
219-
for (int tries = 0; tries <= 15; tries++) {
220-
Duration delay = cappedExponentialNoDelay.nextDelay(tries);
221-
assertThat(0).isEqualTo((int) delay.getSeconds());
222-
if (tries >= 12) {
223-
assertThat(0).isEqualTo(delay.getSeconds());
224-
}
225-
}
226-
227217
// If powerBy is not passed, it's set to default 2.
228218
final Delay cappedExponentialNoPower = CappedExponential.of().timeout(Duration.ofMinutes(20)).maxDelay(MAX_DELAY)
229-
.minDelay(Duration.ofSeconds(1)).build();
230-
231-
int[] resultsNoPower = { 1, 1, 2, 4, 8, 15, 15, 15, 15, 15 };
219+
.minDelay(Duration.ofSeconds(2)).build();
220+
int[] resultsNoPower = { 2, 2, 4, 8, 15, 15, 15, 15, 15 };
232221
for (int tries = 0; tries <= 6; tries++) {
233222
Duration delay = cappedExponentialNoPower.nextDelay(tries);
234223
assertThat(resultsNoPower[tries]).isEqualTo((int) delay.getSeconds());
@@ -237,5 +226,46 @@ public void cappedExponentialDelays() {
237226
}
238227
}
239228

229+
// If timeout is reached the delay is 0
230+
final Delay cappedExponentialTimeout = CappedExponential.of().timeout(Duration.ofSeconds(5))
231+
.maxDelay(Duration.ofSeconds(1)).powerBy(1.0).minDelay(Duration.ofSeconds(1)).build();
232+
233+
int[] resultsTimeout = { 1, 1, 1, 1, 1, 0 };
234+
for (int tries = 0; tries <= 5; tries++) {
235+
Duration delay = cappedExponentialTimeout.nextDelay(tries);
236+
assertThat(resultsTimeout[tries]).isEqualTo((int) delay.getSeconds());
237+
if (tries >= 5) {
238+
assertThat(0).isEqualTo(delay.getSeconds());
239+
}
240+
}
241+
242+
// If minDelay is not passed, it's set to default 1.
243+
final Delay cappedExponentialNoMinDelay = CappedExponential.of().timeout(Duration.ofSeconds(5))
244+
.maxDelay(Duration.ofSeconds(1)).powerBy(1.0).build();
245+
int[] resultsNoMinDelay = { 1, 1, 1, 1, 1, 0 };
246+
for (int tries = 0; tries <= 5; tries++) {
247+
Duration delay = cappedExponentialNoMinDelay.nextDelay(tries);
248+
assertThat(resultsNoMinDelay[tries]).isEqualTo((int) delay.getSeconds());
249+
}
250+
251+
// If maxDelay is not passed, it's set to default 20 sec.
252+
final Delay cappedExponentialNoMaxDelay = CappedExponential.of().timeout(Duration.ofMinutes(20))
253+
.minDelay(Duration.ofSeconds(2)).build();
254+
int[] resultsNoMaxDelay = { 2, 2, 4, 8, 16, 20, 20, 20, 20 };
255+
for (int tries = 0; tries <= 6; tries++) {
256+
Duration delay = cappedExponentialNoMaxDelay.nextDelay(tries);
257+
assertThat(resultsNoMaxDelay[tries]).isEqualTo((int) delay.getSeconds());
258+
}
259+
260+
final Delay cappedExponentialSameMinMaxDelay = CappedExponential.of().timeout(Duration.ofSeconds(5))
261+
.maxDelay(Duration.ofSeconds(1)).powerBy(1.3).minDelay(Duration.ofSeconds(1)).build();
262+
int[] resultsSameMinMaxDelay = { 1, 1, 1, 1, 1, 0 };
263+
for (int tries = 0; tries <= 5; tries++) {
264+
Duration delay = cappedExponentialSameMinMaxDelay.nextDelay(tries);
265+
assertThat(resultsSameMinMaxDelay[tries]).isEqualTo((int) delay.getSeconds());
266+
if (tries >= 5) {
267+
assertThat(0).isEqualTo(delay.getSeconds());
268+
}
269+
}
240270
}
241271
}

0 commit comments

Comments
 (0)