Skip to content

Instantly share code, notes, and snippets.

@krishnabhargav
Last active September 25, 2019 23:01
Show Gist options
  • Save krishnabhargav/48442d81ad09b9c50dd4a9ad067b0735 to your computer and use it in GitHub Desktop.
Save krishnabhargav/48442d81ad09b9c50dd4a9ad067b0735 to your computer and use it in GitHub Desktop.
An attempt to support retry functions in Kotlin
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