Skip to content

Commit 06ea5b9

Browse files
authored
Ensure AutoCloseScope and ResourceScope only ever call finalizers once (#3530)
1 parent 4aa0974 commit 06ea5b9

File tree

3 files changed

+17
-4
lines changed

3 files changed

+17
-4
lines changed

arrow-libs/core/arrow-autoclose/src/commonMain/kotlin/arrow/AutoCloseScope.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package arrow
22

33
import arrow.atomic.Atomic
44
import arrow.atomic.update
5-
import arrow.atomic.value
65
import kotlin.coroutines.cancellation.CancellationException
76

87
/**
@@ -97,7 +96,7 @@ internal class DefaultAutoCloseScope : AutoCloseScope {
9796
}
9897

9998
fun close(error: Throwable?): Nothing? {
100-
return finalizers.value.asReversed().fold(error) { acc, finalizer ->
99+
return finalizers.getAndSet(emptyList()).asReversed().fold(error) { acc, finalizer ->
101100
acc.add(runCatching { finalizer(error) }.exceptionOrNull())
102101
}?.let { throw it }
103102
}

arrow-libs/fx/arrow-fx-coroutines/src/commonMain/kotlin/arrow/fx/coroutines/Resource.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package arrow.fx.coroutines
33
import arrow.AutoCloseScope
44
import arrow.atomic.Atomic
55
import arrow.atomic.update
6-
import arrow.atomic.value
76
import arrow.core.nonFatalOrThrow
87
import arrow.core.prependTo
98
import kotlinx.coroutines.CoroutineDispatcher
@@ -471,7 +470,7 @@ internal class ResourceScopeImpl : ResourceScope {
471470

472471
suspend fun cancelAll(exitCase: ExitCase) {
473472
withContext(NonCancellable) {
474-
finalizers.value.fold(exitCase.errorOrNull) { acc, finalizer ->
473+
finalizers.getAndSet(emptyList()).fold(exitCase.errorOrNull) { acc, finalizer ->
475474
acc.add(runCatching { finalizer(exitCase) }.exceptionOrNull())
476475
}
477476
}?.let { throw it }

arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/ResourceTest.kt

+15
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,21 @@ class ResourceTest {
699699
}
700700
}
701701

702+
@OptIn(DelicateCoroutinesApi::class)
703+
@Test
704+
fun allocatedRunsReleasersOnlyOnce() = runTest {
705+
val released = CompletableDeferred<ExitCase>()
706+
val (_, release) =
707+
resource {
708+
onRelease { exitCase ->
709+
require(released.complete(exitCase))
710+
}
711+
}.allocate()
712+
release(ExitCase.Completed)
713+
release(ExitCase.Completed)
714+
released.shouldHaveCompleted() shouldBe ExitCase.Completed
715+
}
716+
702717
private class Res : AutoCloseable {
703718
private val isActive = AtomicBoolean(true)
704719

0 commit comments

Comments
 (0)