diff --git a/arrow-libs/resilience/arrow-resilience/api/android/arrow-resilience.api b/arrow-libs/resilience/arrow-resilience/api/android/arrow-resilience.api
index 1d408f21525..928b3f2b1ed 100644
--- a/arrow-libs/resilience/arrow-resilience/api/android/arrow-resilience.api
+++ b/arrow-libs/resilience/arrow-resilience/api/android/arrow-resilience.api
@@ -108,7 +108,7 @@ public final class arrow/resilience/SagaBuilder : arrow/resilience/SagaScope {
public fun bind (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun saga (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public final fun totalCompensation (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public final synthetic fun totalCompensation (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface annotation class arrow/resilience/SagaDSLMarker : java/lang/annotation/Annotation {
diff --git a/arrow-libs/resilience/arrow-resilience/api/jvm/arrow-resilience.api b/arrow-libs/resilience/arrow-resilience/api/jvm/arrow-resilience.api
index 1d408f21525..928b3f2b1ed 100644
--- a/arrow-libs/resilience/arrow-resilience/api/jvm/arrow-resilience.api
+++ b/arrow-libs/resilience/arrow-resilience/api/jvm/arrow-resilience.api
@@ -108,7 +108,7 @@ public final class arrow/resilience/SagaBuilder : arrow/resilience/SagaScope {
public fun bind (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun saga (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public final fun totalCompensation (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public final synthetic fun totalCompensation (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface annotation class arrow/resilience/SagaDSLMarker : java/lang/annotation/Annotation {
diff --git a/arrow-libs/resilience/arrow-resilience/src/commonMain/kotlin/arrow/resilience/Saga.kt b/arrow-libs/resilience/arrow-resilience/src/commonMain/kotlin/arrow/resilience/Saga.kt
index ba60b3e68aa..66635034c10 100644
--- a/arrow-libs/resilience/arrow-resilience/src/commonMain/kotlin/arrow/resilience/Saga.kt
+++ b/arrow-libs/resilience/arrow-resilience/src/commonMain/kotlin/arrow/resilience/Saga.kt
@@ -5,9 +5,10 @@ import arrow.atomic.update
import arrow.core.mergeSuppressed
import arrow.core.nonFatalOrThrow
import arrow.core.throwIfNotNull
-import kotlinx.coroutines.NonCancellable
-import kotlinx.coroutines.withContext
-import kotlin.coroutines.cancellation.CancellationException
+import arrow.core.prependTo
+import arrow.fx.coroutines.ExitCase
+import arrow.fx.coroutines.ResourceScope
+import arrow.fx.coroutines.resourceScope
/**
@@ -100,14 +101,8 @@ public fun saga(
* fails then all compensating actions are guaranteed to run. When a compensating action failed it
* will be ignored, and the other compensating actions will continue to be run.
*/
-public suspend fun Saga.transact(): A {
- val builder = SagaBuilder()
- return guaranteeCase({ invoke(builder) }) { res ->
- when (res) {
- null -> builder.totalCompensation()
- else -> Unit
- }
- }
+public suspend fun Saga.transact(): A = resourceScope {
+ invoke(SagaResourceScope(this))
}
/** DSL Marker for the SagaEffect DSL */
@@ -116,10 +111,19 @@ public suspend fun Saga.transact(): A {
/**
* Marker object to protect [SagaScope.saga] from calling [SagaScope.bind] in its `action` step.
*/
-@SagaDSLMarker
public object SagaActionStep
// Internal implementation of the `saga { }` builder.
+private class SagaResourceScope(private val scope: ResourceScope) : SagaScope {
+ override suspend fun saga(
+ action: suspend SagaActionStep.() -> A,
+ compensation: suspend (A) -> Unit
+ ): A = action(SagaActionStep).also { a ->
+ scope.onRelease { if (it !is ExitCase.Completed) compensation(a) }
+ }
+}
+
+@Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN)
@PublishedApi
internal class SagaBuilder(
private val stack: Atomic Unit>> = Atomic(emptyList())
@@ -129,19 +133,11 @@ internal class SagaBuilder(
override suspend fun saga(
action: suspend SagaActionStep.() -> A,
compensation: suspend (A) -> Unit
- ): A =
- guaranteeCase({ action(SagaActionStep) }) { res ->
- // This action failed, so we have no compensate to push on the stack
- // the compensation stack will run in the `transact` stage, this is just the builder
- when (res) {
- null -> Unit
- else -> stack.update(
- function = { listOf(suspend { compensation(res) }) + it },
- transform = { _, new -> new }
- )
- }
- }
+ ): A = action(SagaActionStep).also { res ->
+ stack.update(suspend { compensation(res) }::prependTo)
+ }
+ @Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN)
@PublishedApi
internal suspend fun totalCompensation() {
stack
@@ -156,28 +152,3 @@ internal class SagaBuilder(
}.throwIfNotNull()
}
}
-
-private suspend fun guaranteeCase(
- fa: suspend () -> A,
- finalizer: suspend (value: A?) -> Unit
-): A {
- val res =
- try {
- fa()
- } catch (e: CancellationException) {
- runReleaseAndRethrow(e) { finalizer(null) }
- } catch (t: Throwable) {
- runReleaseAndRethrow(t) { finalizer(null) }
- }
- withContext(NonCancellable) { finalizer(res) }
- return res
-}
-
-private suspend fun runReleaseAndRethrow(original: Throwable, f: suspend () -> Unit): Nothing {
- try {
- withContext(NonCancellable) { f() }
- } catch (e: Throwable) {
- original.addSuppressed(e.nonFatalOrThrow())
- }
- throw original
-}