Skip to content

Commit

Permalink
added retryRaise and retryEither functions
Browse files Browse the repository at this point in the history
  • Loading branch information
akotynski committed Feb 12, 2024
1 parent d42429f commit dec5037
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
public final class ScheduleEitherKt {
public static final fun retry-YL6hcnA (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun retryEither-4AuOtiA (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun retryRaise-4AuOtiA (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class arrow/resilience/CircuitBreaker {
public static final field Companion Larrow/resilience/CircuitBreaker$Companion;
public synthetic fun <init> (Ljava/util/concurrent/atomic/AtomicReference;JDJLkotlin/time/TimeSource;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@file:OptIn(ExperimentalTypeInference::class)

import arrow.core.Either
import arrow.core.raise.Raise
import arrow.core.raise.either
import arrow.core.raise.fold
import arrow.resilience.Schedule
import arrow.resilience.ScheduleStep
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlin.experimental.ExperimentalTypeInference
import kotlin.time.Duration

/**
* Retries [action] using any [Error] that occurred as the input to the [Schedule].
* It will return the last [Error] if the [Schedule] is exhausted, and ignores the output of the [Schedule].
*/
public suspend inline fun <Error, Result, Output> Schedule<Error, Output>.retryRaise(
@BuilderInference action: Raise<Error>.() -> Result,
): Either<Error, Result> = either {
retry(this@retryRaise, action)
}

/**
* Retries [action] using any [Error] that occurred as the input to the [Schedule].
* It will return the last [Error] if the [Schedule] is exhausted, and ignores the output of the [Schedule].
*/
public suspend inline fun <Error, Result, Output> Schedule<Error, Output>.retryEither(
@BuilderInference action: () -> Either<Error, Result>,
): Either<Error, Result> = retryRaise {
action().bind()
}

/**
* Retries [action] using any [Error] that occurred as the input to the [Schedule].
* It will return the last [Error] if the [Schedule] is exhausted, and ignores the output of the [Schedule].
*/
public suspend inline fun <Error, Result, Output> Raise<Error>.retry(
schedule: Schedule<Error, Output>,
@BuilderInference action: Raise<Error>.() -> Result,
): Result {
var step: ScheduleStep<Error, Output> = schedule.step

while (true) {
currentCoroutineContext().ensureActive()
fold(
action,
recover = { error ->
when (val decision = step(error)) {
is Schedule.Decision.Continue -> {
if (decision.delay != Duration.ZERO) delay(decision.delay)
step = decision.step
}

is Schedule.Decision.Done -> raise(error)
}
},
transform = { result ->
return result
},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package arrow.resilience

import arrow.atomic.AtomicLong
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import kotlinx.coroutines.test.TestResult
import kotlinx.coroutines.test.runTest
import retryEither
import retryRaise
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.seconds

class ScheduleEitherTest {

@Test
fun retryRaiseIsStackSafe(): TestResult = runTest(timeout = 1.seconds) {
val count = AtomicLong(0)
val iterations = stackSafeIteration().toLong()

suspend fun increment() {
count.incrementAndGet()
}

val result = Schedule.recurs<CustomError>(iterations).retryRaise {
increment()
raise(CustomError)
}

assertTrue { result is Either.Left }
assertEquals(iterations + 1, count.get())
}

@Test
fun retryRaiseSucceedsIfErrorIsNotRaised(): TestResult = runTest {
val result = Schedule.recurs<CustomError>(0).retryRaise { 1 }

assertTrue { result is Either.Right && result.value == 1 }
}

@Test
fun retryEitherIsStackSafe(): TestResult = runTest {
val count = AtomicLong(0)
val iterations = stackSafeIteration().toLong()

suspend fun increment() {
count.incrementAndGet()
}

val result = Schedule.recurs<CustomError>(iterations).retryEither {
increment()
CustomError.left()
}

assertTrue { result is Either.Left }
assertEquals(iterations + 1, count.get())
}

@Test
fun retryEitherSucceedsIfErrorIsNotRaised(): TestResult = runTest {
val result = Schedule.recurs<CustomError>(0).retryEither { 1.right() }

assertTrue { result is Either.Right && result.value == 1 }
}
}

private object CustomError

0 comments on commit dec5037

Please sign in to comment.