0
# Resource Management
1
2
Arrow FX Coroutines provides comprehensive resource management capabilities that ensure safe acquisition and cleanup of resources. All resource operations integrate with structured concurrency and guarantee cleanup even in the presence of exceptions or cancellation.
3
4
## Core Resource Types
5
6
### Resource Type
7
8
```kotlin { .api }
9
typealias Resource<A> = suspend ResourceScope.() -> A
10
```
11
12
A `Resource<A>` represents a resource that can be safely acquired and will be automatically cleaned up.
13
14
### ResourceScope Interface
15
16
```kotlin { .api }
17
interface ResourceScope : AutoCloseScope {
18
suspend fun <A> Resource<A>.bind(): A
19
suspend fun <A> install(acquire: suspend AcquireStep.() -> A, release: suspend (A, ExitCase) -> Unit): A
20
suspend infix fun <A> Resource<A>.release(release: suspend (A) -> Unit): A
21
suspend infix fun <A> Resource<A>.releaseCase(release: suspend (A, ExitCase) -> Unit): A
22
fun onClose(release: (Throwable?) -> Unit): Unit
23
infix fun onRelease(release: suspend (ExitCase) -> Unit)
24
}
25
26
interface AcquireStep
27
```
28
29
## Resource Creation
30
31
### Basic Resource Creation
32
33
```kotlin { .api }
34
fun <A> resource(block: suspend ResourceScope.() -> A): Resource<A>
35
fun <A> resource(acquire: suspend () -> A, release: suspend (A, ExitCase) -> Unit): Resource<A>
36
```
37
38
Create a resource with custom acquisition and release logic.
39
40
```kotlin
41
val fileResource = resource(
42
acquire = { File("data.txt").also { it.createNewFile() } },
43
release = { file -> file.delete() }
44
)
45
```
46
47
### Resource with Exit Case Handling
48
49
```kotlin { .api }
50
fun <A> resource(acquire: suspend () -> A, release: suspend (A, ExitCase) -> Unit): Resource<A>
51
```
52
53
Create a resource that receives information about how the scope exited.
54
55
```kotlin
56
val connectionResource = resource(
57
acquire = { openDatabaseConnection() },
58
release = { connection, exitCase ->
59
when (exitCase) {
60
is ExitCase.Completed -> connection.commit()
61
is ExitCase.Cancelled -> connection.rollback()
62
is ExitCase.Failure -> connection.rollback()
63
}
64
connection.close()
65
}
66
)
67
```
68
69
### AutoCloseable Resources
70
71
```kotlin { .api }
72
fun <A : AutoCloseable> autoCloseable(closingDispatcher: CoroutineDispatcher = IODispatcher, autoCloseable: suspend () -> A): Resource<A>
73
suspend fun <A : AutoCloseable> ResourceScope.autoCloseable(closingDispatcher: CoroutineDispatcher = IODispatcher, autoCloseable: suspend () -> A): A
74
```
75
76
Create resources from objects that implement `AutoCloseable`.
77
78
```kotlin
79
val inputStreamResource = autoCloseable { FileInputStream("input.txt") }
80
val suspendingResource = autoCloseable { openAsyncConnection() }
81
```
82
83
## Resource Usage
84
85
### ResourceScope Execution
86
87
```kotlin { .api }
88
suspend fun <A> resourceScope(block: suspend ResourceScope.() -> A): A
89
```
90
91
Execute code within a resource scope where resources can be safely acquired and will be automatically cleaned up.
92
93
```kotlin
94
val result = resourceScope {
95
val file = fileResource.bind()
96
val connection = connectionResource.bind()
97
98
// Use resources safely
99
processData(file, connection)
100
// Resources automatically cleaned up here
101
}
102
```
103
104
### Use Pattern
105
106
```kotlin { .api }
107
suspend fun <A, B> Resource<A>.use(f: suspend (A) -> B): B
108
```
109
110
The use pattern for single resources.
111
112
```kotlin
113
val result = fileResource.use { file ->
114
file.readText()
115
}
116
```
117
118
### Manual Resource Allocation (Delicate API)
119
120
```kotlin { .api }
121
@DelicateCoroutinesApi
122
suspend fun <A> Resource<A>.allocate(): Pair<A, suspend (ExitCase) -> Unit>
123
```
124
125
Manually allocate a resource, returning the resource and its release function.
126
127
```kotlin
128
val (connection, release) = connectionResource.allocate()
129
try {
130
// Use connection
131
connection.query("SELECT * FROM users")
132
} finally {
133
release()
134
}
135
```
136
137
## Advanced Resource Operations
138
139
### Resource Binding
140
141
```kotlin { .api }
142
suspend fun <A> Resource<A>.bind(): A
143
```
144
145
Bind a resource to the current scope, ensuring it will be cleaned up when the scope exits.
146
147
```kotlin
148
resourceScope {
149
val file = fileResource.bind()
150
val stream = streamResource.bind()
151
152
// Both resources cleaned up automatically
153
processFileWithStream(file, stream)
154
}
155
```
156
157
### Resource Installation
158
159
```kotlin { .api }
160
suspend fun <A> install(acquire: suspend () -> A, release: suspend (A) -> Unit): A
161
```
162
163
Install a resource directly in the current scope.
164
165
```kotlin
166
resourceScope {
167
val tempFile = install(
168
acquire = { createTempFile() },
169
release = { it.delete() }
170
)
171
172
processFile(tempFile)
173
}
174
```
175
176
### Adding Release Actions
177
178
```kotlin { .api }
179
infix fun <A> Resource<A>.release(release: suspend (A) -> Unit): Resource<A>
180
infix fun <A> Resource<A>.releaseCase(release: suspend (A, ExitCase) -> Unit): Resource<A>
181
```
182
183
Add additional release actions to existing resources.
184
185
```kotlin
186
val enhancedResource = fileResource
187
.release { file -> file.setReadOnly() }
188
.releaseCase { file, exitCase ->
189
if (exitCase is ExitCase.Failure) {
190
logError("Resource failed", exitCase.failure)
191
}
192
}
193
```
194
195
### Scope-Level Release Handlers
196
197
```kotlin { .api }
198
infix fun onRelease(release: suspend () -> Unit)
199
```
200
201
Register a release handler that will run when the scope exits.
202
203
```kotlin
204
resourceScope {
205
onRelease { println("Cleaning up scope") }
206
207
val file = fileResource.bind()
208
processFile(file)
209
// Scope cleanup message printed after file cleanup
210
}
211
```
212
213
## Flow Integration
214
215
### Resource as Flow
216
217
```kotlin { .api }
218
fun <A> Resource<A>.asFlow(): Flow<A>
219
```
220
221
Convert a resource to a Flow that manages the resource lifecycle.
222
223
```kotlin
224
val dataFlow = fileResource.asFlow().map { file ->
225
file.readLines()
226
}.flatten()
227
```
228
229
## Exit Cases
230
231
### ExitCase Sealed Class
232
233
```kotlin { .api }
234
sealed class ExitCase {
235
object Completed : ExitCase()
236
data class Cancelled(val exception: CancellationException) : ExitCase()
237
data class Failure(val failure: Throwable) : ExitCase()
238
239
companion object {
240
fun ExitCase(error: Throwable): ExitCase
241
}
242
}
243
```
244
245
Exit cases provide information about how a resource scope or operation terminated:
246
247
- `ExitCase.Completed`: The operation completed successfully
248
- `ExitCase.Cancelled`: The operation was cancelled
249
- `ExitCase.Failure`: The operation failed with an exception
250
251
## Resource Composition Examples
252
253
### Sequential Resource Dependencies
254
255
```kotlin
256
val appResource = resource(
257
acquire = {
258
resourceScope {
259
val config = configResource.bind()
260
val database = databaseResource(config).bind()
261
val cache = cacheResource(database).bind()
262
263
Application(database, cache)
264
}
265
},
266
release = { app -> app.shutdown() }
267
)
268
```
269
270
### Parallel Resource Initialization
271
272
```kotlin
273
val parallelResources = resource(
274
acquire = {
275
parZip(
276
{ serviceAResource.use { it } },
277
{ serviceBResource.use { it } },
278
{ serviceCResource.use { it } }
279
) { serviceA, serviceB, serviceC ->
280
CombinedServices(serviceA, serviceB, serviceC)
281
}
282
},
283
release = { services -> services.shutdown() }
284
)
285
```
286
287
### Resource Pool Management
288
289
```kotlin
290
class ConnectionPool(private val maxConnections: Int) {
291
fun getConnection(): Resource<Connection> = resource(
292
acquire = { acquireFromPool() },
293
release = { connection -> returnToPool(connection) }
294
)
295
296
private suspend fun acquireFromPool(): Connection = TODO()
297
private suspend fun returnToPool(connection: Connection) = TODO()
298
}
299
```
300
301
## Bracket Pattern Functions
302
303
The bracket pattern provides fundamental resource safety guarantees without the overhead of the full Resource system. These functions ensure cleanup even when exceptions or cancellation occur.
304
305
### Basic Bracket
306
307
```kotlin { .api }
308
suspend fun <A, B> bracket(
309
crossinline acquire: suspend () -> A,
310
use: suspend (A) -> B,
311
crossinline release: suspend (A) -> Unit
312
): B
313
```
314
315
Acquire a resource, use it, and guarantee cleanup regardless of how the operation exits.
316
317
```kotlin
318
val result = bracket(
319
acquire = { openFile("data.txt") },
320
use = { file -> file.readText() },
321
release = { file -> file.close() }
322
)
323
```
324
325
### Bracket with Exit Case
326
327
```kotlin { .api }
328
suspend fun <A, B> bracketCase(
329
crossinline acquire: suspend () -> A,
330
use: suspend (A) -> B,
331
crossinline release: suspend (A, ExitCase) -> Unit
332
): B
333
```
334
335
Like bracket, but the release function receives information about how the operation exited.
336
337
```kotlin
338
val result = bracketCase(
339
acquire = { openDatabaseTransaction() },
340
use = { tx -> tx.performOperations() },
341
release = { tx, exitCase ->
342
when (exitCase) {
343
is ExitCase.Completed -> tx.commit()
344
is ExitCase.Cancelled, is ExitCase.Failure -> tx.rollback()
345
}
346
}
347
)
348
```
349
350
### Guarantee Finalizer
351
352
```kotlin { .api }
353
suspend fun <A> guarantee(
354
fa: suspend () -> A,
355
crossinline finalizer: suspend () -> Unit
356
): A
357
```
358
359
Execute an operation and guarantee a finalizer runs afterwards, regardless of how the operation exits.
360
361
```kotlin
362
val result = guarantee(
363
fa = { performOperation() },
364
finalizer = { cleanup() }
365
)
366
```
367
368
### Guarantee with Exit Case
369
370
```kotlin { .api }
371
suspend fun <A> guaranteeCase(
372
fa: suspend () -> A,
373
crossinline finalizer: suspend (ExitCase) -> Unit
374
): A
375
```
376
377
Like guarantee, but the finalizer receives information about how the operation exited.
378
379
```kotlin
380
val result = guaranteeCase(
381
fa = { riskyOperation() },
382
finalizer = { exitCase ->
383
when (exitCase) {
384
is ExitCase.Completed -> logSuccess()
385
is ExitCase.Cancelled -> logCancellation()
386
is ExitCase.Failure -> logError(exitCase.failure)
387
}
388
}
389
)
390
```
391
392
### Cancellation Handler
393
394
```kotlin { .api }
395
suspend fun <A> onCancel(
396
fa: suspend () -> A,
397
crossinline onCancel: suspend () -> Unit
398
): A
399
```
400
401
Execute an operation and run a specific handler only if the operation is cancelled.
402
403
```kotlin
404
val result = onCancel(
405
fa = { longRunningOperation() },
406
onCancel = { notifyCancellation() }
407
)
408
```