-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement exponential retry mechanism for handling network erro…
…rs (#468) * feat: implement exponential backoff to handle network error * feat: add initialDelay logic * test: add ExponentialBackOffTest.java * chore: address sonarcloud warning Replace java.util.Random with java.security.SecureRandom.
- Loading branch information
1 parent
0711b37
commit e07e2cf
Showing
4 changed files
with
138 additions
and
2 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
60 changes: 60 additions & 0 deletions
60
core/src/main/java/com/rudderstack/android/sdk/core/util/ExponentialBackOff.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,60 @@ | ||
package com.rudderstack.android.sdk.core.util; | ||
|
||
import androidx.annotation.VisibleForTesting; | ||
|
||
import java.security.SecureRandom; | ||
|
||
/** | ||
* This class implements an exponential backoff strategy with jitter for handling retries. | ||
* It allows for configurable maximum delay and includes methods to calculate the next delay | ||
* with jitter and reset the backoff attempts. | ||
* When the calculated delay reaches or exceeds the maximum delay limit, the backoff resets | ||
* and starts again from beginning. | ||
*/ | ||
public class ExponentialBackOff { | ||
private int attempt = 0; | ||
private final int maxDelayInSecs; | ||
private final SecureRandom random; | ||
|
||
/** | ||
* Constructor to initialize the ExponentialBackOff with a maximum delay. | ||
* | ||
* @param maxDelayInSecs Maximum delay in seconds for the backoff. | ||
*/ | ||
public ExponentialBackOff(int maxDelayInSecs) { | ||
this.maxDelayInSecs = maxDelayInSecs; | ||
this.random = new SecureRandom(); | ||
} | ||
|
||
/** | ||
* Calculates the next delay with exponential backoff and jitter. | ||
* | ||
* @return The next delay in milliseconds. | ||
*/ | ||
public long nextDelayInMillis() { | ||
int base = 2; | ||
int initialDelayInSecs = 3; | ||
long delayInSecs = (long) (initialDelayInSecs * Math.pow(base, attempt++)); | ||
long exponentialIntervalInSecs = Math.min(maxDelayInSecs, withJitter(delayInSecs)); | ||
|
||
// Reset the backoff if the delay reaches or exceeds the maximum limit | ||
if (exponentialIntervalInSecs >= maxDelayInSecs) { | ||
resetBackOff(); | ||
} | ||
|
||
return exponentialIntervalInSecs * 1000; | ||
} | ||
|
||
@VisibleForTesting | ||
protected long withJitter(long delayInSecs) { | ||
long jitter = random.nextInt((int) delayInSecs); | ||
return delayInSecs + jitter; | ||
} | ||
|
||
/** | ||
* Resets the backoff attempt counter to 0. | ||
*/ | ||
public void resetBackOff() { | ||
attempt = 0; | ||
} | ||
} |
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
49 changes: 49 additions & 0 deletions
49
core/src/test/java/com/rudderstack/android/sdk/core/ExponentialBackOffTest.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,49 @@ | ||
package com.rudderstack.android.sdk.core; | ||
|
||
import org.junit.Before; | ||
import org.junit.Test; | ||
import static org.junit.Assert.*; | ||
import com.rudderstack.android.sdk.core.util.ExponentialBackOff; | ||
|
||
public class ExponentialBackOffTest { | ||
|
||
private ExponentialBackOff backOff; | ||
|
||
@Before | ||
public void setUp() { | ||
backOff = new ExponentialBackOff(5 * 60) { | ||
@Override | ||
protected long withJitter(long delayInSecs) { | ||
// Return a custom value for testing | ||
long jitter = 1; // 1 sec | ||
return delayInSecs + jitter; | ||
} | ||
}; | ||
} | ||
|
||
@Test | ||
public void testExponentialDelay() { | ||
// Check delays for the first few attempts | ||
long delay1 = backOff.nextDelayInMillis(); | ||
long delay2 = backOff.nextDelayInMillis(); | ||
long delay3 = backOff.nextDelayInMillis(); | ||
|
||
assertEquals((3 + 1) * 1000, delay1); // Expected delay: (initialDelayInSecs * Math.pow(base, attempt)) * 1000 | ||
assertEquals(((3 * 2) + 1) * 1000, delay2); // Expected delay: (initialDelayInSecs * Math.pow(base, attempt)) * 1000 | ||
assertEquals(((3 * 4) + 1) * 1000, delay3); // Expected delay: (initialDelayInSecs * Math.pow(base, attempt)) * 1000 | ||
} | ||
|
||
@Test | ||
public void testResetBackOff() { | ||
// Advance to exceed the max delay - 1 | ||
for (int i = 1; i <= 7; i++) { | ||
backOff.nextDelayInMillis(); // At the 7th attempt, the delay will be 3 * 2^6 + 1 = 193 sec | ||
} | ||
|
||
long delayAfterReset = backOff.nextDelayInMillis(); // Should reset backoff | ||
assertEquals(5 * 60 * 1000, delayAfterReset); // Expected delay: (initialDelayInSecs * Math.pow(base, attempt)) * 1000 | ||
|
||
long delayAfterReset2 = backOff.nextDelayInMillis(); // Should start from initial delay | ||
assertEquals((3 + 1) * 1000, delayAfterReset2); // Expected delay: (initialDelayInSecs * Math.pow(base, attempt)) * 1000 | ||
} | ||
} |