From afe431d71c2fa70f3b64a4eeafa836de44ede253 Mon Sep 17 00:00:00 2001 From: aleksanderkotbury Date: Fri, 9 Feb 2024 17:14:43 +0100 Subject: [PATCH] added retryRaise and retryEither functions --- .../kotlin/arrow/resilience/ScheduleEither.kt | 38 +++++++++++ .../arrow/resilience/ScheduleEitherTest.kt | 68 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 arrow-libs/resilience/arrow-resilience/src/commonMain/kotlin/arrow/resilience/ScheduleEither.kt create mode 100644 arrow-libs/resilience/arrow-resilience/src/commonTest/kotlin/arrow/resilience/ScheduleEitherTest.kt diff --git a/arrow-libs/resilience/arrow-resilience/src/commonMain/kotlin/arrow/resilience/ScheduleEither.kt b/arrow-libs/resilience/arrow-resilience/src/commonMain/kotlin/arrow/resilience/ScheduleEither.kt new file mode 100644 index 00000000000..702efed1dd6 --- /dev/null +++ b/arrow-libs/resilience/arrow-resilience/src/commonMain/kotlin/arrow/resilience/ScheduleEither.kt @@ -0,0 +1,38 @@ +@file:OptIn(ExperimentalTypeInference::class) + +import arrow.core.Either +import arrow.core.raise.Raise +import arrow.core.raise.either +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 + +public suspend inline fun Schedule.retryRaise( + @BuilderInference action: Raise.() -> Result, +): Either = retryEither { either(action) } + +public suspend inline fun Schedule.retryEither( + @BuilderInference action: () -> Either, +): Either { + var step: ScheduleStep = step + + while (true) { + currentCoroutineContext().ensureActive() + when (val result = action()) { + is Either.Left -> when (val decision = step(result.value)) { + is Schedule.Decision.Continue -> { + if (decision.delay != Duration.ZERO) delay(decision.delay) + step = decision.step + } + + is Schedule.Decision.Done -> return result + } + + is Either.Right -> return result + } + } +} diff --git a/arrow-libs/resilience/arrow-resilience/src/commonTest/kotlin/arrow/resilience/ScheduleEitherTest.kt b/arrow-libs/resilience/arrow-resilience/src/commonTest/kotlin/arrow/resilience/ScheduleEitherTest.kt new file mode 100644 index 00000000000..a7a17092acb --- /dev/null +++ b/arrow-libs/resilience/arrow-resilience/src/commonTest/kotlin/arrow/resilience/ScheduleEitherTest.kt @@ -0,0 +1,68 @@ +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 + +class ScheduleEitherTest { + + @Test + fun retryRaiseIsStackSafe(): TestResult = runTest { + val count = AtomicLong(0) + val iterations = 20_000L + + suspend fun increment() { + count.incrementAndGet() + } + + val result = Schedule.recurs(iterations).retryRaise { + increment() + raise(CustomError) + } + + assertTrue { result is Either.Left } + assertEquals(iterations + 1, count.get()) + } + + @Test + fun retryRaiseSucceedsIfErrorIsNotRaised(): TestResult = runTest { + val result = Schedule.recurs(0).retryRaise { 1 } + + assertTrue { result is Either.Right && result.value == 1 } + } + + @Test + fun retryEitherIsStackSafe(): TestResult = runTest { + val count = AtomicLong(0) + val iterations = 20_000L + + suspend fun increment() { + count.incrementAndGet() + } + + val result = Schedule.recurs(iterations).retryEither { + increment() + CustomError.left() + } + + assertTrue { result is Either.Left } + assertEquals(iterations + 1, count.get()) + } + + @Test + fun retryEitherSucceedsIfErrorIsNotRaised(): TestResult = runTest { + val result = Schedule.recurs(0).retryEither { 1.right() } + + assertTrue { result is Either.Right && result.value == 1 } + } +} + +private object CustomError