0
# Resource Management
1
2
ZIO's Scope-based resource lifecycle management provides automatic cleanup and finalizer execution for leak-free applications with guaranteed resource cleanup even in the presence of failures and interruptions.
3
4
## Capabilities
5
6
### Scope - Resource Lifecycle Management
7
8
The foundational abstraction for managing resource lifecycles with automatic cleanup guarantees.
9
10
```scala { .api }
11
/**
12
* A scope represents a resource lifecycle boundary where resources can be safely allocated
13
* and automatically cleaned up when the scope closes
14
*/
15
sealed trait Scope {
16
/** Add a finalizer that runs when the scope closes */
17
def addFinalizer(finalizer: => UIO[Any]): UIO[Unit]
18
19
/** Add a finalizer that receives the exit status */
20
def addFinalizerExit(finalizer: Exit[Any, Any] => UIO[Any]): UIO[Unit]
21
22
/** Create a child scope */
23
def fork: UIO[Scope.Closeable]
24
25
/** Create a child scope with custom execution strategy */
26
def forkWith(executionStrategy: => ExecutionStrategy): UIO[Scope.Closeable]
27
28
/** Extend the scope to cover a larger region */
29
def extend[R]: Scope.ExtendPartiallyApplied[R]
30
}
31
32
/**
33
* A closeable scope that can be explicitly closed
34
*/
35
abstract class Scope.Closeable extends Scope {
36
/** Close the scope, running all finalizers */
37
def close(exit: => Exit[Any, Any]): UIO[Unit]
38
39
/** Number of finalizers registered */
40
def size: Int
41
42
/** Use this scope for an effect */
43
def use[R]: Scope.UsePartiallyApplied[R]
44
}
45
```
46
47
### Scope Factory Methods and Operations
48
49
Create and manage scopes for various resource management patterns.
50
51
```scala { .api }
52
/**
53
* Create a new closeable scope
54
*/
55
def make: UIO[Scope.Closeable]
56
57
/**
58
* Create a scope with custom execution strategy
59
*/
60
def makeWith(executionStrategy: => ExecutionStrategy): UIO[Scope.Closeable]
61
62
/**
63
* Create a scope that runs finalizers in parallel
64
*/
65
def parallel: UIO[Scope.Closeable]
66
67
/**
68
* Add a finalizer to the current scope
69
*/
70
def addFinalizer(finalizer: => UIO[Any]): ZIO[Scope, Nothing, Unit]
71
72
/**
73
* Add an exit-aware finalizer to the current scope
74
*/
75
def addFinalizerExit(finalizer: Exit[Any, Any] => UIO[Any]): ZIO[Scope, Nothing, Unit]
76
77
/**
78
* Access to global scope (never closes)
79
*/
80
val global: Scope.Closeable
81
82
/**
83
* Default scope layer for dependency injection
84
*/
85
val default: ZLayer[Any, Nothing, Scope]
86
87
/**
88
* Execute an effect with a fresh scope
89
*/
90
def scoped[R, E, A](zio: ZIO[R with Scope, E, A]): ZIO[R, E, A]
91
92
/**
93
* Execute an effect that requires a scope
94
*/
95
def scopedWith[R, E, A](f: Scope => ZIO[R, E, A]): ZIO[R, E, A]
96
```
97
98
**Usage Examples:**
99
100
```scala
101
import zio._
102
103
// Basic resource management with scope
104
val resourceProgram = ZIO.scoped {
105
for {
106
_ <- Scope.addFinalizer(Console.printLine("Cleaning up resources"))
107
resource <- ZIO.attempt(new FileInputStream("data.txt"))
108
_ <- Scope.addFinalizer(ZIO.succeed(resource.close()))
109
data <- ZIO.attempt(resource.read())
110
} yield data
111
}
112
113
// Manual scope control
114
val manualScopeProgram = for {
115
scope <- Scope.make
116
_ <- scope.addFinalizer(Console.printLine("Manual cleanup"))
117
118
result <- scope.use { implicit s =>
119
for {
120
resource <- acquireResource
121
data <- processResource(resource)
122
} yield data
123
}
124
125
_ <- scope.close(Exit.succeed(()))
126
} yield result
127
128
// Nested scopes with different lifecycles
129
val nestedScopes = ZIO.scoped {
130
for {
131
_ <- Scope.addFinalizer(Console.printLine("Outer scope cleanup"))
132
outer <- acquireOuterResource
133
134
result <- ZIO.scoped {
135
for {
136
_ <- Scope.addFinalizer(Console.printLine("Inner scope cleanup"))
137
inner <- acquireInnerResource
138
data <- processResources(outer, inner)
139
} yield data
140
}
141
} yield result
142
}
143
```
144
145
### Resource Acquisition Patterns
146
147
Common patterns for safe resource acquisition and management.
148
149
```scala { .api }
150
/**
151
* Acquire-release pattern for resource management
152
*/
153
def acquireRelease[R, E, A](
154
acquire: => ZIO[R, E, A]
155
)(release: A => URIO[R, Any]): ZIO[R with Scope, E, A]
156
157
/**
158
* Acquire-release with exit-aware cleanup
159
*/
160
def acquireReleaseExit[R, E, A](
161
acquire: => ZIO[R, E, A]
162
)(release: (A, Exit[Any, Any]) => URIO[R, Any]): ZIO[R with Scope, E, A]
163
164
/**
165
* Acquire multiple resources atomically
166
*/
167
def acquireReleaseAttempt[R, E, A](
168
acquire: ZIO[R, E, A]
169
)(release: A => URIO[R, Any]): ZIO[R with Scope, E, A]
170
171
/**
172
* Auto-closeable resource pattern (for Java interop)
173
*/
174
def fromAutoCloseable[R, E, A <: AutoCloseable](
175
fa: ZIO[R, E, A]
176
): ZIO[R with Scope, E, A]
177
178
/**
179
* Ensuring finalizer runs regardless of how effect completes
180
*/
181
def ensuring[R1 <: R](finalizer: => URIO[R1, Any]): ZIO[R1, E, A]
182
183
/**
184
* Add finalizer that only runs on specific exit cases
185
*/
186
def onExit[R1 <: R](cleanup: Exit[E, A] => URIO[R1, Any]): ZIO[R1, E, A]
187
```
188
189
**Usage Examples:**
190
191
```scala
192
// File processing with automatic cleanup
193
val fileProcessing = ZIO.scoped {
194
for {
195
file <- ZIO.acquireRelease(
196
ZIO.attempt(new BufferedReader(new FileReader("input.txt")))
197
)(reader => ZIO.succeed(reader.close()).ignore)
198
199
output <- ZIO.acquireRelease(
200
ZIO.attempt(new PrintWriter("output.txt"))
201
)(writer => ZIO.succeed(writer.close()).ignore)
202
203
_ <- processFile(file, output)
204
} yield ()
205
}
206
207
// Database connection management
208
val databaseWork = ZIO.scoped {
209
for {
210
connection <- ZIO.acquireRelease(
211
ZIO.attempt(DriverManager.getConnection(jdbcUrl))
212
)(conn => ZIO.succeed(conn.close()).ignore)
213
214
statement <- ZIO.acquireRelease(
215
ZIO.attempt(connection.prepareStatement(sql))
216
)(stmt => ZIO.succeed(stmt.close()).ignore)
217
218
results <- ZIO.attempt(statement.executeQuery())
219
data <- processResults(results)
220
} yield data
221
}
222
223
// Network resource management
224
val networkOperation = ZIO.scoped {
225
for {
226
socket <- ZIO.acquireReleaseExit(
227
ZIO.attempt(new Socket("localhost", 8080))
228
) { (socket, exit) =>
229
for {
230
_ <- Console.printLine(s"Closing socket. Exit: $exit")
231
_ <- ZIO.succeed(socket.close()).ignore
232
} yield ()
233
}
234
235
stream <- ZIO.fromAutoCloseable(
236
ZIO.attempt(socket.getInputStream())
237
)
238
239
data <- readFromStream(stream)
240
} yield data
241
}
242
```
243
244
### Advanced Resource Patterns
245
246
Complex resource management patterns for sophisticated applications.
247
248
```scala { .api }
249
// Resource pool pattern
250
class ResourcePool[R, E, A](
251
factory: ZIO[R, E, A],
252
cleanup: A => URIO[R, Any],
253
maxSize: Int
254
) {
255
private val available = Queue.bounded[A](maxSize)
256
private val inUse = Ref.make(Set.empty[A])
257
258
def acquire: ZIO[R with Scope, E, A] = ZIO.scoped {
259
for {
260
resource <- available.take.orElse(factory)
261
_ <- inUse.update(_ + resource)
262
_ <- Scope.addFinalizer(release(resource))
263
} yield resource
264
}
265
266
def release(resource: A): URIO[R, Unit] = {
267
for {
268
_ <- inUse.update(_ - resource)
269
_ <- available.offer(resource).ignore
270
} yield ()
271
}
272
273
def shutdown: URIO[R, Unit] = {
274
for {
275
resources <- available.takeAll
276
_ <- ZIO.foreachDiscard(resources)(cleanup)
277
} yield ()
278
}
279
}
280
281
// Cached resource pattern
282
class CachedResource[R, E, A](
283
factory: ZIO[R, E, A],
284
cleanup: A => URIO[R, Any],
285
ttl: Duration
286
) {
287
private val cache = Ref.make(Option.empty[(A, Long)])
288
289
def get: ZIO[R with Scope, E, A] = ZIO.scoped {
290
for {
291
now <- Clock.currentTime(TimeUnit.MILLISECONDS)
292
cached <- cache.get
293
294
resource <- cached match {
295
case Some((resource, timestamp)) if (now - timestamp) < ttl.toMillis =>
296
ZIO.succeed(resource)
297
case _ =>
298
for {
299
newResource <- factory
300
_ <- cache.set(Some((newResource, now)))
301
_ <- Scope.addFinalizer(cleanup(newResource))
302
} yield newResource
303
}
304
} yield resource
305
}
306
307
def invalidate: UIO[Unit] = cache.set(None)
308
}
309
310
// Scoped reference pattern
311
class ScopedRef[A](initialValue: A) {
312
private val ref = Ref.make(initialValue)
313
314
def scoped: ZIO[Scope, Nothing, Ref[A]] = {
315
for {
316
currentValue <- ref.get
317
scopedRef <- Ref.make(currentValue)
318
_ <- Scope.addFinalizerExit { exit =>
319
for {
320
finalValue <- scopedRef.get
321
_ <- ref.set(finalValue)
322
} yield ()
323
}
324
} yield scopedRef
325
}
326
327
def get: UIO[A] = ref.get
328
def set(a: A): UIO[Unit] = ref.set(a)
329
}
330
```
331
332
**Usage Examples:**
333
334
```scala
335
// HTTP client with connection pooling
336
val httpClientProgram = for {
337
pool <- ZIO.succeed(new ResourcePool(
338
factory = ZIO.attempt(HttpClientBuilder.create().build()),
339
cleanup = client => ZIO.succeed(client.close()).ignore,
340
maxSize = 10
341
))
342
343
results <- ZIO.foreachPar(urls) { url =>
344
ZIO.scoped {
345
for {
346
client <- pool.acquire
347
request <- ZIO.succeed(HttpGet(url))
348
response <- ZIO.attempt(client.execute(request))
349
body <- ZIO.attempt(EntityUtils.toString(response.getEntity))
350
} yield body
351
}
352
}
353
354
_ <- pool.shutdown
355
} yield results
356
357
// Cached database connection
358
val cachedDbProgram = for {
359
connectionCache <- ZIO.succeed(new CachedResource(
360
factory = ZIO.attempt(DriverManager.getConnection(url)),
361
cleanup = conn => ZIO.succeed(conn.close()).ignore,
362
ttl = 5.minutes
363
))
364
365
results <- ZIO.foreachPar(queries) { query =>
366
ZIO.scoped {
367
for {
368
conn <- connectionCache.get
369
stmt <- ZIO.attempt(conn.prepareStatement(query))
370
result <- ZIO.attempt(stmt.executeQuery())
371
data <- extractData(result)
372
} yield data
373
}
374
}
375
} yield results
376
377
// Hierarchical resource management
378
val hierarchicalResources = ZIO.scoped {
379
for {
380
// Top-level resources
381
_ <- Scope.addFinalizer(Console.printLine("Shutting down application"))
382
config <- loadConfiguration
383
384
// Mid-level resources
385
database <- ZIO.scoped {
386
for {
387
_ <- Scope.addFinalizer(Console.printLine("Closing database"))
388
db <- initializeDatabase(config.dbConfig)
389
} yield db
390
}
391
392
// Low-level resources
393
result <- ZIO.scoped {
394
for {
395
_ <- Scope.addFinalizer(Console.printLine("Cleaning up transaction"))
396
session <- database.createSession()
397
_ <- Scope.addFinalizer(ZIO.succeed(session.close()).ignore)
398
data <- session.query("SELECT * FROM users")
399
} yield data
400
}
401
} yield result
402
}
403
```