Skip to content

Commit

Permalink
Make install non-cancellable and add tests for it
Browse files Browse the repository at this point in the history
Add suppression for REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE
  • Loading branch information
kyay10 committed Nov 1, 2024
1 parent feb7117 commit 21db530
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ final suspend fun <#A: kotlin/Any?, #B: kotlin/Any?> (kotlin.collections/Iterabl
final suspend fun <#A: kotlin/Any?> (kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ResourceScope, #A>).arrow.fx.coroutines/allocated(): kotlin/Pair<#A, kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ExitCase, kotlin/Unit>> // arrow.fx.coroutines/allocated|[email protected]<arrow.fx.coroutines.ResourceScope,0:0>(){0§<kotlin.Any?>}[0]
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).arrow.fx.coroutines.await/awaitAll(kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines.await/AwaitAllScope, #A>): #A // arrow.fx.coroutines.await/awaitAll|[email protected](kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.await.AwaitAllScope,0:0>){0§<kotlin.Any?>}[0]
final suspend fun <#A: kotlin/Any?> arrow.fx.coroutines.await/awaitAll(kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines.await/AwaitAllScope, #A>): #A // arrow.fx.coroutines.await/awaitAll|awaitAll(kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.await.AwaitAllScope,0:0>){0§<kotlin.Any?>}[0]
final suspend fun <#A: kotlin/AutoCloseable> (arrow.fx.coroutines/ResourceScope).arrow.fx.coroutines/autoCloseable(kotlinx.coroutines/CoroutineDispatcher = ..., kotlin.coroutines/SuspendFunction0<#A>): #A // arrow.fx.coroutines/autoCloseable|[email protected](kotlinx.coroutines.CoroutineDispatcher;kotlin.coroutines.SuspendFunction0<0:0>){0§<kotlin.AutoCloseable>}[0]
final suspend fun arrow.fx.coroutines/cancelAndCompose(kotlinx.coroutines/Deferred<*>, kotlinx.coroutines/Deferred<*>) // arrow.fx.coroutines/cancelAndCompose|cancelAndCompose(kotlinx.coroutines.Deferred<*>;kotlinx.coroutines.Deferred<*>){}[0]
final suspend inline fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?, #D: kotlin/Any?, #E: kotlin/Any?, #F: kotlin/Any?, #G: kotlin/Any?, #H: kotlin/Any?, #I: kotlin/Any?, #J: kotlin/Any?, #K: kotlin/Any?> (arrow.core.raise/Raise<#A>).arrow.fx.coroutines/parZipOrAccumulate(crossinline kotlin/Function2<#A, #A, #A>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #B>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #C>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #D>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #E>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #F>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #G>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #H>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #I>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #J>, crossinline kotlin.coroutines/SuspendFunction10<kotlinx.coroutines/CoroutineScope, #B, #C, #D, #E, #F, #G, #H, #I, #J, #K>): #K // arrow.fx.coroutines/parZipOrAccumulate|[email protected]<0:0>(kotlin.Function2<0:0,0:0,0:0>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:1>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:2>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:3>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:4>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:5>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:6>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:7>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:8>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:9>;kotlin.coroutines.SuspendFunction10<kotlinx.coroutines.CoroutineScope,0:1,0:2,0:3,0:4,0:5,0:6,0:7,0:8,0:9,0:10>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>;3§<kotlin.Any?>;4§<kotlin.Any?>;5§<kotlin.Any?>;6§<kotlin.Any?>;7§<kotlin.Any?>;8§<kotlin.Any?>;9§<kotlin.Any?>;10§<kotlin.Any?>}[0]
final suspend inline fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?, #D: kotlin/Any?, #E: kotlin/Any?, #F: kotlin/Any?, #G: kotlin/Any?, #H: kotlin/Any?, #I: kotlin/Any?, #J: kotlin/Any?, #K: kotlin/Any?> (arrow.core.raise/Raise<#A>).arrow.fx.coroutines/parZipOrAccumulate(kotlin.coroutines/CoroutineContext, crossinline kotlin/Function2<#A, #A, #A>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #B>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #C>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #D>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #E>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #F>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #G>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #H>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #I>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ScopedRaiseAccumulate<#A>, #J>, crossinline kotlin.coroutines/SuspendFunction10<kotlinx.coroutines/CoroutineScope, #B, #C, #D, #E, #F, #G, #H, #I, #J, #K>): #K // arrow.fx.coroutines/parZipOrAccumulate|[email protected]<0:0>(kotlin.coroutines.CoroutineContext;kotlin.Function2<0:0,0:0,0:0>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:1>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:2>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:3>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:4>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:5>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:6>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:7>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:8>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ScopedRaiseAccumulate<0:0>,0:9>;kotlin.coroutines.SuspendFunction10<kotlinx.coroutines.CoroutineScope,0:1,0:2,0:3,0:4,0:5,0:6,0:7,0:8,0:9,0:10>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>;3§<kotlin.Any?>;4§<kotlin.Any?>;5§<kotlin.Any?>;6§<kotlin.Any?>;7§<kotlin.Any?>;8§<kotlin.Any?>;9§<kotlin.Any?>;10§<kotlin.Any?>}[0]
Expand Down Expand Up @@ -237,5 +238,4 @@ final suspend inline fun <#A: kotlin/Any?> arrow.fx.coroutines/guarantee(kotlin.
final suspend inline fun <#A: kotlin/Any?> arrow.fx.coroutines/guaranteeCase(kotlin.coroutines/SuspendFunction0<#A>, crossinline kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ExitCase, kotlin/Unit>): #A // arrow.fx.coroutines/guaranteeCase|guaranteeCase(kotlin.coroutines.SuspendFunction0<0:0>;kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ExitCase,kotlin.Unit>){0§<kotlin.Any?>}[0]
final suspend inline fun <#A: kotlin/Any?> arrow.fx.coroutines/onCancel(kotlin.coroutines/SuspendFunction0<#A>, crossinline kotlin.coroutines/SuspendFunction0<kotlin/Unit>): #A // arrow.fx.coroutines/onCancel|onCancel(kotlin.coroutines.SuspendFunction0<0:0>;kotlin.coroutines.SuspendFunction0<kotlin.Unit>){0§<kotlin.Any?>}[0]
final suspend inline fun <#A: kotlin/Any?> arrow.fx.coroutines/resourceScope(kotlin.coroutines/SuspendFunction1<arrow.fx.coroutines/ResourceScope, #A>): #A // arrow.fx.coroutines/resourceScope|resourceScope(kotlin.coroutines.SuspendFunction1<arrow.fx.coroutines.ResourceScope,0:0>){0§<kotlin.Any?>}[0]
final suspend inline fun <#A: kotlin/AutoCloseable> (arrow.fx.coroutines/ResourceScope).arrow.fx.coroutines/autoCloseable(kotlinx.coroutines/CoroutineDispatcher = ..., kotlin.coroutines/SuspendFunction0<#A>): #A // arrow.fx.coroutines/autoCloseable|[email protected](kotlinx.coroutines.CoroutineDispatcher;kotlin.coroutines.SuspendFunction0<0:0>){0§<kotlin.AutoCloseable>}[0]
final suspend inline fun arrow.fx.coroutines/runReleaseAndRethrow(kotlin/Throwable, crossinline kotlin.coroutines/SuspendFunction0<kotlin/Unit>): kotlin/Nothing // arrow.fx.coroutines/runReleaseAndRethrow|runReleaseAndRethrow(kotlin.Throwable;kotlin.coroutines.SuspendFunction0<kotlin.Unit>){}[0]
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,9 @@ public interface ResourceScope : AutoCloseScope {
public suspend fun <A> install(
acquire: suspend AcquireStep.() -> A,
release: suspend (A, ExitCase) -> Unit,
): A = acquire(AcquireStep).also { a -> onRelease { release(a, it) } }
): A = withContext(NonCancellable) {
acquire(AcquireStep).also { a -> onRelease { release(a, it) } }
}

/** Composes a [release] action to a [Resource] value before binding. */
@ResourceDSL
Expand Down Expand Up @@ -349,6 +351,7 @@ public fun <A> resource(block: suspend ResourceScope.() -> A): Resource<A> = blo
* <!--- KNIT example-resource-06.kt -->
*/
@ScopeDSL
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
public suspend inline fun <A> resourceScope(action: suspend ResourceScope.() -> A): A {
val scope = ResourceScopeImpl()
var finished = false
Expand All @@ -365,6 +368,7 @@ public suspend inline fun <A> resourceScope(action: suspend ResourceScope.() ->
}
}

@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
public suspend inline infix fun <A, B> Resource<A>.use(f: suspend (A) -> B): B =
resourceScope { f(bind()) }

Expand Down Expand Up @@ -396,7 +400,7 @@ public fun <A> resource(
}

/**
* Runs [Resource.use] and emits [A] of the resource
* Runs [Resource].[use] and emits [A] of the resource
*
* ```kotlin
* import arrow.fx.coroutines.*
Expand Down Expand Up @@ -510,14 +514,14 @@ internal expect val IODispatcher: CoroutineDispatcher
* <!--- KNIT example-resource-10.kt -->
*/
@ResourceDSL
public suspend inline fun <A : AutoCloseable> ResourceScope.autoCloseable(
public suspend fun <A : AutoCloseable> ResourceScope.autoCloseable(
closingDispatcher: CoroutineDispatcher = IODispatcher,
autoCloseable: suspend () -> A,
): A = autoCloseable().also { s -> onRelease { withContext(closingDispatcher) { s.close() } } }
): A = install({ autoCloseable() } ) { s: A, _ -> withContext(closingDispatcher) { s.close() } }

public fun <A : AutoCloseable> autoCloseable(
closingDispatcher: CoroutineDispatcher = IODispatcher,
autoCloseable: suspend () -> A,
): Resource<A> = resource {
autoCloseable(closingDispatcher) { autoCloseable() }
autoCloseable(closingDispatcher, autoCloseable)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ package arrow.fx.coroutines
import arrow.atomic.AtomicBoolean
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.shouldBeTypeOf
import io.kotest.property.Arb
import io.kotest.property.checkAll
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import kotlin.test.Test

class ResourceAutoCloseTest {
Expand Down Expand Up @@ -40,4 +45,36 @@ class ResourceAutoCloseTest {
t.didClose.get() shouldBe true
}
}


@Test
fun autoClosableIsNonCancellable() = runTest {
val t = AutoCloseableTest()
lateinit var exit: ExitCase
val waitingToBeCancelled = CompletableDeferred<Unit>()
val cancelled = CompletableDeferred<Unit>()

val job = launch {
resourceScope {
onRelease { exit = it }
autoCloseable {
waitingToBeCancelled.complete(Unit)
cancelled.await()
t
}
yield()
}
}

waitingToBeCancelled.await()
job.cancel("BOOM!")
cancelled.complete(Unit)
job.join()

t.didClose.get() shouldBe true
exit
.shouldBeTypeOf<ExitCase.Cancelled>()
.exception
.message shouldBe "BOOM!"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlinx.coroutines.channels.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield

class ResourceTest {

Expand Down Expand Up @@ -162,6 +165,34 @@ class ResourceTest {
.message shouldBe "BOOM!"
}

@Test
fun installIsNonCancellable() = runTest {
lateinit var exit: ExitCase
val waitingToBeCancelled = CompletableDeferred<Unit>()
val cancelled = CompletableDeferred<Unit>()

val job = launch {
resourceScope {
install({
waitingToBeCancelled.complete(Unit)
cancelled.await()
}) { _, ex ->
exit = ex
}
yield()
}
}
waitingToBeCancelled.await()
job.cancel("BOOM!")
cancelled.complete(Unit)
job.join()

exit
.shouldBeTypeOf<ExitCase.Cancelled>()
.exception
.message shouldBe "BOOM!"
}

@Test
fun parZipSuccess() = runTestUsingDefaultDispatcher {
suspend fun ResourceScope.closeable(): CheckableAutoClose =
Expand Down

0 comments on commit 21db530

Please sign in to comment.