-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Retry cleanups #318
base: master
Are you sure you want to change the base?
Retry cleanups #318
Changes from all commits
a8a14fd
818aa4a
d890edd
171184c
19c77c1
c066f79
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,8 +17,7 @@ public class ExponentialRetry implements RetryPolicy { | |
int maxAttempts; | ||
long workingAttempts = 1; | ||
long maxTime; | ||
long workingTime = 0L; | ||
TimeUnit unit; | ||
private long lastBackoff = 0L; | ||
private long workingInterval; | ||
private volatile long startTime = 0L; | ||
private float percentOfMaxIntervalForJitter; | ||
|
@@ -28,7 +27,6 @@ public class ExponentialRetry implements RetryPolicy { | |
this.maxInterval = builder.maxInterval; | ||
this.workingInterval = initialInterval; | ||
this.maxAttempts = builder.maxAttempts; | ||
this.unit = builder.unit; | ||
this.maxTime = builder.maxTime; | ||
this.percentOfMaxIntervalForJitter = builder.percentOfMaxIntervalForJitter; | ||
} | ||
|
@@ -45,27 +43,33 @@ public long maxIntervalMillis() { | |
return maxInterval; | ||
} | ||
|
||
long workingTime() { | ||
return lastBackoff - startTime; | ||
} | ||
|
||
public boolean isFinished() { | ||
return workingAttempts >= maxAttempts || workingTime >= maxTime; | ||
return workingAttempts >= maxAttempts || workingTime() >= maxTime; | ||
} | ||
|
||
public long nextBackoffMillis() { | ||
return nextBackOffMillis(System.currentTimeMillis()); | ||
} | ||
|
||
long nextBackOffMillis(long nowMillis) { | ||
if (startTime == 0L) { | ||
startTime = System.currentTimeMillis(); | ||
} else { | ||
workingTime += (System.currentTimeMillis() - startTime); | ||
startTime = nowMillis; | ||
} | ||
lastBackoff = nowMillis; | ||
|
||
if (isFinished()) { | ||
return STOP; | ||
} | ||
|
||
workingInterval = unit.toMillis(workingInterval) * (workingAttempts * workingAttempts); | ||
workingInterval = workingInterval * (workingAttempts * workingAttempts); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This still looks like too aggressive increase. |
||
workingAttempts++; | ||
|
||
if (workingInterval <= 0) { | ||
workingInterval = unit.toMillis(maxInterval); | ||
workingInterval = maxInterval; | ||
} | ||
|
||
if (initialInterval != workingInterval) { | ||
|
@@ -100,13 +104,10 @@ public int maxAttempts() { | |
", maxInterval=" + maxInterval + | ||
", maxAttempts=" + maxAttempts + | ||
", workingAttempts=" + workingAttempts + | ||
", unit=" + unit + | ||
'}'; | ||
} | ||
|
||
public static class Builder { | ||
|
||
private final TimeUnit unit = TimeUnit.MILLISECONDS; | ||
public float percentOfMaxIntervalForJitter = PERCENT_OF_MAX_INTERVAL_AS_JITTER; | ||
private long initialInterval = DEFAULT_INITIAL_INTERVAL_MILLIS; | ||
private long maxInterval = DEFAULT_MAX_INTERVAL_MILLIS; | ||
|
@@ -120,7 +121,7 @@ public Builder initialInterval(long initialInterval, TimeUnit unit) { | |
NakadiException.throwNonNull(unit, "Please provide a TimeUnit"); | ||
this.initialInterval = unit.toMillis(initialInterval); | ||
if (this.initialInterval < INITIAL_INTERVAL_MIN_AS_MILLIS) { | ||
NakadiException.throwNonNull(null, "Please provide an initial value of at least " | ||
throw new IllegalArgumentException("Please provide an initial value of at least " | ||
+ INITIAL_INTERVAL_MIN_AS_MILLIS | ||
+ " millis"); | ||
} | ||
|
@@ -131,7 +132,7 @@ public Builder maxInterval(long maxInterval, TimeUnit unit) { | |
NakadiException.throwNonNull(unit, "Please provide a TimeUnit"); | ||
this.maxInterval = unit.toMillis(maxInterval); | ||
if (this.maxInterval < MAX_INTERVAL_MIN_AS_MILLIS) { | ||
NakadiException.throwNonNull(null, "Please provide a max interval value of at least " | ||
throw new IllegalArgumentException("Please provide a max interval value of at least " | ||
+ MAX_INTERVAL_MIN_AS_MILLIS | ||
+ " millis"); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,81 +8,88 @@ | |
public class ExponentialRetryTest { | ||
|
||
@Test | ||
public void tempusFugit() throws Exception { | ||
|
||
public void tempusFugit_1() throws Exception { | ||
ExponentialRetry exponentialRetry = ExponentialRetry.newBuilder() | ||
.initialInterval(11, TimeUnit.MILLISECONDS) | ||
.maxAttempts(Integer.MAX_VALUE) | ||
.maxInterval(20, TimeUnit.MILLISECONDS) | ||
.percentOfMaxIntervalForJitter(20) | ||
.maxTime(3, TimeUnit.SECONDS) | ||
.build(); | ||
runRetries(exponentialRetry); | ||
validateTimeoutState(exponentialRetry); | ||
} | ||
|
||
while(true) { | ||
long l = exponentialRetry.nextBackoffMillis(); | ||
if(l == -1) { | ||
break; | ||
} | ||
Thread.sleep(l); | ||
} | ||
|
||
assertTrue(exponentialRetry.workingTime >= exponentialRetry.maxTime); | ||
assertTrue(exponentialRetry.workingAttempts < exponentialRetry.maxAttempts); | ||
|
||
exponentialRetry = ExponentialRetry.newBuilder() | ||
@Test | ||
public void tempusFugit_2() throws Exception { | ||
ExponentialRetry exponentialRetry = ExponentialRetry.newBuilder() | ||
.maxTime(3, TimeUnit.SECONDS) | ||
.maxInterval(100, TimeUnit.MILLISECONDS) | ||
.build(); | ||
runRetries(exponentialRetry); | ||
validateTimeoutState(exponentialRetry); | ||
} | ||
|
||
while(true) { | ||
long l = exponentialRetry.nextBackoffMillis(); | ||
if(l == -1) { | ||
break; | ||
} | ||
Thread.sleep(l); | ||
} | ||
|
||
assertTrue(exponentialRetry.workingTime >= exponentialRetry.maxTime); | ||
private void validateTimeoutState(ExponentialRetry exponentialRetry) { | ||
assertTrue(exponentialRetry.workingTime() >= exponentialRetry.maxTime); | ||
assertTrue(exponentialRetry.workingAttempts < exponentialRetry.maxAttempts); | ||
} | ||
|
||
@Test | ||
public void annumero() throws Exception { | ||
|
||
public void annumero_1() throws Exception { | ||
ExponentialRetry exponentialRetry = ExponentialRetry.newBuilder() | ||
.initialInterval(101, TimeUnit.MILLISECONDS) | ||
.maxAttempts(20) | ||
.maxInterval(100, TimeUnit.MILLISECONDS) | ||
.maxTime(Integer.MAX_VALUE, TimeUnit.SECONDS) | ||
.build(); | ||
runRetries(exponentialRetry); | ||
validateRetriesExceededState(exponentialRetry); | ||
} | ||
|
||
while(true) { | ||
long l = exponentialRetry.nextBackoffMillis(); | ||
if(l == -1) { | ||
break; | ||
} | ||
Thread.sleep(l); | ||
} | ||
|
||
assertTrue(exponentialRetry.workingTime < exponentialRetry.maxTime); | ||
assertTrue(exponentialRetry.workingAttempts >= exponentialRetry.maxAttempts); | ||
@Test | ||
public void annumero_2() throws Exception { | ||
ExponentialRetry exponentialRetry = ExponentialRetry.newBuilder() | ||
.maxAttempts(3) | ||
.maxInterval(100, TimeUnit.MILLISECONDS) | ||
.build(); | ||
runRetries(exponentialRetry); | ||
validateRetriesExceededState(exponentialRetry); | ||
} | ||
|
||
@Test | ||
public void workingTimeCalculation() { | ||
ExponentialRetry exponentialRetry = ExponentialRetry.newBuilder() | ||
.maxInterval(100, TimeUnit.MILLISECONDS) | ||
.maxTime(110, TimeUnit.MILLISECONDS) | ||
.build(); | ||
exponentialRetry.nextBackOffMillis(1); | ||
assertFalse(exponentialRetry.isFinished()); | ||
exponentialRetry.nextBackOffMillis(100); | ||
assertFalse(exponentialRetry.isFinished()); | ||
exponentialRetry.nextBackOffMillis(101); | ||
assertFalse(exponentialRetry.isFinished()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would previously fail here as accumulated time was 202 milliseconds (instead of 101). |
||
exponentialRetry.nextBackOffMillis(111); | ||
assertTrue(exponentialRetry.isFinished()); | ||
} | ||
|
||
exponentialRetry = ExponentialRetry.newBuilder() | ||
.maxAttempts(3) | ||
.maxInterval(100, TimeUnit.MILLISECONDS) | ||
.build(); | ||
private void validateRetriesExceededState(ExponentialRetry exponentialRetry) { | ||
assertTrue(exponentialRetry.workingTime() < exponentialRetry.maxTime); | ||
assertTrue(exponentialRetry.workingAttempts >= exponentialRetry.maxAttempts); | ||
} | ||
|
||
private void runRetries(ExponentialRetry exponentialRetry) throws InterruptedException { | ||
while(true) { | ||
long l = exponentialRetry.nextBackoffMillis(); | ||
if(l == -1) { | ||
if(l == RetryPolicy.STOP) { | ||
break; | ||
} | ||
// This does not hold: l >= exponentialRetry.initialInterval() | ||
assertTrue(l <= exponentialRetry.maxIntervalMillis()); | ||
|
||
Thread.sleep(l); | ||
} | ||
|
||
assertTrue(exponentialRetry.workingTime < exponentialRetry.maxTime); | ||
assertTrue(exponentialRetry.workingAttempts >= exponentialRetry.maxAttempts); | ||
assertTrue(exponentialRetry.isFinished()); | ||
} | ||
|
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracted for testing.