forked from ydb-platform/ydb-java-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
RetryPolicy Proposal
- Loading branch information
Showing
7 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package tech.ydb.core; | ||
|
||
/** | ||
* Recipes should use the configured error policy to decide how to retry | ||
* errors like unsuccessful {@link tech.ydb.core.StatusCode}. | ||
* | ||
* @author Aleksandr Gorshenin | ||
* @param <T> Type of errors to check | ||
*/ | ||
public interface ErrorPolicy<T> { | ||
|
||
/** | ||
* Returns true if the given value should be retried | ||
* | ||
* @param value value to check | ||
* @return true if value is retryable | ||
*/ | ||
boolean isRetryable(T value); | ||
|
||
/** | ||
* Returns true if the given exception should be retried | ||
* Usually exceptions are never retried, but some policies can implement more difficult logic | ||
* | ||
* @param ex exception to check | ||
* @return true if exception is retryable | ||
*/ | ||
default boolean isRetryable(Exception ex) { | ||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package tech.ydb.core; | ||
|
||
/** | ||
* Abstracts the policy to use when retrying some actions | ||
* | ||
* @author Aleksandr Gorshenin | ||
*/ | ||
public interface RetryPolicy { | ||
/** | ||
* Called when an operation is failed for some reason to determine if it should be retried. | ||
* And if so, returns the delay to make the next retry attempt after | ||
* | ||
* @param retryCount the number of times retried so far (0 the first time) | ||
* @param elapsedTimeMs the elapsed time in ms since the operation was attempted | ||
* @return delay for the next retry | ||
* <ul> | ||
* <li>Positive number N - operation must be retried in N milliseconds </li> | ||
* <li>Zero : operation must be retried immediately </li> | ||
* <li>Negative number : retry is not allowed, operation must be failed </li> | ||
* </ul> | ||
*/ | ||
long nextRetryMs(int retryCount, long elapsedTimeMs); | ||
} |
41 changes: 41 additions & 0 deletions
41
core/src/main/java/tech/ydb/core/retry/ExponentialBackoffRetry.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package tech.ydb.core.retry; | ||
|
||
import java.util.concurrent.ThreadLocalRandom; | ||
|
||
import tech.ydb.core.RetryPolicy; | ||
|
||
/** | ||
* | ||
* @author Aleksandr Gorshenin | ||
*/ | ||
public abstract class ExponentialBackoffRetry implements RetryPolicy { | ||
private final long backoffMs; | ||
private final int backoffCeiling; | ||
|
||
protected ExponentialBackoffRetry(long backoffMs, int backoffCeiling) { | ||
this.backoffMs = backoffMs; | ||
this.backoffCeiling = backoffCeiling; | ||
} | ||
|
||
protected long backoffTimeMillis(int retryNumber) { | ||
int slots = 1 << Math.min(retryNumber, backoffCeiling); | ||
long delay = backoffMs * slots; | ||
return delay + ThreadLocalRandom.current().nextLong(delay); | ||
} | ||
|
||
/** | ||
* Return current base of backoff delays | ||
* @return backoff base duration in milliseconds | ||
*/ | ||
public long getBackoffMillis() { | ||
return backoffMs; | ||
} | ||
|
||
/** | ||
* Return current maximal level of backoff exponent | ||
* @return maximal level of backoff exponent | ||
*/ | ||
public int getBackoffCeiling() { | ||
return backoffCeiling; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package tech.ydb.core.retry; | ||
|
||
import tech.ydb.core.RetryPolicy; | ||
|
||
/** | ||
* | ||
* @author Aleksandr Gorshenin | ||
*/ | ||
public class RetryForever implements RetryPolicy { | ||
private final long intervalMs; | ||
|
||
public RetryForever(long intervalMs) { | ||
this.intervalMs = intervalMs; | ||
} | ||
|
||
@Override | ||
public long nextRetryMs(int retryCount, long elapsedTimeMs) { | ||
return intervalMs; | ||
} | ||
|
||
/** | ||
* Return current interval of retries | ||
* @return retry interval in milliseconds | ||
*/ | ||
public long getIntervalMillis() { | ||
return intervalMs; | ||
} | ||
|
||
/** | ||
* Create new retry policy with specified retry interval | ||
* @param ms new interval in milliseconds | ||
* @return updated retry policy */ | ||
public RetryForever withIntervalMs(long ms) { | ||
return new RetryForever(ms); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package tech.ydb.core.retry; | ||
|
||
/** | ||
* | ||
* @author Aleksandr Gorshenin | ||
*/ | ||
public class RetryNTimes extends ExponentialBackoffRetry { | ||
private final int maxRetries; | ||
|
||
public RetryNTimes(int maxRetries, long backoffMs, int backoffCeiling) { | ||
super(backoffMs, backoffCeiling); | ||
this.maxRetries = maxRetries; | ||
} | ||
|
||
@Override | ||
public long nextRetryMs(int retryCount, long elapsedTimeMs) { | ||
if (retryCount >= maxRetries) { | ||
return -1; | ||
} | ||
return backoffTimeMillis(retryCount); | ||
} | ||
|
||
/** | ||
* Return maximal count of retries | ||
* @return maximal count of retries | ||
*/ | ||
public int getMaxRetries() { | ||
return maxRetries; | ||
} | ||
|
||
/** | ||
* Create new retry policy with specified max retries count | ||
* @param maxRetries new value of max count of retries | ||
* @return updated retry policy */ | ||
public RetryNTimes withMaxRetries(int maxRetries) { | ||
return new RetryNTimes(maxRetries, getBackoffMillis(), getBackoffCeiling()); | ||
} | ||
|
||
/** | ||
* Create new retry policy with specified backoff duration | ||
* @param ms new backoff duration in milliseconds | ||
* @return updated retry policy */ | ||
public RetryNTimes withBackoffMs(long ms) { | ||
return new RetryNTimes(maxRetries, ms, getBackoffCeiling()); | ||
} | ||
|
||
/** | ||
* Create new retry policy with specified backoff ceiling | ||
* @param ceiling new backoff ceiling | ||
* @return updated retry policy */ | ||
public RetryNTimes withBackoffCeiling(int ceiling) { | ||
return new RetryNTimes(maxRetries, getBackoffMillis(), ceiling); | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
core/src/main/java/tech/ydb/core/retry/RetryUntilElapsed.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package tech.ydb.core.retry; | ||
|
||
/** | ||
* | ||
* @author Aleksandr Gorshenin | ||
*/ | ||
public class RetryUntilElapsed extends ExponentialBackoffRetry { | ||
private final long maxElapsedMs; | ||
|
||
public RetryUntilElapsed(long maxElapsedMs, long backoffMs, int backoffCeiling) { | ||
super(backoffMs, backoffCeiling); | ||
this.maxElapsedMs = maxElapsedMs; | ||
} | ||
|
||
@Override | ||
public long nextRetryMs(int retryCount, long elapsedTimeMs) { | ||
if (maxElapsedMs <= elapsedTimeMs) { | ||
return -1; | ||
} | ||
long backoff = backoffTimeMillis(retryCount); | ||
return (elapsedTimeMs + backoff < maxElapsedMs) ? backoff : maxElapsedMs - elapsedTimeMs; | ||
} | ||
|
||
/** | ||
* Return maximal count of elapsed milliseconds | ||
* @return maximal count of elapsed milliseconds | ||
*/ | ||
public long getMaxElapsedMillis() { | ||
return maxElapsedMs; | ||
} | ||
|
||
/** | ||
* Create new retry policy with specified count of elapsed milliseconds | ||
* @param maxElapsedMs new value of max elapsed milliseconds | ||
* @return updated retry policy */ | ||
public RetryUntilElapsed withMaxElapsedMs(long maxElapsedMs) { | ||
return new RetryUntilElapsed(maxElapsedMs, getBackoffMillis(), getBackoffCeiling()); | ||
} | ||
|
||
/** | ||
* Create new retry policy with specified backoff duration | ||
* @param ms new backoff duration | ||
* @return new retry policy */ | ||
public RetryUntilElapsed withBackoffMs(long ms) { | ||
return new RetryUntilElapsed(maxElapsedMs, ms, getBackoffCeiling()); | ||
} | ||
|
||
/** | ||
* Create new retry policy with specified backoff ceiling | ||
* @param ceiling new backoff ceiling | ||
* @return new retry policy */ | ||
public RetryUntilElapsed withBackoffCeiling(int ceiling) { | ||
return new RetryUntilElapsed(maxElapsedMs, getBackoffMillis(), ceiling); | ||
} | ||
} |
119 changes: 119 additions & 0 deletions
119
core/src/test/java/tech/ydb/core/retry/RetryPoliciesTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package tech.ydb.core.retry; | ||
|
||
import org.junit.Assert; | ||
import org.junit.Test; | ||
|
||
/** | ||
* | ||
* @author Aleksandr Gorshenin | ||
*/ | ||
public class RetryPoliciesTest { | ||
|
||
@Test | ||
public void foreverRetryTest() { | ||
RetryForever policy = new RetryForever(1234); | ||
|
||
Assert.assertEquals(1234, policy.nextRetryMs(0, 0)); | ||
Assert.assertEquals(1234, policy.nextRetryMs(Integer.MAX_VALUE, 0)); | ||
Assert.assertEquals(1234, policy.nextRetryMs(0, Integer.MAX_VALUE)); | ||
Assert.assertEquals(1234, policy.nextRetryMs(Integer.MAX_VALUE, Integer.MAX_VALUE)); | ||
} | ||
|
||
@Test | ||
public void foreverUpdateTest() { | ||
RetryForever policy = new RetryForever(50); | ||
Assert.assertEquals(50, policy.getIntervalMillis()); | ||
Assert.assertEquals(50, policy.nextRetryMs(0, 0)); | ||
|
||
RetryForever updated = policy.withIntervalMs(150); | ||
Assert.assertEquals(150, updated.getIntervalMillis()); | ||
Assert.assertEquals(150, updated.nextRetryMs(0, 0)); | ||
} | ||
|
||
@Test | ||
public void zeroRetriesTest() { | ||
RetryNTimes policy = new RetryNTimes(0, 100, 3); | ||
|
||
Assert.assertEquals(-1, policy.nextRetryMs(0, 0)); | ||
Assert.assertEquals(-1, policy.nextRetryMs(Integer.MAX_VALUE, 0)); | ||
Assert.assertEquals(-1, policy.nextRetryMs(0, Integer.MAX_VALUE)); | ||
Assert.assertEquals(-1, policy.nextRetryMs(Integer.MAX_VALUE, Integer.MAX_VALUE)); | ||
} | ||
|
||
@Test | ||
public void nRetriesTest() { | ||
RetryNTimes policy = new RetryNTimes(5, 100, 3); | ||
|
||
assertDuration(100, 200, policy.nextRetryMs(0, 0)); | ||
assertDuration(200, 400, policy.nextRetryMs(1, 150)); | ||
assertDuration(400, 800, policy.nextRetryMs(2, 400)); | ||
assertDuration(800, 1600, policy.nextRetryMs(3, 1600)); | ||
assertDuration(800, 1600, policy.nextRetryMs(4, 2800)); | ||
|
||
Assert.assertEquals(-1, policy.nextRetryMs(5, 4000)); | ||
} | ||
|
||
@Test | ||
public void updateNRetriesTest() { | ||
RetryNTimes policy = new RetryNTimes(5, 100, 3); | ||
|
||
Assert.assertEquals(5, policy.getMaxRetries()); | ||
Assert.assertEquals(100, policy.getBackoffMillis()); | ||
Assert.assertEquals(3, policy.getBackoffCeiling()); | ||
assertDuration(100, 200, policy.nextRetryMs(0, 0)); | ||
|
||
RetryNTimes updated = policy.withMaxRetries(4).withBackoffMs(150).withBackoffCeiling(1); | ||
|
||
Assert.assertEquals(4, updated.getMaxRetries()); | ||
Assert.assertEquals(150, updated.getBackoffMillis()); | ||
Assert.assertEquals(1, updated.getBackoffCeiling()); | ||
assertDuration(150, 300, updated.nextRetryMs(0, 0)); | ||
} | ||
|
||
@Test | ||
public void zeroElapsedTest() { | ||
RetryUntilElapsed policy = new RetryUntilElapsed(0, 100, 3); | ||
|
||
Assert.assertEquals(-1, policy.nextRetryMs(0, 0)); | ||
Assert.assertEquals(-1, policy.nextRetryMs(Integer.MAX_VALUE, 0)); | ||
Assert.assertEquals(-1, policy.nextRetryMs(0, Integer.MAX_VALUE)); | ||
Assert.assertEquals(-1, policy.nextRetryMs(Integer.MAX_VALUE, Integer.MAX_VALUE)); | ||
} | ||
|
||
@Test | ||
public void untilElapsedTest() { | ||
RetryUntilElapsed policy = new RetryUntilElapsed(2500, 50, 3); | ||
|
||
assertDuration(50, 100, policy.nextRetryMs(0, 0)); | ||
assertDuration(100, 200, policy.nextRetryMs(1, 75)); | ||
assertDuration(200, 400, policy.nextRetryMs(2, 225)); | ||
assertDuration(400, 800, policy.nextRetryMs(3, 525)); | ||
assertDuration(400, 800, policy.nextRetryMs(4, 1125)); | ||
assertDuration(400, 800, policy.nextRetryMs(5, 1725)); | ||
|
||
Assert.assertEquals(175, policy.nextRetryMs(6, 2325)); | ||
Assert.assertEquals(-1, policy.nextRetryMs(7, 2500)); | ||
} | ||
|
||
@Test | ||
public void updateElapsedTest() { | ||
RetryUntilElapsed policy = new RetryUntilElapsed(2500, 50, 3); | ||
|
||
Assert.assertEquals(2500, policy.getMaxElapsedMillis()); | ||
Assert.assertEquals(50, policy.getBackoffMillis()); | ||
Assert.assertEquals(3, policy.getBackoffCeiling()); | ||
assertDuration(50, 100, policy.nextRetryMs(0, 0)); | ||
|
||
RetryUntilElapsed updated = policy.withMaxElapsedMs(1000).withBackoffMs(100).withBackoffCeiling(1); | ||
|
||
Assert.assertEquals(1000, updated.getMaxElapsedMillis()); | ||
Assert.assertEquals(100, updated.getBackoffMillis()); | ||
Assert.assertEquals(1, updated.getBackoffCeiling()); | ||
assertDuration(100, 200, updated.nextRetryMs(0, 0)); | ||
} | ||
|
||
private void assertDuration(long from, long to, long ms) { | ||
Assert.assertTrue(from <= ms); | ||
Assert.assertTrue(to >= ms); | ||
} | ||
} |