Resource
Resource models resource allocation and releasing. It is especially useful when multiple resources that depend on each other need to be acquired and later released in reverse order. Or when you want to load independent resources in parallel.
When a resource is created we can call use to run a suspend computation with the resource. The finalizers are then guaranteed to run afterwards in reverse order of acquisition.
Consider the following use case:
import arrow.fx.coroutines.*
class UserProcessor {
fun start(): Unit = println("Creating UserProcessor")
fun shutdown(): Unit = println("Shutting down UserProcessor")
fun process(ds: DataSource): List<String> =
ds.users().map { "Processed $it" }
}
class DataSource {
fun connect(): Unit = println("Connecting dataSource")
fun users(): List<String> = listOf("User-1", "User-2", "User-3")
fun close(): Unit = println("Closed dataSource")
}
class Service(val db: DataSource, val userProcessor: UserProcessor) {
suspend fun processData(): List<String> = throw RuntimeException("I'm going to leak resources by not closing them")
}
//sampleStart
suspend fun main(): Unit {
val userProcessor = UserProcessor().also { it.start() }
val dataSource = DataSource().also { it.connect() }
val service = Service(dataSource, userProcessor)
service.processData()
dataSource.close()
userProcessor.shutdown()
}
//sampleEnd
In the following example, we are creating and using a service that has a dependency on two resources: A database and a processor. All resources need to be closed in the correct order at the end. However this program is not safe because it is prone to leaking dataSource
and userProcessor
when an exception or cancellation signal occurs whilst using the service. As a consequence of the resource leak, this program does not guarantee the correct release of resources if something fails while acquiring or using the resource. Additionally manually keeping track of acquisition effects is an unnecessary overhead.
We can split the above program into 3 different steps:
Acquiring the resource
Using the resource
Releasing the resource with either ExitCase.Completed, ExitCase.Failure or ExitCase.Cancelled.
That is exactly what Resource
does, and how we can solve our problem:
Constructing Resource
Creating a resource can be easily done by the resource
DSL, and there are two ways to define the finalizers with release
or releaseCase
.
import arrow.fx.coroutines.*
val resourceA = resource {
"A"
} release { a ->
println("Releasing $a")
}
val resourceB = resource {
"B"
} releaseCase { b, exitCase ->
println("Releasing $b with exit: $exitCase")
}
Here releaseCase
also signals with what ExitCase state the use
step finished.
Using and composing Resource
import arrow.fx.coroutines.*
class UserProcessor {
fun start(): Unit = println("Creating UserProcessor")
fun shutdown(): Unit = println("Shutting down UserProcessor")
fun process(ds: DataSource): List<String> =
ds.users().map { "Processed $it" }
}
class DataSource {
fun connect(): Unit = println("Connecting dataSource")
fun users(): List<String> = listOf("User-1", "User-2", "User-3")
fun close(): Unit = println("Closed dataSource")
}
class Service(val db: DataSource, val userProcessor: UserProcessor) {
suspend fun processData(): List<String> = userProcessor.process(db)
}
//sampleStart
val userProcessor = resource {
UserProcessor().also(UserProcessor::start)
} release UserProcessor::shutdown
val dataSource = resource {
DataSource().also { it.connect() }
} release DataSource::close
suspend fun main(): Unit {
userProcessor.parZip(dataSource) { userProcessor, ds ->
Service(ds, userProcessor)
}.use { service -> service.processData() }
}
//sampleEnd
Resources are immutable and can be composed using zip or parZip. Resources guarantee that their release finalizers are always invoked in the correct order when an exception is raised or the context where the program is running gets canceled.
To achieve this Resource ensures that the acquire
&release
step are NonCancellable. If a cancellation signal, or an exception is received during acquire
, the resource is assumed to not have been acquired and thus will not trigger the release function. => Any composed resources that are already acquired they will be guaranteed to release as expected.
If you don't need a data-type like Resource but want a function alternative to try/catch/finally
with automatic error composition, and automatic NonCancellableacquire
and release
steps use bracketCase or bracket.
Types
Functions
Inheritors
Extensions
Composes a releaseCase action to a Resource.use action creating a Resource.