This is an automated email from the ASF dual-hosted git repository.

domgarguilo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/main by this push:
     new 5d741657e7 Add duration based maxRetries option to Retry class (#4337)
5d741657e7 is described below

commit 5d741657e7d3f003e5c817963318e47c271f8e45
Author: Dom G <domgargu...@apache.org>
AuthorDate: Fri Mar 22 08:38:41 2024 -0400

    Add duration based maxRetries option to Retry class (#4337)
    
    * Add duration based maxRetries option to Retry class
---
 .../java/org/apache/accumulo/core/util/Retry.java  | 53 ++++++++++++
 .../org/apache/accumulo/core/util/RetryTest.java   | 98 ++++++++++++++++++++++
 2 files changed, 151 insertions(+)

diff --git a/core/src/main/java/org/apache/accumulo/core/util/Retry.java 
b/core/src/main/java/org/apache/accumulo/core/util/Retry.java
index 28659b376f..916537db3e 100644
--- a/core/src/main/java/org/apache/accumulo/core/util/Retry.java
+++ b/core/src/main/java/org/apache/accumulo/core/util/Retry.java
@@ -272,6 +272,12 @@ public class Retry {
      * @return this builder with the maximum number of retries set to the 
provided value
      */
     NeedsRetryDelay maxRetries(long max);
+
+    /**
+     * @return this builder with the maximum number of retries set to the 
number of retries that can
+     *         occur within the given duration
+     */
+    NeedsRetryDelay maxRetriesWithinDuration(Duration duration);
   }
 
   public interface NeedsRetryDelay {
@@ -358,6 +364,7 @@ public class Retry {
     private Duration waitIncrement;
     private Duration logInterval;
     private double backOffFactor = 1.5;
+    private Duration retriesForDuration = null;
 
     RetryFactoryBuilder() {}
 
@@ -381,6 +388,48 @@ public class Retry {
       return this;
     }
 
+    @Override
+    public NeedsRetryDelay maxRetriesWithinDuration(Duration duration) {
+      checkState();
+      Preconditions.checkArgument(!duration.isNegative(),
+          "Duration for retries must not be negative");
+      this.retriesForDuration = duration;
+      return this;
+    }
+
+    /**
+     * Calculate the maximum number of retries that can occur within {@link 
#retriesForDuration}
+     */
+    private void calculateRetriesWithinDuration() {
+      long numberOfRetries = 0;
+      long cumulativeWaitTimeMillis = 0;
+      long currentWaitTimeMillis = initialWait.toMillis();
+      final long retriesForDurationMillis = retriesForDuration.toMillis();
+
+      // set an upper bound for the number of retries
+      final long maxRetries = Duration.ofHours(1).toMillis();
+
+      while (cumulativeWaitTimeMillis + currentWaitTimeMillis <= 
retriesForDurationMillis
+          && numberOfRetries < maxRetries) {
+
+        cumulativeWaitTimeMillis += currentWaitTimeMillis;
+        numberOfRetries++;
+
+        if (backOffFactor > 1.0) {
+          currentWaitTimeMillis = (long) Math.ceil(currentWaitTimeMillis * 
backOffFactor);
+        } else {
+          currentWaitTimeMillis += waitIncrement.toMillis();
+        }
+
+        if (currentWaitTimeMillis > maxWait.toMillis()) {
+          currentWaitTimeMillis = maxWait.toMillis(); // Ensure wait time does 
not exceed maxWait
+        }
+
+      }
+
+      this.maxRetries = numberOfRetries;
+    }
+
     @Override
     public NeedsTimeIncrement retryAfter(Duration duration) {
       checkState();
@@ -434,6 +483,10 @@ public class Retry {
 
     @Override
     public Retry createRetry() {
+      if (retriesForDuration != null) {
+        calculateRetriesWithinDuration();
+      }
+      this.modifiable = false;
       return new Retry(maxRetries, initialWait, waitIncrement, maxWait, 
logInterval, backOffFactor);
     }
 
diff --git a/core/src/test/java/org/apache/accumulo/core/util/RetryTest.java 
b/core/src/test/java/org/apache/accumulo/core/util/RetryTest.java
index 45992ec9b5..c05189a514 100644
--- a/core/src/test/java/org/apache/accumulo/core/util/RetryTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/util/RetryTest.java
@@ -33,6 +33,7 @@ import org.apache.accumulo.core.util.Retry.NeedsTimeIncrement;
 import org.apache.accumulo.core.util.Retry.RetryFactory;
 import org.easymock.EasyMock;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -351,4 +352,101 @@ public class RetryTest {
       }
     }
   }
+
+  @Nested
+  public class MaxRetriesWithinDuration {
+
+    @Test
+    public void noIncrement() {
+      Duration retriesForDuration = Duration.ofSeconds(3);
+      Duration retryAfter = Duration.ofMillis(100);
+      Retry retry = 
Retry.builder().maxRetriesWithinDuration(retriesForDuration)
+          
.retryAfter(retryAfter).incrementBy(Duration.ofMillis(0)).maxWait(Duration.ofMillis(1000))
+          .backOffFactor(1.0).logInterval(Duration.ofMinutes(3)).createRetry();
+
+      // with no increment, the number of retries should be the duration 
divided by the retryAfter
+      // (which is used as the initial wait and in this case does not change)
+      long expectedRetries = retriesForDuration.dividedBy(retryAfter);
+      assertEquals(expectedRetries, retry.getMaxRetries());
+
+      // try again with lots of expected retries
+      retriesForDuration = Duration.ofSeconds(30);
+      retryAfter = Duration.ofMillis(10);
+      retry = 
Retry.builder().maxRetriesWithinDuration(retriesForDuration).retryAfter(retryAfter)
+          
.incrementBy(Duration.ofMillis(0)).maxWait(Duration.ofMillis(1000)).backOffFactor(1.0)
+          .logInterval(Duration.ofMinutes(3)).createRetry();
+
+      expectedRetries = retriesForDuration.dividedBy(retryAfter);
+      assertEquals(expectedRetries, retry.getMaxRetries());
+    }
+
+    @Test
+    public void withIncrement() {
+      final Duration retriesForDuration = Duration.ofMillis(1500);
+      final Duration retryAfter = Duration.ofMillis(100);
+      final Duration increment = Duration.ofMillis(100);
+
+      Retry retry = 
Retry.builder().maxRetriesWithinDuration(retriesForDuration)
+          
.retryAfter(retryAfter).incrementBy(increment).maxWait(Duration.ofMillis(1000))
+          .backOffFactor(1.0).logInterval(Duration.ofMinutes(3)).createRetry();
+
+      // the max retries should be calculated like this:
+      // 1. 100
+      // 2. 100 + 100 = 200
+      // 3. 200 + 100 = 300
+      // 4. 300 + 100 = 400
+      // 5. 400 + 100 = 500
+
+      // 100 + 200 + 300 + 400 + 500 = 1500
+
+      assertEquals(5, retry.getMaxRetries());
+    }
+
+    @Test
+    public void withBackoffFactorAndMaxWait() {
+      final Duration retriesForDuration = Duration.ofSeconds(4);
+      final Duration retryAfter = Duration.ofMillis(100);
+      Retry retry = 
Retry.builder().maxRetriesWithinDuration(retriesForDuration)
+          
.retryAfter(retryAfter).incrementBy(Duration.ofMillis(0)).maxWait(Duration.ofMillis(500))
+          .backOffFactor(1.5).logInterval(Duration.ofMinutes(3)).createRetry();
+
+      // max retries should be calculated like this:
+      // 1. 100
+      // 2. 100 * 1.5 = 150
+      // 3. 150 * 1.5 = 225
+      // 4. 225 * 1.5 = 337
+      // 5. 337 * 1.5 = 505 (which is greater than the max wait of 500 so its 
capped)
+
+      // 100 + 150 + 225 + 337 + 500 + 500 + 500 + 500 + 500 + 500 = 3812
+      assertEquals(10, retry.getMaxRetries());
+    }
+
+    @Test
+    public void smallDuration() {
+      Duration retriesForDuration = Duration.ofMillis(0);
+      final Duration retryAfter = Duration.ofMillis(100);
+      Retry retry = 
Retry.builder().maxRetriesWithinDuration(retriesForDuration)
+          
.retryAfter(retryAfter).incrementBy(Duration.ofMillis(0)).maxWait(Duration.ofMillis(500))
+          .backOffFactor(1.5).logInterval(Duration.ofMinutes(3)).createRetry();
+      assertEquals(0, retry.getMaxRetries());
+
+      retriesForDuration = Duration.ofMillis(99);
+      assertTrue(retriesForDuration.compareTo(retryAfter) < 0);
+      retry = 
Retry.builder().maxRetriesWithinDuration(retriesForDuration).retryAfter(retryAfter)
+          
.incrementBy(Duration.ofMillis(0)).maxWait(Duration.ofMillis(500)).backOffFactor(1.5)
+          .logInterval(Duration.ofMinutes(3)).createRetry();
+      assertEquals(0, retry.getMaxRetries());
+    }
+
+    @Test
+    public void equalDurationAndInitialWait() {
+      final Duration retriesForDuration = Duration.ofMillis(100);
+      final Duration retryAfter = Duration.ofMillis(100);
+      assertEquals(0, retriesForDuration.compareTo(retryAfter));
+      Retry retry = 
Retry.builder().maxRetriesWithinDuration(retriesForDuration)
+          
.retryAfter(retryAfter).incrementBy(Duration.ofMillis(0)).maxWait(Duration.ofMillis(500))
+          .backOffFactor(1.5).logInterval(Duration.ofMinutes(3)).createRetry();
+      assertEquals(1, retry.getMaxRetries());
+    }
+  }
 }

Reply via email to