A multiplatform higher-order function for retrying operations that may fail.
This release introduces the kotlin-retry-result
subproject, leveraging the Result<V, E>
type from kotlin-result
for code that must be retried but returns an Err
instead of throwing an Exception
.
See the README section for a full example.
To install the new extension library:
repositories {
mavenCentral()
}
dependencies {
implementation("com.michael-bull.kotlin-retry:kotlin-retry:2.0.1")
implementation("com.michael-bull.kotlin-retry:kotlin-retry-result:2.0.1")
}
Version 2.0.0 represents a major breaking change in the form of an underlying rewrite and support all three tiers of Kotlin/Native.
The project has been converted to be multiplatform, matching compatibility with kotlinx-coroutines
support for all three tiers of Kotlin/Native.
The full list of platform targets can be found in the kotlin-conventions
Gradle plugin.
The implementation no longer relies on CoroutineContext
to persist the state of retry attempts. Instead, the RetryPolicy
(now a functional interface) is passed the attempt that just failed. This makes it clear on the callsite what information about the failed attempt is available, instead of being expected to know what fields are in the coroutineContext.retryStatus
.
The attempt argument for a RetryPolicy
contains:
failure
as a generic.
retry
or runRetrying
function, this will be Throwable
, however leaves future oppurtunity for a Result
-backed retry
function that uses an Err
instead of a Throwable
.0
means the invocation of the block
has failed and no attempts have been made to retry invoking the block
.1
means the invocation of the block
has failed, and one attempt to retry invoking the block
also failed - totalling two attempts and one retry.delay
between the failed attempt and the attempt prior.cumulativeDelay
across all attempts.Before:
public fun binaryExponentialBackoff(min: Long, max: Long): RetryPolicy<*> {
require(min > 0) { "min must be positive, but was $min" }
require(max > 0) { "max must be positive, but was $max" }
return {
val attempt = coroutineContext.retryStatus.attempt
val delay = min(max, min saturatedMultiply attempt.binaryExponential())
RetryAfter(delay)
}
}
After:
public fun <E> binaryExponentialBackoff(min: Long, max: Long): RetryPolicy<E> {
require(min > 0) { "min must be positive, but was $min" }
require(max > 0) { "max must be positive, but was $max" }
return RetryPolicy { attempt ->
val delay = min(max, min saturatedMultiply attempt.number.binaryExponential())
RetryAfter(delay)
}
}
Various functions have been added, renamed, or their signature otherwise changed to comply with the rewrite.
Renamed
maxDelay
-> delayAtMost
limitAttempts
-> stopAtAttempts
limitByDelay
-> stopAtDelay
limitByCumulativeDelay
-> stopAtCumulativeDelay
retryIf
-> continueIf
retryUnless
-> continueUnless
Added
delayAtLeast
delayAtMost
delayIn
stopAtRetries
stopIf
stopUnless
RetryPolicy
that only retries if a given predicate is not satisfied. Analogous to takeUnless
in the stdlib.retryIf
function (201a368350020b5d54cc112d0a9dd9024df467fb) by @gnefedev