Last active
September 25, 2019 23:01
-
-
Save krishnabhargav/48442d81ad09b9c50dd4a9ad067b0735 to your computer and use it in GitHub Desktop.
An attempt to support retry functions in Kotlin
This file contains hidden or 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
import Retry.retry | |
import Retry.retryForever | |
import kotlinx.coroutines.* | |
import kotlin.math.pow | |
import kotlin.math.roundToLong | |
import kotlin.random.Random | |
private fun funcThatFails(i: Int): Int { | |
if (i > 100 || Random.nextBoolean()) | |
throw Exception("Application failed") | |
return i | |
} | |
fun main() { | |
println(retryForever { funcThatFails(10) }) | |
println(retryForever { funcThatFails(10) }) | |
println(retry( | |
Retry.Strategy( | |
backoffStrategy = Retry.BackoffStrategy.polynomialOneSecond, | |
maxAttempts = 2 | |
) | |
) { funcThatFails(101) }) | |
println(retry { funcThatFails(101) }) | |
} | |
object Retry { | |
class RetryLimitReachedException(maxAttempts: Int) : | |
Exception("Retry Limit reached. Max Attempts allowed = $maxAttempts") | |
sealed class BackoffStrategy { | |
abstract fun valueInMs(attempt: Int): Long | |
data class Fixed(val delayInMs: Long) : BackoffStrategy() { | |
override fun valueInMs(attempt: Int): Long = delayInMs | |
} | |
data class RandomizedExponential(val lowerBoundInMs: Long = 0, val upperBoundInMs: Long) : BackoffStrategy() { | |
override fun valueInMs(attempt: Int): Long = attempt * Random.nextLong(lowerBoundInMs, upperBoundInMs) | |
} | |
data class Polynomial(val delayExponent: Long) : BackoffStrategy() { | |
override fun valueInMs(attempt: Int): Long = delayExponent.toDouble().pow(attempt).roundToLong() | |
} | |
companion object { | |
val fixedOneSecond = Fixed(1000) | |
val randomOneSecond = RandomizedExponential(0, 1000) | |
val polynomialOneSecond = Polynomial(1000) | |
} | |
} | |
class Strategy( | |
val maxAttempts: Int? = null, | |
val backoffStrategy: BackoffStrategy, | |
val maxDelay: Long = Long.MAX_VALUE | |
) { | |
suspend fun wait(attemptNumber: Int): Boolean { | |
if (maxAttempts == null || attemptNumber < maxAttempts) { | |
delay((backoffStrategy.valueInMs(attemptNumber)).coerceAtMost(maxDelay)) | |
return true | |
} | |
return false | |
} | |
} | |
val default = Strategy(maxAttempts = 10, backoffStrategy = BackoffStrategy.randomOneSecond) | |
val forever = Strategy(backoffStrategy = BackoffStrategy.fixedOneSecond) | |
fun retry(strategy: Strategy = default, f: () -> Any): Any = runBlocking { | |
var attemptNumber = 0 | |
while (true) { | |
if (strategy.wait(attemptNumber)) { | |
try { | |
return@runBlocking f() | |
} catch (e: Exception) { | |
println("Handled exception : $e") | |
attemptNumber++ | |
} | |
} else throw RetryLimitReachedException(attemptNumber) | |
} | |
} | |
fun retryForever(f: () -> Any): Any = retry(forever, f) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment