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

AutoClose and Resource rehaul/cleanup #3431

Open
wants to merge 6 commits into
base: arrow-2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions arrow-libs/core/arrow-autoclose/api/arrow-autoclose.api
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
public abstract interface class arrow/AutoCloseScope {
public abstract fun autoClose (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
public abstract fun install (Ljava/lang/AutoCloseable;)Ljava/lang/AutoCloseable;
public abstract fun onClose (Lkotlin/jvm/functions/Function1;)V
}

public final class arrow/AutoCloseScope$DefaultImpls {
public static fun install (Larrow/AutoCloseScope;Ljava/lang/AutoCloseable;)Ljava/lang/AutoCloseable;
}

public final class arrow/AutoCloseScopeKt {
public static final fun autoClose (Larrow/AutoCloseScope;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
public static final fun autoCloseScope (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
}

Expand All @@ -16,10 +17,22 @@ public final class arrow/AutoCloseableExtensionsKt {
}

public final class arrow/DefaultAutoCloseScope : arrow/AutoCloseScope {
public fun <init> ()V
public fun autoClose (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
public final fun close (Ljava/lang/Throwable;)Ljava/lang/Void;
public static final synthetic fun box-impl (Ljava/util/concurrent/atomic/AtomicReference;)Larrow/DefaultAutoCloseScope;
public static final fun closeAll-impl (Ljava/util/concurrent/atomic/AtomicReference;Ljava/lang/Throwable;)V
public static fun constructor-impl (Ljava/util/concurrent/atomic/AtomicReference;)Ljava/util/concurrent/atomic/AtomicReference;
public static synthetic fun constructor-impl$default (Ljava/util/concurrent/atomic/AtomicReference;ILkotlin/jvm/internal/DefaultConstructorMarker;)Ljava/util/concurrent/atomic/AtomicReference;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Ljava/util/concurrent/atomic/AtomicReference;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Ljava/util/concurrent/atomic/AtomicReference;Ljava/util/concurrent/atomic/AtomicReference;)Z
public fun hashCode ()I
public static fun hashCode-impl (Ljava/util/concurrent/atomic/AtomicReference;)I
public fun install (Ljava/lang/AutoCloseable;)Ljava/lang/AutoCloseable;
public static fun install-impl (Ljava/util/concurrent/atomic/AtomicReference;Ljava/lang/AutoCloseable;)Ljava/lang/AutoCloseable;
public fun onClose (Lkotlin/jvm/functions/Function1;)V
public static fun onClose-impl (Ljava/util/concurrent/atomic/AtomicReference;Lkotlin/jvm/functions/Function1;)V
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Ljava/util/concurrent/atomic/AtomicReference;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Ljava/util/concurrent/atomic/AtomicReference;
}

public final class arrow/ThrowIfFatalKt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package arrow

import arrow.atomic.Atomic
import arrow.atomic.update
import kotlin.coroutines.cancellation.CancellationException
import arrow.atomic.value
import kotlin.jvm.JvmInline

/**
* The [AutoCloseScope] DSL allows for elegantly working with close-ables,
Expand Down Expand Up @@ -61,53 +62,47 @@ import kotlin.coroutines.cancellation.CancellationException
* ```
* <!--- KNIT example-autocloseable-02.kt -->
*/
public inline fun <A> autoCloseScope(block: AutoCloseScope.() -> A): A {
val scope = DefaultAutoCloseScope()
return try {
block(scope)
.also { scope.close(null) }
} catch (e: CancellationException) {
scope.close(e) ?: throw e
public inline fun <A> autoCloseScope(block: AutoCloseScope.() -> A): A = with(DefaultAutoCloseScope()) {
try {
block()
} catch (e: Throwable) {
scope.close(e.throwIfFatal()) ?: throw e
}
closeAll(e.throwIfFatal())
error("Unreachable, closeAll should throw the exception passed to it. Please report this bug to the Arrow team.")
}.also { closeAll(null) }
}

public interface AutoCloseScope {
public fun <A> autoClose(
acquire: () -> A,
release: (A, Throwable?) -> Unit
): A
public fun onClose(release: (Throwable?) -> Unit)

@ExperimentalStdlibApi
public fun <A : AutoCloseable> install(autoCloseable: A): A =
autoClose({ autoCloseable }) { a, _ -> a.close() }
autoCloseable.also { onClose { autoCloseable.close() } }
}

@PublishedApi
internal class DefaultAutoCloseScope : AutoCloseScope {
private val finalizers = Atomic(emptyList<(Throwable?) -> Unit>())
public inline fun <A> AutoCloseScope.autoClose(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem, this introduces a source breaking change, to support suspend in the acquire step. If that is needed, I think users should prefer onClose, or Resource instead.

So I would prefer to revert this also.

acquire: () -> A,
crossinline release: (A, Throwable?) -> Unit
): A = acquire().also { a -> onClose { e -> release(a, e) } }

override fun <A> autoClose(acquire: () -> A, release: (A, Throwable?) -> Unit): A =
try {
acquire().also { a ->
finalizers.update { it + { e -> release(a, e) } }
}
} catch (e: Throwable) {
throw e
}
@JvmInline
@PublishedApi
internal value class DefaultAutoCloseScope(
private val finalizers: Atomic<List<(Throwable?) -> Unit>> = Atomic(emptyList())
) : AutoCloseScope {
override fun onClose(release: (Throwable?) -> Unit) =
finalizers.update { listOf(release) + it }

fun close(error: Throwable?): Nothing? {
return finalizers.get().asReversed().fold(error) { acc, function ->
acc.add(runCatching { function.invoke(error) }.exceptionOrNull())
fun closeAll(error: Throwable?) {
finalizers.value.fold(error) { acc, function ->
acc.add(runCatching { function(error) }.exceptionOrNull())
}?.let { throw it }
}

private fun Throwable?.add(other: Throwable?): Throwable? =
this?.apply {
other?.let { addSuppressed(it) }
} ?: other
}

private fun Throwable?.add(other: Throwable?): Throwable? =
this?.apply {
other?.let { addSuppressed(it) }
} ?: other

@PublishedApi
internal expect fun Throwable.throwIfFatal(): Throwable
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package arrow

import kotlin.coroutines.cancellation.CancellationException

@PublishedApi
internal actual fun Throwable.throwIfFatal(): Throwable =
when(this) {
is VirtualMachineError, is ThreadDeath, is InterruptedException, is LinkageError, is CancellationException -> throw this
is VirtualMachineError, is ThreadDeath, is InterruptedException, is LinkageError -> throw this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hate that this function cannot be private. I really do not want to expose it 😓

This is why I had the CancellationException explicitly in the try/catch, because I rather had a correct definition here, than one specific to AutoCloseable...

else -> this
}
38 changes: 36 additions & 2 deletions arrow-libs/fx/arrow-fx-coroutines/api/arrow-fx-coroutines.api
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public final class arrow/fx/coroutines/AcquireStep {
public final class arrow/fx/coroutines/BracketKt {
public static final fun bracket (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun bracketCase (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun getThrowableOrNull (Larrow/fx/coroutines/ExitCase;)Ljava/lang/Throwable;
public static final fun guarantee (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun guaranteeCase (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun onCancel (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand All @@ -30,7 +31,7 @@ public final class arrow/fx/coroutines/CyclicBarrierCancellationException : java
public fun <init> ()V
}

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

Expand All @@ -51,6 +52,8 @@ public final class arrow/fx/coroutines/ExitCase$Companion {

public final class arrow/fx/coroutines/ExitCase$Completed : arrow/fx/coroutines/ExitCase {
public static final field INSTANCE Larrow/fx/coroutines/ExitCase$Completed;
public fun equals (Ljava/lang/Object;)Z
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

Expand Down Expand Up @@ -238,26 +241,57 @@ public final class arrow/fx/coroutines/ResourceExtensionsKt {
public final class arrow/fx/coroutines/ResourceKt {
public static final fun allocated (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun asFlow (Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun install (Larrow/fx/coroutines/ResourceScope;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun resource (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function2;
public static final fun resource (Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function2;
public static final fun resourceScope (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun resourceScope (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun use (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public abstract interface class arrow/fx/coroutines/ResourceScope : arrow/AutoCloseScope {
public abstract fun bind (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun onClose (Lkotlin/jvm/functions/Function1;)V
public abstract fun install (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun onRelease (Lkotlin/jvm/functions/Function2;)V
public abstract fun release (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun releaseCase (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class arrow/fx/coroutines/ResourceScope$DefaultImpls {
public static fun bind (Larrow/fx/coroutines/ResourceScope;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun install (Larrow/fx/coroutines/ResourceScope;Ljava/lang/AutoCloseable;)Ljava/lang/AutoCloseable;
public static fun onClose (Larrow/fx/coroutines/ResourceScope;Lkotlin/jvm/functions/Function1;)V
public static fun release (Larrow/fx/coroutines/ResourceScope;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun releaseCase (Larrow/fx/coroutines/ResourceScope;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class arrow/fx/coroutines/ResourceScopeImpl : arrow/fx/coroutines/ResourceScope {
public fun bind (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Ljava/util/concurrent/atomic/AtomicReference;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final synthetic fun box-impl (Ljava/util/concurrent/atomic/AtomicReference;)Larrow/fx/coroutines/ResourceScopeImpl;
public static final fun cancelAll-impl (Ljava/util/concurrent/atomic/AtomicReference;Larrow/fx/coroutines/ExitCase;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun constructor-impl (Ljava/util/concurrent/atomic/AtomicReference;)Ljava/util/concurrent/atomic/AtomicReference;
public static synthetic fun constructor-impl$default (Ljava/util/concurrent/atomic/AtomicReference;ILkotlin/jvm/internal/DefaultConstructorMarker;)Ljava/util/concurrent/atomic/AtomicReference;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Ljava/util/concurrent/atomic/AtomicReference;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Ljava/util/concurrent/atomic/AtomicReference;Ljava/util/concurrent/atomic/AtomicReference;)Z
public fun hashCode ()I
public static fun hashCode-impl (Ljava/util/concurrent/atomic/AtomicReference;)I
public fun install (Ljava/lang/AutoCloseable;)Ljava/lang/AutoCloseable;
public static fun install-impl (Ljava/util/concurrent/atomic/AtomicReference;Ljava/lang/AutoCloseable;)Ljava/lang/AutoCloseable;
public fun onClose (Lkotlin/jvm/functions/Function1;)V
public static fun onClose-impl (Ljava/util/concurrent/atomic/AtomicReference;Lkotlin/jvm/functions/Function1;)V
public fun onRelease (Lkotlin/jvm/functions/Function2;)V
public static fun onRelease-impl (Ljava/util/concurrent/atomic/AtomicReference;Lkotlin/jvm/functions/Function2;)V
public fun release (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun release-impl (Ljava/util/concurrent/atomic/AtomicReference;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun releaseCase (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun releaseCase-impl (Ljava/util/concurrent/atomic/AtomicReference;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Ljava/util/concurrent/atomic/AtomicReference;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Ljava/util/concurrent/atomic/AtomicReference;
}

public abstract interface annotation class arrow/fx/coroutines/ScopeDSL : java/lang/annotation/Annotation {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,26 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext

public sealed class ExitCase {
public object Completed : ExitCase() {
override fun toString(): String =
"ExitCase.Completed"
}

public data class Cancelled(val exception: CancellationException) : ExitCase()
public data class Failure(val failure: Throwable) : ExitCase()
public sealed interface ExitCase {
public data object Completed : ExitCase
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data object 🥳

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)
public fun ExitCase(error: Throwable?): ExitCase = when (error) {
null -> Completed
is CancellationException -> Cancelled(error)
else -> Failure(error)
}
}
}

public val ExitCase.throwableOrNull: Throwable? get() = when (this) {
ExitCase.Completed -> null
is ExitCase.Cancelled -> exception
is ExitCase.Failure -> failure
}

Comment on lines +22 to +27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we define this inside ExitCase so that we don't need an import for it? Preferably as a function I'd say, that'd be more in line with the APIs we now from Result.

I also think exceptionOrNull is a more common name, than throwableOrNull.

/**
* Registers an [onCancel] handler after [fa].
* [onCancel] is guaranteed to be called in case of cancellation, otherwise it's ignored.
Expand Down
Loading
Loading