@zio.dev/zio-blocks 0.0.29 → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/compile-time-resource-safety-with-scope.md +997 -0
- package/guides/query-dsl-extending.md +1 -1
- package/guides/query-dsl-fluent-builder.md +203 -203
- package/guides/query-dsl-reified-optics.md +1 -1
- package/guides/query-dsl-sql.md +1 -1
- package/guides/zio-schema-migration.md +6 -6
- package/index.md +20 -14
- package/package.json +1 -1
- package/reference/codec.md +7 -7
- package/reference/context.md +1 -1
- package/reference/docs.md +1 -1
- package/reference/dynamic-schema.md +39 -42
- package/reference/media-type.md +2 -2
- package/reference/resource-management/defer-handle.md +246 -0
- package/reference/resource-management/finalization.md +286 -0
- package/reference/resource-management/finalizer.md +167 -0
- package/reference/{resource-management-di → resource-management}/index.md +3 -8
- package/reference/{resource-management-di → resource-management}/resource.md +532 -50
- package/reference/resource-management/scope.md +3026 -0
- package/reference/resource-management/unscoped.md +125 -0
- package/reference/{resource-management-di → resource-management}/wire.md +74 -28
- package/reference/schema-evolution/as.md +4 -4
- package/reference/schema-evolution/into.md +2 -2
- package/reference/schema-expr.md +2 -2
- package/reference/streams.md +989 -0
- package/reference/type-class-derivation.md +1 -1
- package/ringbuffer.md +1 -1
- package/sidebars.js +9 -4
- package/reference/resource-management-di/scope.md +0 -1423
package/reference/codec.md
CHANGED
|
@@ -48,23 +48,23 @@ val result: Either[SchemaError, Person] = Person.codec.decode(bytes)
|
|
|
48
48
|
To include the base schema module with JSON support, add the following dependency to your `build.sbt`:
|
|
49
49
|
|
|
50
50
|
```scala
|
|
51
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
51
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.30"
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
Additional format modules are separate artifacts:
|
|
55
55
|
|
|
56
56
|
```scala
|
|
57
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.
|
|
58
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.
|
|
59
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.
|
|
60
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.
|
|
61
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.
|
|
57
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.30"
|
|
58
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.30"
|
|
59
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.30"
|
|
60
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.30"
|
|
61
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.30"
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
For cross-platform projects (Scala.js):
|
|
65
65
|
|
|
66
66
|
```scala
|
|
67
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.
|
|
67
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.30"
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Supported Scala versions: 2.13.x and 3.x.
|
package/reference/context.md
CHANGED
|
@@ -116,7 +116,7 @@ val config = ctx.get[Config] // Compile-time proof it exists
|
|
|
116
116
|
Add the ZIO Blocks Context module to your `build.sbt`:
|
|
117
117
|
|
|
118
118
|
```scala
|
|
119
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.
|
|
119
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.30"
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
## Construction
|
package/reference/docs.md
CHANGED
|
@@ -10,7 +10,7 @@ Complete API reference for the zio-blocks-docs module - a zero-dependency GitHub
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
12
12
|
```scala
|
|
13
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.
|
|
13
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.30"
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
## Core Types
|
|
@@ -82,48 +82,45 @@ dynamic
|
|
|
82
82
|
// name = "name",
|
|
83
83
|
// value = Primitive(
|
|
84
84
|
// primitiveType = String(None),
|
|
85
|
-
// typeId =
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// selfType = None,
|
|
125
|
-
// aliasedTo = None,
|
|
126
|
-
// representation = None,
|
|
85
|
+
// typeId = String,
|
|
86
|
+
// primitiveBinding = zio.blocks.schema.binding.NoBinding$$anon$1@5b27ced8,
|
|
87
|
+
// doc = Doc(blocks = IndexedSeq(), metadata = Map()),
|
|
88
|
+
// modifiers = List(),
|
|
89
|
+
// storedDefaultValue = None,
|
|
90
|
+
// storedExamples = List()
|
|
91
|
+
// ),
|
|
92
|
+
// doc = Doc(blocks = IndexedSeq(), metadata = Map()),
|
|
93
|
+
// modifiers = List()
|
|
94
|
+
// ),
|
|
95
|
+
// Term(
|
|
96
|
+
// name = "age",
|
|
97
|
+
// value = Primitive(
|
|
98
|
+
// primitiveType = Int(None),
|
|
99
|
+
// typeId = Int,
|
|
100
|
+
// primitiveBinding = zio.blocks.schema.binding.NoBinding$$anon$1@5b27ced8,
|
|
101
|
+
// doc = Doc(blocks = IndexedSeq(), metadata = Map()),
|
|
102
|
+
// modifiers = List(),
|
|
103
|
+
// storedDefaultValue = None,
|
|
104
|
+
// storedExamples = List()
|
|
105
|
+
// ),
|
|
106
|
+
// doc = Doc(blocks = IndexedSeq(), metadata = Map()),
|
|
107
|
+
// modifiers = List()
|
|
108
|
+
// ),
|
|
109
|
+
// Term(
|
|
110
|
+
// name = "address",
|
|
111
|
+
// value = Record(
|
|
112
|
+
// fields = Vector(
|
|
113
|
+
// Term(
|
|
114
|
+
// name = "street",
|
|
115
|
+
// value = Primitive(
|
|
116
|
+
// primitiveType = String(None),
|
|
117
|
+
// typeId = String,
|
|
118
|
+
// primitiveBinding = zio.blocks.schema.binding.NoBinding$$anon$1@5b27ced8,
|
|
119
|
+
// doc = Doc(blocks = IndexedSeq(), metadata = Map()),
|
|
120
|
+
// modifiers = List(),
|
|
121
|
+
// storedDefaultValue = None,
|
|
122
|
+
// storedExamples = List()
|
|
123
|
+
// ),
|
|
127
124
|
// ...
|
|
128
125
|
```
|
|
129
126
|
|
package/reference/media-type.md
CHANGED
|
@@ -75,13 +75,13 @@ textAny.matches(html) // true
|
|
|
75
75
|
Add the following to your `build.sbt`:
|
|
76
76
|
|
|
77
77
|
```scala
|
|
78
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-mediatype" % "0.0.
|
|
78
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-mediatype" % "0.0.30"
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
For cross-platform projects (Scala.js):
|
|
82
82
|
|
|
83
83
|
```scala
|
|
84
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-mediatype" % "0.0.
|
|
84
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-mediatype" % "0.0.30"
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
Supported Scala versions: 2.13.x and 3.x.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: defer-handle
|
|
3
|
+
title: "DeferHandle"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`DeferHandle` is a handle returned by `Scope.defer` that allows cancelling a registered finalizer before the scope closes:
|
|
7
|
+
|
|
8
|
+
```scala
|
|
9
|
+
abstract class DeferHandle {
|
|
10
|
+
def cancel(): Unit
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
When `Scope#defer(cleanup)` is called, the cleanup action is registered and a `DeferHandle` is returned. This handle can be used to remove that finalizer early, preventing it from running when the scope closes. This is useful when a resource is explicitly released before the scope ends, and running the finalizer again would be unnecessary or harmful.
|
|
15
|
+
|
|
16
|
+
## Construction
|
|
17
|
+
|
|
18
|
+
`DeferHandle` is not instantiated directly. Instead, it is created by calling `Scope#defer` with a cleanup action:
|
|
19
|
+
|
|
20
|
+
```scala
|
|
21
|
+
import zio.blocks.scope.{Scope, DeferHandle}
|
|
22
|
+
|
|
23
|
+
trait Scope {
|
|
24
|
+
def defer(cleanup: => Unit): DeferHandle
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The following example demonstrates creating a `DeferHandle`:
|
|
29
|
+
|
|
30
|
+
```scala
|
|
31
|
+
import zio.blocks.scope.Scope
|
|
32
|
+
|
|
33
|
+
Scope.global.scoped { scope =>
|
|
34
|
+
import scope._
|
|
35
|
+
|
|
36
|
+
val handle = defer {
|
|
37
|
+
println("This cleanup will run when scope closes, unless cancelled")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The handle can now be used to cancel the cleanup
|
|
41
|
+
handle.cancel()
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Core Operations
|
|
46
|
+
|
|
47
|
+
The `DeferHandle#cancel` method removes the registered finalizer so it will not run when the scope closes:
|
|
48
|
+
|
|
49
|
+
```scala
|
|
50
|
+
trait DeferHandle {
|
|
51
|
+
def cancel(): Unit
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This method is:
|
|
56
|
+
|
|
57
|
+
- **Thread-safe**: Can be called from any thread without synchronization
|
|
58
|
+
- **Idempotent**: Calling it multiple times has the same effect as calling once
|
|
59
|
+
|
|
60
|
+
If the scope has already closed (and the finalizer has already run or been discarded), calling `DeferHandle#cancel` is a no-op. In the following example, we register a cleanup action, then cancel it before the scope closes:
|
|
61
|
+
|
|
62
|
+
```scala
|
|
63
|
+
import zio.blocks.scope.Scope
|
|
64
|
+
import java.io.ByteArrayOutputStream
|
|
65
|
+
|
|
66
|
+
Scope.global.scoped { scope =>
|
|
67
|
+
import scope._
|
|
68
|
+
|
|
69
|
+
val buffer = allocate(ByteArrayOutputStream())
|
|
70
|
+
val closeHandle = defer {
|
|
71
|
+
println("Auto-closing buffer")
|
|
72
|
+
$(buffer)(_.close())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Manually close the buffer
|
|
76
|
+
$(buffer)(_.close())
|
|
77
|
+
|
|
78
|
+
// Cancel the automatic finalizer since we already closed it
|
|
79
|
+
closeHandle.cancel()
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Use Cases
|
|
84
|
+
|
|
85
|
+
`DeferHandle` is useful in several common scenarios:
|
|
86
|
+
|
|
87
|
+
### Preventing Duplicate Cleanup
|
|
88
|
+
|
|
89
|
+
When a resource is explicitly released before the scope ends, cancel the automatic finalizer to avoid duplicate cleanup:
|
|
90
|
+
|
|
91
|
+
```scala
|
|
92
|
+
import zio.blocks.scope.Scope
|
|
93
|
+
import java.io.ByteArrayOutputStream
|
|
94
|
+
|
|
95
|
+
val result = Scope.global.scoped { scope =>
|
|
96
|
+
import scope._
|
|
97
|
+
|
|
98
|
+
val buffer = allocate(ByteArrayOutputStream())
|
|
99
|
+
|
|
100
|
+
val finalizeHandle = defer {
|
|
101
|
+
println(s"Finalizer running, buffer closing")
|
|
102
|
+
$(buffer)(_.close())
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Explicit cleanup
|
|
106
|
+
$(buffer) { buf =>
|
|
107
|
+
buf.write("data".getBytes)
|
|
108
|
+
println(s"Manual use: buffer has ${buf.size()} bytes")
|
|
109
|
+
buf.close()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Cancel the automatic finalizer
|
|
113
|
+
finalizeHandle.cancel()
|
|
114
|
+
|
|
115
|
+
"done"
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Conditional Cleanup
|
|
120
|
+
|
|
121
|
+
Cancel finalizers based on runtime conditions:
|
|
122
|
+
|
|
123
|
+
```scala
|
|
124
|
+
import zio.blocks.scope.Scope
|
|
125
|
+
|
|
126
|
+
def acquireResource(shouldCleanup: Boolean) = Scope.global.scoped { scope =>
|
|
127
|
+
import scope._
|
|
128
|
+
|
|
129
|
+
val resource = "important resource"
|
|
130
|
+
val handle = scope.defer {
|
|
131
|
+
println("Cleaning up resource")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!shouldCleanup) {
|
|
135
|
+
handle.cancel()
|
|
136
|
+
println("Cleanup disabled")
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
resource
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
acquireResource(shouldCleanup = false)
|
|
143
|
+
acquireResource(shouldCleanup = true)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Transferring Ownership
|
|
147
|
+
|
|
148
|
+
When transferring a resource to external management, cancel its finalizer so the external system can control cleanup:
|
|
149
|
+
|
|
150
|
+
```scala
|
|
151
|
+
import zio.blocks.scope.Scope
|
|
152
|
+
import java.io.ByteArrayInputStream
|
|
153
|
+
|
|
154
|
+
val result = Scope.global.scoped { scope =>
|
|
155
|
+
import scope._
|
|
156
|
+
|
|
157
|
+
val stream = allocate(ByteArrayInputStream("data".getBytes))
|
|
158
|
+
val handle = defer {
|
|
159
|
+
println("Scope finalizer would close stream")
|
|
160
|
+
$(stream)(_.close())
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Transfer ownership to external manager
|
|
164
|
+
// (In real code, this might pass to a thread pool or async framework)
|
|
165
|
+
handle.cancel() // Let the manager handle cleanup
|
|
166
|
+
|
|
167
|
+
"transferred"
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Noop Handle
|
|
172
|
+
|
|
173
|
+
When `defer()` is called on an already-closed scope, a no-op handle is returned:
|
|
174
|
+
|
|
175
|
+
```scala
|
|
176
|
+
import zio.blocks.scope.Scope
|
|
177
|
+
|
|
178
|
+
Scope.global.scoped { scope =>
|
|
179
|
+
import scope._
|
|
180
|
+
|
|
181
|
+
val handle = scope.defer {
|
|
182
|
+
println("This will run when scope closes")
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Subsequent calls to cancel() remove the finalizer
|
|
186
|
+
handle.cancel()
|
|
187
|
+
|
|
188
|
+
println("Finalizer has been cancelled")
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Thread Safety
|
|
193
|
+
|
|
194
|
+
`DeferHandle` is thread-safe. Multiple threads can call `cancel()` on the same handle without external synchronization:
|
|
195
|
+
|
|
196
|
+
```scala
|
|
197
|
+
import zio.blocks.scope.Scope
|
|
198
|
+
import java.util.concurrent.CountDownLatch
|
|
199
|
+
import java.util.concurrent.Executors
|
|
200
|
+
|
|
201
|
+
val result = Scope.global.scoped { scope =>
|
|
202
|
+
import scope._
|
|
203
|
+
|
|
204
|
+
val handle = defer {
|
|
205
|
+
println("Finalizer")
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Use a thread pool to simulate concurrent access
|
|
209
|
+
val executor = Executors.newFixedThreadPool(5)
|
|
210
|
+
val latch = new CountDownLatch(5)
|
|
211
|
+
|
|
212
|
+
(1 to 5).foreach { i =>
|
|
213
|
+
executor.submit(new Runnable {
|
|
214
|
+
def run(): Unit = {
|
|
215
|
+
handle.cancel()
|
|
216
|
+
println(s"Thread $i cancelled")
|
|
217
|
+
latch.countDown()
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Wait for all threads to finish
|
|
223
|
+
latch.await()
|
|
224
|
+
executor.shutdown()
|
|
225
|
+
|
|
226
|
+
"completed"
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## See Also
|
|
231
|
+
|
|
232
|
+
- [`Scope#defer`](./scope.md#registering-finalizers) — the method that returns a `DeferHandle`
|
|
233
|
+
- [`Finalizer`](./finalizer.md) — the trait defining `Finalizer#defer`
|
|
234
|
+
- [`Finalization`](./finalization.md) — the result of running all finalizers
|
|
235
|
+
|
|
236
|
+
## Integration
|
|
237
|
+
|
|
238
|
+
`DeferHandle` is part of ZIO Blocks' resource management system. It works directly with:
|
|
239
|
+
|
|
240
|
+
- **[`Scope`](./scope.md)** — The primary way to create a `DeferHandle` is via `Scope#defer`. A scope manages multiple finalizers and runs them all when the scope closes. `DeferHandle` allows selective cancellation of individual finalizers before that happens.
|
|
241
|
+
|
|
242
|
+
- **[`Finalizer`](./finalizer.md)** — `Finalizer` defines the `Finalizer#defer` operation that returns a `DeferHandle`. It abstracts the concept of registering cleanup actions.
|
|
243
|
+
|
|
244
|
+
- **[`Finalization`](./finalization.md)** — When a scope closes, it runs all registered finalizers. A cancelled `DeferHandle` removes its associated finalizer from this process.
|
|
245
|
+
|
|
246
|
+
Together, these types form the foundation of compile-time resource safety in ZIO Blocks, allowing you to manage resource lifecycles with certainty that cleanup will occur exactly when needed.
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: finalization
|
|
3
|
+
title: "Finalization"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`Finalization` is the result of running all finalizers in a scope, collecting any errors that occurred during cleanup:
|
|
7
|
+
|
|
8
|
+
```scala
|
|
9
|
+
import zio.blocks.chunk.Chunk
|
|
10
|
+
|
|
11
|
+
abstract class Finalization(val errors: Chunk[Throwable]) {
|
|
12
|
+
def isEmpty: Boolean
|
|
13
|
+
def nonEmpty: Boolean
|
|
14
|
+
def orThrow(): Unit
|
|
15
|
+
def suppress(initial: Throwable): Throwable
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
When a scope closes, each registered finalizer runs in LIFO order. If any finalizer throws an exception, that error is caught and collected into a `Finalization`. This type ensures that all finalizers run even if some fail, and allows the caller to decide how to handle accumulated errors.
|
|
20
|
+
|
|
21
|
+
`Finalization` collects errors from finalizers in a `Chunk[Throwable]`. The first error in the chunk corresponds to the head of the chunk (the first finalizer that failed in LIFO execution order).
|
|
22
|
+
|
|
23
|
+
## Core Methods
|
|
24
|
+
|
|
25
|
+
The following four methods allow you to inspect and handle errors from finalization:
|
|
26
|
+
|
|
27
|
+
### `Finalization#isEmpty`
|
|
28
|
+
|
|
29
|
+
Returns `true` if no finalizer errors were collected:
|
|
30
|
+
|
|
31
|
+
```scala
|
|
32
|
+
import zio.blocks.chunk.Chunk
|
|
33
|
+
|
|
34
|
+
abstract class Finalization(val errors: Chunk[Throwable]) {
|
|
35
|
+
def isEmpty: Boolean
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Here's an example of checking if finalization succeeded:
|
|
40
|
+
|
|
41
|
+
```scala
|
|
42
|
+
import zio.blocks.scope.Scope
|
|
43
|
+
|
|
44
|
+
Scope.global.scoped { scope =>
|
|
45
|
+
import scope._
|
|
46
|
+
|
|
47
|
+
$(open()) { openScope =>
|
|
48
|
+
import openScope.scope._
|
|
49
|
+
defer {
|
|
50
|
+
println("Cleanup")
|
|
51
|
+
}
|
|
52
|
+
val fin = openScope.close()
|
|
53
|
+
if (fin.isEmpty) println("No errors") else println("Errors occurred")
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `Finalization#nonEmpty`
|
|
59
|
+
|
|
60
|
+
Returns `true` if at least one finalizer error was collected:
|
|
61
|
+
|
|
62
|
+
```scala
|
|
63
|
+
import zio.blocks.chunk.Chunk
|
|
64
|
+
|
|
65
|
+
abstract class Finalization(val errors: Chunk[Throwable]) {
|
|
66
|
+
def nonEmpty: Boolean
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Here's an example of checking for errors:
|
|
71
|
+
|
|
72
|
+
```scala
|
|
73
|
+
import zio.blocks.scope.Scope
|
|
74
|
+
|
|
75
|
+
Scope.global.scoped { scope =>
|
|
76
|
+
import scope._
|
|
77
|
+
|
|
78
|
+
$(open()) { openScope =>
|
|
79
|
+
import openScope.scope._
|
|
80
|
+
defer {
|
|
81
|
+
throw new Exception("Cleanup failed")
|
|
82
|
+
}
|
|
83
|
+
val fin = openScope.close()
|
|
84
|
+
if (fin.nonEmpty) {
|
|
85
|
+
println(s"Errors occurred: ${fin.errors.length}")
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `Finalization#orThrow()`
|
|
92
|
+
|
|
93
|
+
Throws the first collected error with all remaining errors added as suppressed exceptions. Does nothing if there are no errors:
|
|
94
|
+
|
|
95
|
+
```scala
|
|
96
|
+
import zio.blocks.chunk.Chunk
|
|
97
|
+
|
|
98
|
+
abstract class Finalization(val errors: Chunk[Throwable]) {
|
|
99
|
+
def orThrow(): Unit
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The first error corresponds to the head of the chunk (the first finalizer that failed in LIFO execution order). Remaining errors are attached as suppressed exceptions using `addSuppressed()`. Here's an example:
|
|
104
|
+
|
|
105
|
+
```scala
|
|
106
|
+
import zio.blocks.scope.Scope
|
|
107
|
+
|
|
108
|
+
Scope.global.scoped { scope =>
|
|
109
|
+
import scope._
|
|
110
|
+
$(open()) { openScope =>
|
|
111
|
+
defer { throw Exception("Error 2") }
|
|
112
|
+
defer { throw Exception("Error 1") }
|
|
113
|
+
val fin = openScope.close()
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
fin.orThrow()
|
|
117
|
+
} catch {
|
|
118
|
+
case e: Exception =>
|
|
119
|
+
println(s"Primary: ${e.getMessage}")
|
|
120
|
+
e.getSuppressed.foreach(s => println(s"Suppressed: ${s.getMessage}"))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `Finalization#suppress(initial)`
|
|
127
|
+
|
|
128
|
+
Adds all collected finalizer errors as suppressed exceptions to `initial` and returns it. If there are no errors, `initial` is returned unchanged:
|
|
129
|
+
|
|
130
|
+
```scala
|
|
131
|
+
import zio.blocks.chunk.Chunk
|
|
132
|
+
|
|
133
|
+
abstract class Finalization(val errors: Chunk[Throwable]) {
|
|
134
|
+
def suppress(initial: Throwable): Throwable
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
This is useful when you want to preserve the original error context while attaching cleanup errors. Here's an example:
|
|
139
|
+
|
|
140
|
+
```scala
|
|
141
|
+
import zio.blocks.scope.Scope
|
|
142
|
+
|
|
143
|
+
Scope.global.scoped { scope =>
|
|
144
|
+
import scope._
|
|
145
|
+
|
|
146
|
+
val initialError = Exception("Original error")
|
|
147
|
+
|
|
148
|
+
$(open()) { openScope =>
|
|
149
|
+
defer { throw Exception("Cleanup error") }
|
|
150
|
+
val fin = openScope.close()
|
|
151
|
+
|
|
152
|
+
val combined = fin.suppress(initialError)
|
|
153
|
+
println(s"Primary: ${combined.getMessage}")
|
|
154
|
+
combined.getSuppressed.foreach(s => println(s"Suppressed: ${s.getMessage}"))
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Error Ordering
|
|
160
|
+
|
|
161
|
+
Errors in the finalization are ordered by when finalizers ran (in LIFO sequence):
|
|
162
|
+
|
|
163
|
+
```scala
|
|
164
|
+
import zio.blocks.scope.Scope
|
|
165
|
+
|
|
166
|
+
Scope.global.scoped { scope =>
|
|
167
|
+
import scope._
|
|
168
|
+
|
|
169
|
+
$(open()) { openScope =>
|
|
170
|
+
// Registered first, runs last (LIFO)
|
|
171
|
+
defer { throw Exception("Error 1") }
|
|
172
|
+
|
|
173
|
+
// Registered second, runs first
|
|
174
|
+
defer { throw Exception("Error 2") }
|
|
175
|
+
|
|
176
|
+
// Registered third, runs first
|
|
177
|
+
defer { throw Exception("Error 3") }
|
|
178
|
+
|
|
179
|
+
val fin = openScope.close()
|
|
180
|
+
|
|
181
|
+
// Errors list order: [Error 3, Error 2, Error 1]
|
|
182
|
+
println(s"First error: ${fin.errors.head.getMessage}")
|
|
183
|
+
println(s"Total errors: ${fin.errors.length}")
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Use Cases
|
|
189
|
+
|
|
190
|
+
Here are common scenarios where finalization handling is useful:
|
|
191
|
+
|
|
192
|
+
### Conditional Error Handling
|
|
193
|
+
|
|
194
|
+
Check if errors occurred and handle them appropriately:
|
|
195
|
+
|
|
196
|
+
```scala
|
|
197
|
+
import zio.blocks.scope.Scope
|
|
198
|
+
|
|
199
|
+
Scope.global.scoped { scope =>
|
|
200
|
+
import scope._
|
|
201
|
+
|
|
202
|
+
$(open()) { openScope =>
|
|
203
|
+
defer {
|
|
204
|
+
println("Cleanup complete")
|
|
205
|
+
}
|
|
206
|
+
val fin = openScope.close()
|
|
207
|
+
|
|
208
|
+
if (fin.nonEmpty) {
|
|
209
|
+
fin.orThrow()
|
|
210
|
+
} else {
|
|
211
|
+
println("No errors during finalization")
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Combining Multiple Error Sources
|
|
218
|
+
|
|
219
|
+
Attach cleanup errors to an existing error:
|
|
220
|
+
|
|
221
|
+
```scala
|
|
222
|
+
import zio.blocks.scope.Scope
|
|
223
|
+
|
|
224
|
+
def doWork(): Unit = {
|
|
225
|
+
Scope.global.scoped { scope =>
|
|
226
|
+
import scope._
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
throw Exception("Work failed")
|
|
230
|
+
} catch {
|
|
231
|
+
case workError: Exception =>
|
|
232
|
+
$(open()) { openScope =>
|
|
233
|
+
defer { throw Exception("Cleanup failed") }
|
|
234
|
+
val fin = openScope.close()
|
|
235
|
+
val combined = fin.suppress(workError)
|
|
236
|
+
throw combined
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
() // Return unit on normal path
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
doWork()
|
|
245
|
+
} catch {
|
|
246
|
+
case e: Exception =>
|
|
247
|
+
println(s"Work: ${e.getMessage}")
|
|
248
|
+
e.getSuppressed.foreach(s => println(s"During cleanup: ${s.getMessage}"))
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Logging All Cleanup Errors
|
|
253
|
+
|
|
254
|
+
Inspect and log all errors without stopping execution:
|
|
255
|
+
|
|
256
|
+
```scala
|
|
257
|
+
import zio.blocks.scope.Scope
|
|
258
|
+
|
|
259
|
+
Scope.global.scoped { scope =>
|
|
260
|
+
import scope._
|
|
261
|
+
|
|
262
|
+
$(open()) { openScope =>
|
|
263
|
+
defer { throw Exception("Error 1") }
|
|
264
|
+
defer { throw Exception("Error 2") }
|
|
265
|
+
val fin = openScope.close()
|
|
266
|
+
|
|
267
|
+
if (fin.nonEmpty) {
|
|
268
|
+
println(s"Finalization collected ${fin.errors.length} errors:")
|
|
269
|
+
fin.errors.foreach(e => println(s" - ${e.getMessage}"))
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Relationship to Scope
|
|
276
|
+
|
|
277
|
+
`Finalization` is returned by:
|
|
278
|
+
|
|
279
|
+
- `Scope.open().close()` — when explicitly closing a scope
|
|
280
|
+
- `Scope.global.runFinalizers()` — when running global finalizers on shutdown
|
|
281
|
+
|
|
282
|
+
## See Also
|
|
283
|
+
|
|
284
|
+
- [`Scope#defer`](./scope.md) — registers finalizers that produce errors
|
|
285
|
+
- [`DeferHandle`](./defer-handle.md) — handle for cancelling finalizers
|
|
286
|
+
- [`Finalizer`](./finalizer.md) — the trait for registering cleanup actions
|