Daniel Pitts
12/17/2023, 10:24 PMcreateA().use { createB().use { createC().use { ... } } }
Javier
12/17/2023, 10:31 PMephemient
12/18/2023, 3:17 AMCloser().use { closer ->
val a = closer.register(createA())
val b = closer.register(createB())
val c = closer.register(createC())
...
Daniel Pitts
12/18/2023, 3:18 AMpackage com.stochastictinkr.kotlessing.util
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.util.LinkedList
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.AbstractCoroutineContextElement
import kotlin.coroutines.CoroutineContext
class ResourceScope : AbstractCoroutineContextElement(Key), Disposable {
companion object Key : CoroutineContext.Key<ResourceScope>
private var mutex: Mutex = Mutex()
private var closed = false
private val resources = LinkedList<Disposable>()
private suspend inline fun <R> withLock(block: () -> R): R = mutex.withLock(action = block)
suspend fun <R : Disposable?> create(creator: suspend () -> R): R = withLock {
check(!closed) { "Scope is already closed" }
val resource = creator()
if (resource != null) {
resources.addFirst(resource)
}
resource
}
suspend fun remove(disposable: Disposable) = withLock {
if (closed) false else resources.remove(disposable)
}
suspend infix fun <R : Disposable> R.moveTo(otherScope: ResourceScope) = withLock {
if (closed) return false
val resource = this
otherScope.create {
if (resources.remove(resource)) resource else null
} == resource
}
override suspend fun dispose() {
withLock {
if (closed) return
closed = true
}
resources.map { runCatching { it.dispose() } }
resources.clear()
}
}
interface Disposable {
suspend fun dispose()
}
suspend inline fun <D : Disposable, R> D.use(block: (D) -> R): R =
try {
block(this)
} finally {
dispose()
}
suspend inline fun <R : Disposable?> CoroutineScope.create(crossinline creator: suspend CoroutineScope.() -> R): R =
requireNotNull(coroutineContext[ResourceScope]) { "No ResourceScope available" }.create { creator() }
suspend fun <R> CoroutineScope.resourcesScope(block: suspend CoroutineScope.() -> R): R {
return ResourceScope().use { withContext(it, block) }
}