Skip to content

Commit ad8d71a

Browse files
committed
Add duration based maxRetries option to Retry
1 parent 3541a15 commit ad8d71a

File tree

2 files changed

+128
-32
lines changed

2 files changed

+128
-32
lines changed

core/src/main/java/org/apache/accumulo/core/util/Retry.java

+54-14
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ private static class RetryFactoryBuilder
364364
private Duration waitIncrement;
365365
private Duration logInterval;
366366
private double backOffFactor = 1.5;
367+
private Duration retriesForDuration = null;
367368

368369
RetryFactoryBuilder() {}
369370

@@ -390,23 +391,58 @@ public NeedsRetryDelay maxRetries(long max) {
390391
@Override
391392
public NeedsRetryDelay retriesForDuration(Duration duration) {
392393
checkState();
393-
long durationInMillis = unit.toMillis(duration);
394-
long totalWait = 0;
395-
long retries = 0;
396-
long currentWait = initialWait;
397-
while (totalWait < durationInMillis && (maxRetries == -1 || retries < maxRetries)) {
398-
totalWait += currentWait;
399-
if (totalWait < durationInMillis) {
400-
retries++;
401-
if (backOffFactor > 1) {
402-
currentWait = Math.min(maxWait, (long) (currentWait * backOffFactor));
403-
} else {
404-
currentWait = Math.min(maxWait, currentWait + waitIncrement);
405-
}
394+
Preconditions.checkState(!duration.isNegative(), "Duration must not be negative");
395+
this.retriesForDuration = duration;
396+
return this;
397+
}
398+
399+
private void calculateRetriesForDuration() {
400+
long durationMillis = this.retriesForDuration.toMillis();
401+
long totalWaitMillis = initialWait.toMillis();
402+
long currentWaitMillis = totalWaitMillis;
403+
404+
System.out.println("Duration in milliseconds: " + durationMillis);
405+
406+
long retries = 0L;
407+
// if the initial wait already exceeds or meets the duration
408+
if (totalWaitMillis >= durationMillis) {
409+
this.maxRetries = retries;
410+
return;
411+
}
412+
413+
while (totalWaitMillis + currentWaitMillis <= durationMillis) {
414+
415+
if (backOffFactor > 1) {
416+
currentWaitMillis = (long) (currentWaitMillis * backOffFactor);
417+
} else {
418+
currentWaitMillis += waitIncrement.toMillis();
419+
}
420+
System.out.println("Current wait time in milliseconds: " + currentWaitMillis);
421+
422+
// Ensure the wait time does not exceed maxWait
423+
if (currentWaitMillis > maxWait.toMillis()) {
424+
currentWaitMillis = maxWait.toMillis();
425+
}
426+
427+
// Break if adding another wait period would exceed the duration
428+
if (totalWaitMillis + currentWaitMillis > durationMillis) {
429+
System.out.println("Breaking the loop as the total wait time would exceed the duration");
430+
break;
431+
}
432+
433+
totalWaitMillis += currentWaitMillis;
434+
System.out.println("Total wait time in milliseconds: " + totalWaitMillis);
435+
retries++;
436+
System.out.println("Number of retries: " + retries);
437+
438+
if (retries > 100) {
439+
System.out.println("Breaking the loop as the number of retries exceeds 100");
440+
break;
406441
}
407442
}
443+
408444
this.maxRetries = retries;
409-
return this;
445+
System.out.println("Max retries set to: " + this.maxRetries);
410446
}
411447

412448
@Override
@@ -462,6 +498,10 @@ public RetryFactory createFactory() {
462498

463499
@Override
464500
public Retry createRetry() {
501+
if (retriesForDuration != null) {
502+
calculateRetriesForDuration();
503+
}
504+
this.modifiable = false;
465505
return new Retry(maxRetries, initialWait, waitIncrement, maxWait, logInterval, backOffFactor);
466506
}
467507

core/src/test/java/org/apache/accumulo/core/util/RetryTest.java

+74-18
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.easymock.EasyMock;
3535
import org.junit.jupiter.api.BeforeEach;
3636
import org.junit.jupiter.api.Test;
37+
import org.junit.jupiter.api.Timeout;
3738
import org.slf4j.Logger;
3839
import org.slf4j.LoggerFactory;
3940

@@ -352,36 +353,91 @@ public void testInfiniteRetryWithBackoff() throws InterruptedException {
352353
}
353354
}
354355

356+
@Timeout(30)
355357
@Test
356358
public void testRetriesForDuration() throws InterruptedException {
357-
final TimeUnit unit = MILLISECONDS;
358-
final long duration = 10_000;
359+
final Duration duration = Duration.ofSeconds(10);
360+
final Duration increment = Duration.ofMillis(500);
361+
Retry retry = Retry.builder().retriesForDuration(duration).retryAfter(Duration.ofMillis(0))
362+
.incrementBy(increment).maxWait(increment).backOffFactor(1)
363+
.logInterval(Duration.ofMinutes(3)).createRetry();
364+
365+
// with the backoff factor set to 1 and with max wait set to the increment value, the expected
366+
// number of retries is the duration divided by the increment
367+
long expectedRetries = duration.dividedBy(increment);
368+
369+
assertExpectedRetries(retry, duration, expectedRetries);
370+
}
371+
372+
@Timeout(30)
373+
@Test
374+
public void testRetriesForDurationWithInitial() throws InterruptedException {
375+
final Duration duration = Duration.ofSeconds(10);
376+
final Duration increment = Duration.ofMillis(500);
377+
final Duration initialWait = Duration.ofMillis(400);
378+
Retry retry =
379+
Retry.builder().retriesForDuration(duration).retryAfter(initialWait).incrementBy(increment)
380+
.maxWait(increment).backOffFactor(1).logInterval(Duration.ofMinutes(3)).createRetry();
381+
382+
// with the backoff factor set to 1 and with max wait set to the increment value, the expected
383+
// number of retries is the duration minus the initial wait divided by the increment
384+
long expectedRetries = duration.minus(initialWait).dividedBy(increment);
385+
386+
assertExpectedRetries(retry, duration, expectedRetries);
387+
}
388+
389+
@Timeout(30)
390+
@Test
391+
public void testRetriesForDuration2() throws InterruptedException {
392+
final Duration increment = Duration.ofMillis(100);
393+
final double backOffFactor = 2;
394+
Duration maxWait = Duration.ofMillis(800); // Allows for 4 doublings 100- > 200 -> 400 -> 800
395+
final Duration initialWait = Duration.ofMillis(50);
396+
397+
// Total duration accounting for the initial wait, the max wait, and the backoff factor
398+
Duration duration =
399+
initialWait.plus(Duration.ofMillis(100 + 200 + 400 + 800 + 800 + 800 + 800));
400+
401+
// count up the expected retries from the calculation above
402+
long expectedRetries = 7;
403+
404+
Retry retry = Retry.builder().retriesForDuration(duration).retryAfter(initialWait)
405+
.incrementBy(increment).maxWait(maxWait).backOffFactor(backOffFactor)
406+
.logInterval(Duration.ofMinutes(3)).createRetry();
359407

360-
Retry retry = Retry.builder().retriesForDuration(duration, unit).retryAfter(100, unit)
361-
.incrementBy(100, unit).maxWait(500, unit).backOffFactor(1.5).logInterval(3, MINUTES)
362-
.createRetry();
408+
assertExpectedRetries(retry, duration, expectedRetries);
409+
}
363410

364-
long totalTime = 0;
365-
long expectedRetries = 0;
411+
private static void assertExpectedRetries(Retry retry, Duration duration, long expectedRetries)
412+
throws InterruptedException {
413+
Duration totalTime = Duration.ZERO;
366414

415+
int iterations = 0;
367416
// While the total wait time is less than the duration and there are retries left
368-
while (totalTime <= duration && retry.canRetry()) {
369-
totalTime += retry.getCurrentWait();
370-
if (totalTime <= duration) {
417+
while (retry.canRetry() && (totalTime.compareTo(duration) <= 0)) {
418+
iterations++;
419+
totalTime = totalTime.plus(retry.getCurrentWait());
420+
if (totalTime.compareTo(duration) <= 0) {
371421
retry.useRetry();
372-
expectedRetries++;
373422
if (retry.canRetry()) {
374-
try {
375-
retry.waitForNextAttempt(log, "Iteration " + expectedRetries);
376-
} catch (IllegalArgumentException | InterruptedException e) {
377-
log.error("Failed on iteration: {}", expectedRetries, e);
378-
throw e;
379-
}
423+
log.info("Iteration {} - Total time: {}ms - Current wait: {}ms", iterations,
424+
totalTime.toMillis(), retry.getCurrentWait().toMillis());
425+
retry.waitForNextAttempt(log, "testRetriesForDuration");
380426
}
381427
}
382428
}
383429

384-
assertEquals(expectedRetries, retry.retriesCompleted());
430+
assertEquals(expectedRetries, retry.retriesCompleted(),
431+
() -> getErrorMessage(expectedRetries, retry));
432+
}
433+
434+
static String getErrorMessage(long expectedRetries, Retry retry) {
435+
return String.format(
436+
"Expected %d retries, but only completed %d retries. Retry parameters: maxRetries=%dms, startWait=%sms, maxWait=%sms, waitIncrement=%sms, backOffFactor=%f, logInterval=%sms",
437+
expectedRetries, retry.retriesCompleted(), retry.getMaxRetries(),
438+
retry.getCurrentWait().toMillis(), retry.getMaxWait().toMillis(),
439+
retry.getWaitIncrement().toMillis(), retry.getWaitFactor(),
440+
retry.getLogInterval().toMillis());
385441
}
386442

387443
}

0 commit comments

Comments
 (0)