TP-54316 | Retry Mechanism System (#9363)
Co-authored-by: Shivam Goyal <shivam.goyal@navi.com>
This commit is contained in:
@@ -83,6 +83,8 @@ dependencies {
|
||||
|
||||
testImplementation libs.junit
|
||||
|
||||
testImplementation libs.kotlinx.coroutines.test
|
||||
|
||||
testImplementation libs.mockk
|
||||
}
|
||||
|
||||
|
||||
85
navi-base/src/main/java/com/navi/base/utils/Retry.kt
Normal file
85
navi-base/src/main/java/com/navi/base/utils/Retry.kt
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.base.utils
|
||||
|
||||
import kotlin.math.pow
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* Linear with retryIntervalInSeconds of 3 secs means at T = 0, 3, 6, 9, 12, ... execute will be
|
||||
* called
|
||||
*
|
||||
* Exponential with retryIntervalInSeconds of 3 secs means 0, 3, 9, 27, 81, ...
|
||||
*/
|
||||
enum class RetryStrategy {
|
||||
LINEAR,
|
||||
EXPONENTIAL
|
||||
}
|
||||
|
||||
/**
|
||||
* For retryCount n means, execute will be called at max n+1 times. 1 time for normal execution &
|
||||
* then n times for retry
|
||||
*
|
||||
* @param retryCount should be >= 0. It says after 1st execution, how many times should we retry
|
||||
*/
|
||||
suspend fun <T> retry(
|
||||
retryCount: Int = 3,
|
||||
retryIntervalInSeconds: Double = 0.0,
|
||||
retryStrategy: RetryStrategy = RetryStrategy.LINEAR,
|
||||
execute: suspend () -> T,
|
||||
shouldRetry: (T) -> Boolean
|
||||
): T {
|
||||
|
||||
if (retryCount < 1) throw IllegalArgumentException("retryCount should be >= 0")
|
||||
|
||||
if (retryIntervalInSeconds < 0)
|
||||
throw IllegalArgumentException("retryIntervalInSeconds should be >= 0")
|
||||
|
||||
// Default execute
|
||||
var result = execute()
|
||||
|
||||
// If result is success & retry is not needed, return result
|
||||
if (!shouldRetry(result)) {
|
||||
return result
|
||||
}
|
||||
|
||||
// Default execute has failed & retry should start
|
||||
for (i in 1..retryCount) {
|
||||
delay(
|
||||
getDelayDuration(
|
||||
retryIntervalInSeconds = retryIntervalInSeconds,
|
||||
retryStrategy = retryStrategy,
|
||||
retryCount = i
|
||||
)
|
||||
)
|
||||
|
||||
result = execute()
|
||||
|
||||
// If retry is success, return retryResult
|
||||
if (!shouldRetry(result)) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// All retry count has exhausted, return the last result
|
||||
return result
|
||||
}
|
||||
|
||||
/** This function is made internal only for testing purpose & is not intended to be used directly */
|
||||
internal fun getDelayDuration(
|
||||
retryIntervalInSeconds: Double,
|
||||
retryStrategy: RetryStrategy,
|
||||
retryCount: Int
|
||||
): Duration {
|
||||
return when (retryStrategy) {
|
||||
RetryStrategy.LINEAR -> (retryIntervalInSeconds * retryCount).seconds
|
||||
RetryStrategy.EXPONENTIAL -> (retryIntervalInSeconds.pow(retryCount)).seconds
|
||||
}
|
||||
}
|
||||
169
navi-base/src/test/java/com/navi/base/utils/RetryUtilTest.kt
Normal file
169
navi-base/src/test/java/com/navi/base/utils/RetryUtilTest.kt
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.base.utils
|
||||
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class RetryUtilTest {
|
||||
|
||||
@Test
|
||||
fun `linear retry strategy delay duration test`() = runTest {
|
||||
var retryIntervalInSeconds = 3.0
|
||||
|
||||
assertEquals(3.seconds, getDelayDuration(retryIntervalInSeconds, RetryStrategy.LINEAR, 1))
|
||||
assertEquals(6.seconds, getDelayDuration(retryIntervalInSeconds, RetryStrategy.LINEAR, 2))
|
||||
assertEquals(9.seconds, getDelayDuration(retryIntervalInSeconds, RetryStrategy.LINEAR, 3))
|
||||
|
||||
retryIntervalInSeconds = 0.0
|
||||
|
||||
assertEquals(0.seconds, getDelayDuration(retryIntervalInSeconds, RetryStrategy.LINEAR, 1))
|
||||
assertEquals(0.seconds, getDelayDuration(retryIntervalInSeconds, RetryStrategy.LINEAR, 2))
|
||||
assertEquals(0.seconds, getDelayDuration(retryIntervalInSeconds, RetryStrategy.LINEAR, 3))
|
||||
|
||||
retryIntervalInSeconds = 1.0
|
||||
|
||||
assertEquals(1.seconds, getDelayDuration(retryIntervalInSeconds, RetryStrategy.LINEAR, 1))
|
||||
assertEquals(2.seconds, getDelayDuration(retryIntervalInSeconds, RetryStrategy.LINEAR, 2))
|
||||
assertEquals(3.seconds, getDelayDuration(retryIntervalInSeconds, RetryStrategy.LINEAR, 3))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exponential retry strategy delay duration test`() = runTest {
|
||||
var retryIntervalInSeconds = 3.0
|
||||
|
||||
assertEquals(
|
||||
3.seconds,
|
||||
getDelayDuration(retryIntervalInSeconds, RetryStrategy.EXPONENTIAL, 1)
|
||||
)
|
||||
assertEquals(
|
||||
9.seconds,
|
||||
getDelayDuration(retryIntervalInSeconds, RetryStrategy.EXPONENTIAL, 2)
|
||||
)
|
||||
assertEquals(
|
||||
27.seconds,
|
||||
getDelayDuration(retryIntervalInSeconds, RetryStrategy.EXPONENTIAL, 3)
|
||||
)
|
||||
|
||||
retryIntervalInSeconds = 0.0
|
||||
|
||||
assertEquals(
|
||||
0.seconds,
|
||||
getDelayDuration(retryIntervalInSeconds, RetryStrategy.EXPONENTIAL, 1)
|
||||
)
|
||||
assertEquals(
|
||||
0.seconds,
|
||||
getDelayDuration(retryIntervalInSeconds, RetryStrategy.EXPONENTIAL, 2)
|
||||
)
|
||||
assertEquals(
|
||||
0.seconds,
|
||||
getDelayDuration(retryIntervalInSeconds, RetryStrategy.EXPONENTIAL, 3)
|
||||
)
|
||||
|
||||
retryIntervalInSeconds = 1.0
|
||||
|
||||
assertEquals(
|
||||
1.seconds,
|
||||
getDelayDuration(retryIntervalInSeconds, RetryStrategy.EXPONENTIAL, 1)
|
||||
)
|
||||
assertEquals(
|
||||
3.seconds,
|
||||
getDelayDuration(retryIntervalInSeconds, RetryStrategy.EXPONENTIAL, 2)
|
||||
)
|
||||
assertEquals(
|
||||
9.seconds,
|
||||
getDelayDuration(retryIntervalInSeconds, RetryStrategy.EXPONENTIAL, 3)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test number of times execute is getting invoked for all execute failing & asking retry with retryCount as 3`() =
|
||||
runTest {
|
||||
var executeCount = 0
|
||||
val execute = {
|
||||
executeCount++
|
||||
1
|
||||
}
|
||||
val shouldRetry: (Any) -> Boolean = { true }
|
||||
|
||||
retry(
|
||||
retryCount = 3,
|
||||
retryIntervalInSeconds = 3.0,
|
||||
retryStrategy = RetryStrategy.LINEAR,
|
||||
execute = execute,
|
||||
shouldRetry = shouldRetry
|
||||
)
|
||||
|
||||
assertEquals(4, executeCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test number of times execute is getting invoked for 1st instance of execute getting success for retryCount as 3`() =
|
||||
runTest {
|
||||
var executeCount = 0
|
||||
val execute = {
|
||||
executeCount++
|
||||
1
|
||||
}
|
||||
val shouldRetry: (Any) -> Boolean = { false }
|
||||
|
||||
retry(
|
||||
retryCount = 3,
|
||||
retryIntervalInSeconds = 3.0,
|
||||
retryStrategy = RetryStrategy.LINEAR,
|
||||
execute = execute,
|
||||
shouldRetry = shouldRetry
|
||||
)
|
||||
|
||||
assertEquals(1, executeCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test number of times execute is getting invoked for 2nd instance of execute getting success for retryCount as 3`() =
|
||||
runTest {
|
||||
var executeCount = 0
|
||||
val execute = {
|
||||
executeCount++
|
||||
1
|
||||
}
|
||||
val shouldRetry: (Any) -> Boolean = { executeCount == 1 }
|
||||
|
||||
retry(
|
||||
retryCount = 3,
|
||||
retryIntervalInSeconds = 3.0,
|
||||
retryStrategy = RetryStrategy.LINEAR,
|
||||
execute = execute,
|
||||
shouldRetry = shouldRetry
|
||||
)
|
||||
|
||||
assertEquals(2, executeCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test number of times execute is getting invoked for 3rd instance of execute getting success for retryCount as 3`() =
|
||||
runTest {
|
||||
var executeCount = 0
|
||||
val execute = {
|
||||
executeCount++
|
||||
1
|
||||
}
|
||||
val shouldRetry: (Any) -> Boolean = { executeCount <= 2 }
|
||||
|
||||
retry(
|
||||
retryCount = 3,
|
||||
retryIntervalInSeconds = 3.0,
|
||||
retryStrategy = RetryStrategy.LINEAR,
|
||||
execute = execute,
|
||||
shouldRetry = shouldRetry
|
||||
)
|
||||
|
||||
assertEquals(3, executeCount)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user