0
# Resource Management
1
2
Automatic resource management with try-with-resources semantics through the Using utility. Provides safe resource handling that ensures proper cleanup even in the presence of exceptions.
3
4
## Capabilities
5
6
### Single Resource Management
7
8
Manage a single resource with automatic cleanup through the Releasable typeclass.
9
10
```scala { .api }
11
object Using {
12
/**
13
* Use a resource in a Try context with automatic cleanup
14
* @param resource Resource to manage (call-by-name for lazy evaluation)
15
* @param f Operation to perform on the resource
16
* @param releasable Typeclass instance for resource cleanup
17
* @return Try containing the result or any exception
18
*/
19
def apply[R: Releasable, A](resource: => R)(f: R => A): Try[A]
20
21
/**
22
* Use a resource with automatic cleanup, throwing exceptions directly
23
* @param resource Resource to manage
24
* @param body Operation to perform on the resource
25
* @param releasable Typeclass instance for resource cleanup
26
* @return Result of the operation
27
* @throws Exception if resource creation or operation fails
28
*/
29
def resource[R, A](resource: R)(body: R => A)(implicit releasable: Releasable[R]): A
30
}
31
```
32
33
**Usage Examples:**
34
35
```scala
36
import scala.util.Using
37
import java.io.{FileInputStream, BufferedReader, FileReader}
38
39
// Using with Try (exception handling)
40
val content = Using(new FileInputStream("data.txt")) { fis =>
41
val bytes = new Array[Byte](fis.available())
42
fis.read(bytes)
43
new String(bytes)
44
}
45
46
content match {
47
case Success(data) => println(s"File content: $data")
48
case Failure(exception) => println(s"Error reading file: $exception")
49
}
50
51
// Using with direct exceptions
52
try {
53
val result = Using.resource(new BufferedReader(new FileReader("config.txt"))) { reader =>
54
reader.lines().toArray.mkString("\n")
55
}
56
println(result)
57
} catch {
58
case ex: Exception => println(s"Failed to read config: $ex")
59
}
60
61
// Combining with other operations
62
val processedData = Using(new FileInputStream("input.dat")) { inputStream =>
63
val data = readBinaryData(inputStream)
64
processData(data)
65
transformData(data)
66
}.getOrElse(defaultData)
67
```
68
69
### Multiple Resource Management
70
71
Manage multiple resources simultaneously with guaranteed cleanup of all resources.
72
73
```scala { .api }
74
/**
75
* Manage two resources with automatic cleanup
76
* @param resource1 First resource to manage
77
* @param resource2 Second resource to manage (call-by-name)
78
* @param body Operation to perform on both resources
79
* @return Result of the operation
80
* @throws Exception if any resource creation or operation fails
81
*/
82
def resources[R1: Releasable, R2: Releasable, A](
83
resource1: R1,
84
resource2: => R2
85
)(body: (R1, R2) => A): A
86
87
/**
88
* Manage three resources with automatic cleanup
89
* @param resource1 First resource to manage
90
* @param resource2 Second resource to manage (call-by-name)
91
* @param resource3 Third resource to manage (call-by-name)
92
* @param body Operation to perform on all resources
93
* @return Result of the operation
94
*/
95
def resources[R1: Releasable, R2: Releasable, R3: Releasable, A](
96
resource1: R1,
97
resource2: => R2,
98
resource3: => R3
99
)(body: (R1, R2, R3) => A): A
100
101
/**
102
* Manage four resources with automatic cleanup
103
* @param resource1 First resource to manage
104
* @param resource2 Second resource to manage (call-by-name)
105
* @param resource3 Third resource to manage (call-by-name)
106
* @param resource4 Fourth resource to manage (call-by-name)
107
* @param body Operation to perform on all resources
108
* @return Result of the operation
109
*/
110
def resources[R1: Releasable, R2: Releasable, R3: Releasable, R4: Releasable, A](
111
resource1: R1,
112
resource2: => R2,
113
resource3: => R3,
114
resource4: => R4
115
)(body: (R1, R2, R3, R4) => A): A
116
```
117
118
**Usage Examples:**
119
120
```scala
121
import scala.util.Using
122
import java.io.{FileInputStream, FileOutputStream, BufferedReader, FileReader}
123
124
// Copy file with automatic cleanup of both streams
125
Using.resources(
126
new FileInputStream("source.txt"),
127
new FileOutputStream("destination.txt")
128
) { (input, output) =>
129
val buffer = new Array[Byte](8192)
130
var bytesRead = 0
131
while ({ bytesRead = input.read(buffer); bytesRead != -1 }) {
132
output.write(buffer, 0, bytesRead)
133
}
134
}
135
136
// Process multiple files
137
Using.resources(
138
new BufferedReader(new FileReader("config.properties")),
139
new BufferedReader(new FileReader("data.csv")),
140
new FileOutputStream("report.txt")
141
) { (configReader, dataReader, reportOutput) =>
142
val config = loadProperties(configReader)
143
val data = parseCSV(dataReader)
144
val report = generateReport(config, data)
145
reportOutput.write(report.getBytes)
146
}
147
148
// Database operations with multiple connections
149
Using.resources(
150
createReadConnection(),
151
createWriteConnection()
152
) { (readConn, writeConn) =>
153
val data = fetchData(readConn)
154
val processed = processData(data)
155
saveResults(writeConn, processed)
156
}
157
```
158
159
### Resource Manager
160
161
Advanced resource management with a manager that can handle an arbitrary number of resources.
162
163
```scala { .api }
164
final class Manager {
165
/**
166
* Register and return a resource for management
167
* @param resource Resource to manage
168
* @param releasable Typeclass instance for resource cleanup
169
* @return The resource itself
170
*/
171
def apply[R: Releasable](resource: R): R
172
173
/**
174
* Register a resource for management without returning it
175
* @param resource Resource to manage
176
* @param releasable Typeclass instance for resource cleanup
177
*/
178
def acquire[R: Releasable](resource: R): Unit
179
}
180
181
object Manager {
182
/**
183
* Execute operation with resource manager
184
* @param op Operation that uses the manager to acquire resources
185
* @return Try containing the result or any exception
186
*/
187
def apply[A](op: Manager => A): Try[A]
188
}
189
```
190
191
**Usage Examples:**
192
193
```scala
194
import scala.util.Using
195
196
// Managing multiple resources dynamically
197
val result = Using.Manager { manager =>
198
val file1 = manager(new FileInputStream("file1.txt"))
199
val file2 = manager(new FileInputStream("file2.txt"))
200
val output = manager(new FileOutputStream("merged.txt"))
201
202
// All resources are automatically managed
203
mergeFiles(file1, file2, output)
204
}
205
206
// Conditional resource acquisition
207
Using.Manager { manager =>
208
val primaryDB = manager(connectToPrimaryDatabase())
209
210
val backupDB = if (primaryDB.isHealthy) {
211
None
212
} else {
213
Some(manager(connectToBackupDatabase()))
214
}
215
216
performDatabaseOperations(primaryDB, backupDB)
217
}
218
219
// Resource acquisition in loops
220
Using.Manager { manager =>
221
val outputFiles = (1 to 10).map { i =>
222
manager(new FileOutputStream(s"output_$i.txt"))
223
}
224
225
// Process data and write to all files
226
processDataToMultipleFiles(inputData, outputFiles)
227
}
228
```
229
230
### Releasable Typeclass
231
232
The Releasable typeclass defines how resources should be cleaned up.
233
234
```scala { .api }
235
trait Releasable[-R] {
236
/**
237
* Release the resource, cleaning up any held resources
238
* @param resource Resource to release
239
*/
240
def release(resource: R): Unit
241
}
242
243
object Releasable {
244
/**
245
* Implicit instance for AutoCloseable resources
246
*/
247
implicit object AutoCloseableIsReleasable extends Releasable[AutoCloseable] {
248
def release(resource: AutoCloseable): Unit = resource.close()
249
}
250
251
/**
252
* Implicit instance for scala.io.Source (Scala 2.11 only)
253
*/
254
implicit object SourceReleasable extends Releasable[Source] {
255
def release(resource: Source): Unit = resource.close()
256
}
257
}
258
```
259
260
**Custom Releasable Instances:**
261
262
```scala
263
import scala.util.Using.Releasable
264
265
// Custom resource type
266
case class DatabaseConnection(url: String) {
267
def close(): Unit = {
268
// Close database connection
269
println(s"Closing connection to $url")
270
}
271
}
272
273
// Custom Releasable instance
274
implicit val dbReleasable: Releasable[DatabaseConnection] = new Releasable[DatabaseConnection] {
275
def release(resource: DatabaseConnection): Unit = resource.close()
276
}
277
278
// Usage with custom resource
279
Using(DatabaseConnection("jdbc:mysql://localhost/db")) { conn =>
280
// Perform database operations
281
executeQuery(conn, "SELECT * FROM users")
282
}
283
284
// Thread pool management
285
case class ThreadPool(size: Int) {
286
def shutdown(): Unit = {
287
println(s"Shutting down thread pool of size $size")
288
}
289
}
290
291
implicit val threadPoolReleasable: Releasable[ThreadPool] =
292
(resource: ThreadPool) => resource.shutdown()
293
294
Using(ThreadPool(10)) { pool =>
295
// Execute tasks using thread pool
296
executeTasks(pool, tasks)
297
}
298
```
299
300
### Exception Handling Patterns
301
302
**Graceful Degradation:**
303
304
```scala
305
import scala.util.Using
306
307
def readConfigWithFallback(primaryPath: String, fallbackPath: String): Config = {
308
Using(new FileInputStream(primaryPath)) { input =>
309
parseConfig(input)
310
}.recoverWith { case _ =>
311
Using(new FileInputStream(fallbackPath)) { input =>
312
parseConfig(input)
313
}
314
}.getOrElse(defaultConfig)
315
}
316
```
317
318
**Resource Validation:**
319
320
```scala
321
import scala.util.Using
322
323
def validateAndProcessFile(path: String): Either[String, ProcessedData] = {
324
Using(new FileInputStream(path)) { input =>
325
if (input.available() == 0) {
326
Left("File is empty")
327
} else {
328
val data = readData(input)
329
if (isValidData(data)) {
330
Right(processData(data))
331
} else {
332
Left("Invalid data format")
333
}
334
}
335
}.fold(
336
exception => Left(s"Error reading file: ${exception.getMessage}"),
337
identity
338
)
339
}
340
```
341
342
**Retry with Resources:**
343
344
```scala
345
import scala.util.Using
346
import scala.util.control.NonFatal
347
348
def withRetry[A](maxAttempts: Int)(operation: => A): Option[A] = {
349
(1 to maxAttempts).view.map { attempt =>
350
try {
351
Some(operation)
352
} catch {
353
case NonFatal(e) =>
354
if (attempt == maxAttempts) {
355
println(s"All $maxAttempts attempts failed")
356
None
357
} else {
358
println(s"Attempt $attempt failed, retrying...")
359
Thread.sleep(1000 * attempt) // Exponential backoff
360
None
361
}
362
}
363
}.find(_.isDefined).flatten
364
}
365
366
// Usage with resource management
367
withRetry(3) {
368
Using(connectToDatabase()) { conn =>
369
executeQuery(conn, "SELECT COUNT(*) FROM users")
370
}.get
371
}
372
```
373
374
### Advanced Patterns
375
376
**Resource Pooling:**
377
378
```scala
379
import scala.util.Using
380
import java.util.concurrent.BlockingQueue
381
382
class ResourcePool[R: Releasable](factory: () => R, maxSize: Int) {
383
private val pool: BlockingQueue[R] = new java.util.concurrent.LinkedBlockingQueue[R](maxSize)
384
385
def withResource[A](operation: R => A): A = {
386
val resource = Option(pool.poll()).getOrElse(factory())
387
try {
388
val result = operation(resource)
389
if (pool.remainingCapacity() > 0) {
390
pool.offer(resource)
391
} else {
392
implicitly[Releasable[R]].release(resource)
393
}
394
result
395
} catch {
396
case exception =>
397
implicitly[Releasable[R]].release(resource)
398
throw exception
399
}
400
}
401
}
402
403
// Usage
404
val dbPool = new ResourcePool(() => createDatabaseConnection(), 10)
405
406
dbPool.withResource { conn =>
407
executeQuery(conn, "SELECT * FROM products")
408
}
409
```
410
411
**Nested Resource Management:**
412
413
```scala
414
import scala.util.Using
415
416
def processNestedResources(): Unit = {
417
Using(openArchive("data.zip")) { archive =>
418
archive.entries.foreach { entry =>
419
Using(archive.getInputStream(entry)) { entryStream =>
420
Using(new BufferedReader(new InputStreamReader(entryStream))) { reader =>
421
processFileContent(entry.getName, reader)
422
}
423
}
424
}
425
}
426
}
427
```
428
429
The Using utility provides a robust foundation for resource management in Scala applications, ensuring that resources are properly cleaned up regardless of whether operations succeed or fail.