Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Resource.allocated() to decompose Resource into it's allocate and… #2820

Merged
merged 12 commits into from Sep 14, 2022
6 changes: 6 additions & 0 deletions arrow-libs/fx/arrow-fx-coroutines/api/arrow-fx-coroutines.api
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
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 =
i-walker marked this conversation as resolved.
Show resolved Hide resolved
if (error is CancellationException) Cancelled(error) else Failure(error)
}
}

/**
Expand Down
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