Skip to content

Commit

Permalink
Add Resource.allocated() to decompose Resource into it's allocate and… (
Browse files Browse the repository at this point in the history
#2820)

* Add Resource.allocated() to decompose Resource into it's allocate and release. This can be used to integrate Resources with code which cannot be run within the [use] function.
* Add DelicateCoroutinesApi to allocated
* Add constructor ExitCase(e: Throwable)
Co-authored-by: Simon Vergauwen <[email protected]>
  • Loading branch information
custommonkey authored Sep 14, 2022
1 parent 90f3c96 commit e5f42ec
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 72 deletions.
6 changes: 6 additions & 0 deletions arrow-libs/fx/arrow-fx-coroutines/api/arrow-fx-coroutines.api
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public final class arrow/fx/coroutines/CircuitBreaker$State$Open : arrow/fx/coro
}

public abstract class arrow/fx/coroutines/ExitCase {
public static final field Companion Larrow/fx/coroutines/ExitCase$Companion;
}

public final class arrow/fx/coroutines/ExitCase$Cancelled : arrow/fx/coroutines/ExitCase {
Expand All @@ -107,6 +108,10 @@ public final class arrow/fx/coroutines/ExitCase$Cancelled : arrow/fx/coroutines/
public fun toString ()Ljava/lang/String;
}

public final class arrow/fx/coroutines/ExitCase$Companion {
public final fun ExitCase (Ljava/lang/Throwable;)Larrow/fx/coroutines/ExitCase;
}

public final class arrow/fx/coroutines/ExitCase$Completed : arrow/fx/coroutines/ExitCase {
public static final field INSTANCE Larrow/fx/coroutines/ExitCase$Completed;
public fun toString ()Ljava/lang/String;
Expand Down Expand Up @@ -303,6 +308,7 @@ public final class arrow/fx/coroutines/Race3Kt {

public abstract class arrow/fx/coroutines/Resource {
public static final field Companion Larrow/fx/coroutines/Resource$Companion;
public final fun allocated (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun ap (Larrow/fx/coroutines/Resource;)Larrow/fx/coroutines/Resource;
public final fun flatMap (Lkotlin/jvm/functions/Function1;)Larrow/fx/coroutines/Resource;
public final fun map (Lkotlin/jvm/functions/Function2;)Larrow/fx/coroutines/Resource;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public sealed class ExitCase {

public data class Cancelled(val exception: CancellationException) : ExitCase()
public data class Failure(val failure: Throwable) : ExitCase()

public companion object {
public fun ExitCase(error: Throwable): ExitCase =
if (error is CancellationException) Cancelled(error) else Failure(error)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import arrow.core.continuations.AtomicRef
import arrow.core.continuations.update
import arrow.core.identity
import arrow.core.prependTo
import arrow.fx.coroutines.ExitCase.Companion.ExitCase
import arrow.fx.coroutines.continuations.ResourceScope
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
import kotlin.experimental.ExperimentalTypeInference

/**
Expand Down Expand Up @@ -180,9 +181,8 @@ public sealed class Resource<out A> {
val a = dsl(effect)
f(a)
} catch (e: Throwable) {
val ex = if (e is CancellationException) ExitCase.Cancelled(e) else ExitCase.Failure(e)
val ee = withContext(NonCancellable) {
effect.finalizers.get().cancelAll(ex, e) ?: e
effect.finalizers.get().cancelAll(ExitCase(e), e) ?: e
}
throw ee
}
Expand Down Expand Up @@ -318,7 +318,7 @@ public sealed class Resource<out A> {
public inline fun <B, C, D> zip(
b: Resource<B>,
c: Resource<C>,
crossinline map: (A, B, C) -> D,
crossinline map: (A, B, C) -> D
): Resource<D> =
arrow.fx.coroutines.continuations.resource {
map(bind(), b.bind(), c.bind())
Expand All @@ -328,7 +328,7 @@ public sealed class Resource<out A> {
b: Resource<B>,
c: Resource<C>,
d: Resource<D>,
crossinline map: (A, B, C, D) -> E,
crossinline map: (A, B, C, D) -> E
): Resource<E> =
arrow.fx.coroutines.continuations.resource {
map(bind(), b.bind(), c.bind(), d.bind())
Expand All @@ -339,7 +339,7 @@ public sealed class Resource<out A> {
c: Resource<C>,
d: Resource<D>,
e: Resource<E>,
crossinline map: (A, B, C, D, E) -> G,
crossinline map: (A, B, C, D, E) -> G
): Resource<G> =
arrow.fx.coroutines.continuations.resource {
map(bind(), b.bind(), c.bind(), d.bind(), e.bind())
Expand All @@ -351,7 +351,7 @@ public sealed class Resource<out A> {
d: Resource<D>,
e: Resource<E>,
f: Resource<F>,
crossinline map: (A, B, C, D, E, F) -> G,
crossinline map: (A, B, C, D, E, F) -> G
): Resource<G> =
arrow.fx.coroutines.continuations.resource {
map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind())
Expand All @@ -364,7 +364,7 @@ public sealed class Resource<out A> {
e: Resource<E>,
f: Resource<F>,
g: Resource<G>,
crossinline map: (A, B, C, D, E, F, G) -> H,
crossinline map: (A, B, C, D, E, F, G) -> H
): Resource<H> =
arrow.fx.coroutines.continuations.resource {
map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind())
Expand All @@ -378,7 +378,7 @@ public sealed class Resource<out A> {
f: Resource<F>,
g: Resource<G>,
h: Resource<H>,
crossinline map: (A, B, C, D, E, F, G, H) -> I,
crossinline map: (A, B, C, D, E, F, G, H) -> I
): Resource<I> =
arrow.fx.coroutines.continuations.resource {
map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind())
Expand All @@ -393,7 +393,7 @@ public sealed class Resource<out A> {
g: Resource<G>,
h: Resource<H>,
i: Resource<I>,
crossinline map: (A, B, C, D, E, F, G, H, I) -> J,
crossinline map: (A, B, C, D, E, F, G, H, I) -> J
): Resource<J> =
arrow.fx.coroutines.continuations.resource {
map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind(), i.bind())
Expand All @@ -409,7 +409,7 @@ public sealed class Resource<out A> {
h: Resource<H>,
i: Resource<I>,
j: Resource<J>,
crossinline map: (A, B, C, D, E, F, G, H, I, J) -> K,
crossinline map: (A, B, C, D, E, F, G, H, I, J) -> K
): Resource<K> =
arrow.fx.coroutines.continuations.resource {
map(bind(), b.bind(), c.bind(), d.bind(), e.bind(), f.bind(), g.bind(), h.bind(), i.bind(), j.bind())
Expand Down Expand Up @@ -464,12 +464,74 @@ public sealed class Resource<out A> {
public fun <B, C> parZip(
ctx: CoroutineContext = Dispatchers.Default,
fb: Resource<B>,
f: suspend (A, B) -> C,
f: suspend (A, B) -> C
): Resource<C> =
arrow.fx.coroutines.continuations.resource {
parZip(ctx, { this@Resource.bind() }, { fb.bind() }) { a, b -> f(a, b) }
}

/**
* Deconstruct [Resource] into an `acquire` and `release` handlers.
* The `release` action **must** always be called with resource [A] returned from `acquire`,
* if the `release` step is never called, then the resource [A] will leak. The `acquire` and `release`
* steps are already made `NonCancellable` to guarantee correct invocation like `Resource` or `bracketCase`.
*
* ```kotlin
* import arrow.fx.coroutines.*
* import arrow.fx.coroutines.ExitCase.Companion.ExitCase
*
* val resource = Resource({ println("Acquire") }) { _, exitCase ->
* println("Release $exitCase")
* }
*
* suspend fun main(): Unit {
* val (acquire, release) = resource.allocated()
* val a = acquire()
* try {
* /** Do something with A */
* release(a, ExitCase.Completed)
* } catch(e: Throwable) {
* val e2 = runCatching { release(a, ExitCase(e)) }.exceptionOrNull()
* throw Platform.composeErrors(e, e2)
* }
* }
* ```
* <!--- KNIT example-resource-08.kt -->
*
* This is a **delicate** API. It is easy to accidentally create resource or memory leaks `allocated` is used.
* A `Resource` allocated by `allocated` is not subject to the guarantees that [Resource] makes,
* instead the caller is responsible for correctly invoking the `release` handler at the appropriate time.
* This API is useful for building inter-op APIs between [Resource] and non-suspending code, such as Java libraries.
*/
@DelicateCoroutinesApi
public suspend fun allocated(): Pair<suspend () -> A, suspend (@UnsafeVariance A, ExitCase) -> Unit> =
when (this) {
is Bind<*, A> ->
Dsl {
val any = source.bind()
val ff = f as (Any?) -> Resource<A>
ff(any).bind()
}.allocated()
is Allocate -> acquire to release
is Defer -> resource().allocated()
is Dsl -> {
val effect = ResourceScopeImpl()
val allocated = try {
val allocate: suspend () -> A = suspend { dsl(effect) }
val release: suspend (A, ExitCase) -> Unit = { _, e ->
effect.finalizers.get().cancelAll(e)?.let { throw it }
}
allocate to release
} catch (e: Throwable) {
val ee = withContext(NonCancellable) {
effect.finalizers.get().cancelAll(ExitCase(e), e) ?: e
}
throw ee
}
allocated
}
}

@Deprecated(
"Bind is being deprecated. Use resource DSL instead",
ReplaceWith(
Expand All @@ -481,7 +543,7 @@ public sealed class Resource<out A> {

public class Allocate<A>(
public val acquire: suspend () -> A,
public val release: suspend (A, ExitCase) -> Unit,
public val release: suspend (A, ExitCase) -> Unit
) : Resource<A>()

@Deprecated(
Expand Down Expand Up @@ -517,11 +579,11 @@ public sealed class Resource<out A> {
* }
* }
* ```
* <!--- KNIT example-resource-08.kt -->
* <!--- KNIT example-resource-09.kt -->
*/
public operator fun <A> invoke(
acquire: suspend () -> A,
release: suspend (A, ExitCase) -> Unit,
release: suspend (A, ExitCase) -> Unit
): Resource<A> = Allocate(acquire, release)

/**
Expand Down Expand Up @@ -571,7 +633,7 @@ public sealed class Resource<out A> {
* println(res)
* }
* ```
* <!--- KNIT example-resource-09.kt -->
* <!--- KNIT example-resource-10.kt -->
*/
@Deprecated(
"Use the resource computation DSL instead",
Expand Down Expand Up @@ -661,7 +723,7 @@ public inline fun <A, B> Iterable<A>.traverseResource(crossinline f: (A) -> Reso
* res.forEach(::println)
* }
* ```
* <!--- KNIT example-resource-10.kt -->
* <!--- KNIT example-resource-11.kt -->
*/
@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
Expand Down Expand Up @@ -706,7 +768,7 @@ public inline fun <A, B> Iterable<A>.traverse(crossinline f: (A) -> Resource<B>)
* res.forEach(::println)
* }
* ```
* <!--- KNIT example-resource-11.kt -->
* <!--- KNIT example-resource-12.kt -->
*/
@Suppress("NOTHING_TO_INLINE")
public inline fun <A> Iterable<Resource<A>>.sequence(): Resource<List<A>> =
Expand Down Expand Up @@ -775,7 +837,7 @@ private class ResourceScopeImpl : ResourceScope {

private suspend fun List<suspend (ExitCase) -> Unit>.cancelAll(
exitCase: ExitCase,
first: Throwable? = null,
first: Throwable? = null
): Throwable? = fold(first) { acc, finalizer ->
val other = kotlin.runCatching { finalizer(exitCase) }.exceptionOrNull()
other?.let {
Expand Down
Loading

0 comments on commit e5f42ec

Please sign in to comment.