@zio.dev/zio-blocks 0.0.51 → 0.0.56
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/adr/2026-07-18-data-migration.md +123 -0
- package/guides/async-getting-started.md +687 -0
- package/guides/compile-time-resource-safety-with-scope.md +6 -0
- package/guides/getting-started-with-mux.md +0 -112
- package/guides/query-dsl-extending.md +1 -1
- package/guides/query-dsl-fluent-builder.md +1 -1
- package/guides/query-dsl-reified-optics.md +1 -1
- package/guides/query-dsl-sql.md +395 -1
- package/guides/sql-checked-interpolation.md +173 -0
- package/guides/sql-transactions.md +286 -0
- package/guides/telemetry-guide.md +131 -70
- package/guides/zio-schema-migration.md +6 -6
- package/index.md +200 -559
- package/package.json +1 -1
- package/reference/async.md +1379 -531
- package/reference/chunk.md +3 -3
- package/reference/codegen/index.md +1 -1
- package/reference/combinators.md +4 -4
- package/reference/config/config-decoder.md +460 -0
- package/reference/config/config-source.md +489 -0
- package/reference/config/errors.md +278 -0
- package/reference/config/flags.md +369 -0
- package/reference/config/formats.md +314 -0
- package/reference/config/index.md +304 -0
- package/reference/config/rollout.md +336 -0
- package/reference/context.md +6 -49
- package/reference/data-migration.md +269 -0
- package/reference/datastar/attributes.md +302 -0
- package/reference/datastar/events.md +234 -0
- package/reference/datastar/index.md +256 -0
- package/reference/datastar/signals.md +230 -0
- package/reference/datastar/sse.md +295 -0
- package/reference/datastar.md +2 -2
- package/reference/docs.md +2 -2
- package/reference/endpoint/bulk-creation.md +96 -0
- package/reference/endpoint/endpoint.md +1 -0
- package/reference/endpoint/index.md +9 -89
- package/reference/endpoint/path-codec.md +12 -24
- package/reference/endpoint/route-pattern.md +4 -6
- package/reference/endpoint/segment-codec.md +19 -32
- package/reference/html.md +313 -9
- package/reference/htmx/index.md +4 -52
- package/reference/htmx/response-headers.md +240 -0
- package/reference/http-model/headers.md +735 -0
- package/reference/http-model/index.md +3 -1
- package/reference/http-model/model.md +107 -71
- package/reference/http-model/schema-codecs.md +522 -0
- package/reference/http-model/schema.md +6 -3
- package/reference/http-model/server-sent-event.md +341 -0
- package/reference/jwt.md +195 -0
- package/reference/maybe.md +128 -11
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +7 -2
- package/reference/openapi.md +3 -3
- package/reference/projection.md +654 -0
- package/reference/resource-management/index.md +1 -1
- package/reference/resource-management/resource.md +2 -98
- package/reference/resource-management/scope.md +1 -209
- package/reference/resource-management/wire.md +4 -50
- package/reference/ringbuffer/advanced.mdx +1 -1
- package/reference/ringbuffer/index.mdx +3 -3
- package/reference/ringbuffer/mpmc.mdx +38 -4
- package/reference/ringbuffer/mpsc.mdx +36 -4
- package/reference/ringbuffer/spmc.mdx +1 -1
- package/reference/ringbuffer/spsc.mdx +87 -15
- package/reference/schema/allows.md +0 -96
- package/reference/schema/binding.md +2 -2
- package/reference/schema/built-in-codecs/avro.md +2 -2
- package/reference/schema/built-in-codecs/bson.md +50 -20
- package/reference/schema/built-in-codecs/csv.md +2 -2
- package/reference/schema/built-in-codecs/index.md +3 -3
- package/reference/schema/built-in-codecs/json/index.md +2 -2
- package/reference/schema/built-in-codecs/json/json.md +1 -0
- package/reference/schema/built-in-codecs/messagepack.md +3 -3
- package/reference/schema/built-in-codecs/thrift.md +2 -2
- package/reference/schema/built-in-codecs/toon.md +3 -3
- package/reference/schema/built-in-codecs/yaml.md +2 -2
- package/reference/schema/codec.md +11 -11
- package/reference/schema/dynamic-optic.md +48 -3
- package/reference/schema/dynamic-schema.md +3 -3
- package/reference/schema/index.md +2 -0
- package/reference/schema/path-interpolator.md +2 -0
- package/reference/schema/reflect-transformer.md +140 -0
- package/reference/schema/schema-evolution/as.md +4 -4
- package/reference/schema/schema-evolution/into.md +2 -2
- package/reference/schema/schema-expr.md +2 -2
- package/reference/schema/schema-search.md +263 -0
- package/reference/schema/schema.md +10 -2
- package/reference/schema/type-class-derivation.md +1 -1
- package/reference/smithy.md +502 -3
- package/reference/sql/db-codec-deriver.md +3 -3
- package/reference/sql/db-codec.md +22 -22
- package/reference/sql/db-con.md +4 -4
- package/reference/sql/db-connection.md +1 -1
- package/reference/sql/db-param.md +1 -1
- package/reference/sql/db-result-reader.md +4 -2
- package/reference/sql/db-tx.md +46 -14
- package/reference/sql/ddl.md +1 -1
- package/reference/sql/frag.md +44 -10
- package/reference/sql/index.md +7 -7
- package/reference/sql/repo.md +15 -15
- package/reference/sql/sql-dialect.md +1 -1
- package/reference/sql/sql-logger.md +1 -1
- package/reference/sql/sql-name-mapper.md +3 -3
- package/reference/sql/table-metadata.md +3 -3
- package/reference/sql/table.md +10 -10
- package/reference/sql/transactor-zio.md +1 -1
- package/reference/sql/transactor.md +21 -11
- package/reference/sql-zio.md +2 -2
- package/reference/streams/core/index.md +32 -0
- package/reference/streams/{pipeline.md → core/pipeline.md} +210 -74
- package/reference/streams/{sink.md → core/sink.md} +331 -353
- package/reference/streams/{stream.md → core/stream.md} +919 -209
- package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
- package/reference/streams/execution-and-compatibility/index.md +35 -0
- package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
- package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
- package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
- package/reference/streams/index.md +140 -67
- package/reference/streams/primitives/index.md +30 -0
- package/reference/streams/primitives/reader.md +1992 -0
- package/reference/streams/{writer.md → primitives/writer.md} +254 -98
- package/reference/telemetry/common/any-value.md +90 -0
- package/reference/telemetry/common/attribute-key.md +87 -0
- package/reference/telemetry/common/attributes.md +118 -0
- package/reference/telemetry/common/index.md +39 -0
- package/reference/telemetry/common/instrumentation-scope.md +24 -0
- package/reference/telemetry/common/resource.md +34 -0
- package/reference/telemetry/index.md +311 -0
- package/reference/telemetry/logging/index.md +197 -0
- package/reference/telemetry/logging/log-enrichment.md +72 -0
- package/reference/telemetry/logging/log-formatter.md +100 -0
- package/reference/telemetry/logging/log-record-processor.md +56 -0
- package/reference/telemetry/logging/log-record.md +44 -0
- package/reference/telemetry/logging/log-writer.md +64 -0
- package/reference/telemetry/logging/logger-provider.md +142 -0
- package/reference/telemetry/logging/logger.md +83 -0
- package/reference/telemetry/logging/severity.md +62 -0
- package/reference/telemetry/metrics/index.md +150 -0
- package/reference/telemetry/metrics/instruments.md +183 -0
- package/reference/telemetry/metrics/labeled-instruments.md +74 -0
- package/reference/telemetry/metrics/meter-provider.md +76 -0
- package/reference/telemetry/metrics/meter.md +98 -0
- package/reference/telemetry/metrics/metric-data.md +57 -0
- package/reference/telemetry/otel/custom-exporter.md +216 -0
- package/reference/telemetry/otel/index.md +212 -0
- package/reference/telemetry/tracing/index.md +155 -0
- package/reference/telemetry/tracing/sampler.md +89 -0
- package/reference/telemetry/tracing/span-builder.md +57 -0
- package/reference/telemetry/tracing/span-context.md +39 -0
- package/reference/telemetry/tracing/span-data.md +32 -0
- package/reference/telemetry/tracing/span-kind.md +55 -0
- package/reference/telemetry/tracing/span-processor.md +53 -0
- package/reference/telemetry/tracing/span-status.md +47 -0
- package/reference/telemetry/tracing/span.md +117 -0
- package/reference/telemetry/tracing/tracer-provider.md +91 -0
- package/reference/telemetry/tracing/tracer.md +52 -0
- package/reference/typeid.md +0 -64
- package/sidebars.js +365 -185
- package/undocumented-report.md +528 -270
- package/reference/config.md +0 -158
- package/reference/streams/concurrent-operators.md +0 -106
- package/reference/streams/reader.md +0 -1284
- package/reference/streams/scala-2-compatibility.md +0 -55
- package/reference/streams/zero-boxing.md +0 -275
- package/reference/telemetry.md +0 -693
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: writer
|
|
3
3
|
title: "Writer"
|
|
4
|
+
sidebar_label: "Writer"
|
|
5
|
+
description: "The push-based sink for elements: the write-and-close protocol, the primitive write family, and the sixteen deferred *Async mirrors."
|
|
6
|
+
keywords:
|
|
7
|
+
- "Push-Based Writing"
|
|
8
|
+
- "Deferred Effects"
|
|
9
|
+
- "Writer Cancellation"
|
|
10
|
+
- "Specialized Writes"
|
|
11
|
+
- "Writer"
|
|
4
12
|
---
|
|
5
13
|
|
|
6
14
|
`Writer[-Elem]` is a **push-based sink for elements** that accepts values one at a time until closed or filled. It is the push-based counterpart to `Reader[+Elem]` (which pulls). Elements are written on demand by the producer, making it ideal for streaming, buffering, and integration with I/O subsystems. The fundamental operations are `write(elem): Boolean` — pushes an element and returns success or closure — and `close()` — signals the end of writing and releases resources.
|
|
@@ -31,7 +39,7 @@ Imagine you're building a data pipeline where a producer feeds items to a bounde
|
|
|
31
39
|
|
|
32
40
|
With Java's `OutputStream`, you call `write()` and either it succeeds (void return) or throws an exception. This leaves ambiguity: Was the exception transient (try again later) or permanent (the stream is done)? If the buffer fills, the thread blocks—but you don't know how long, or even that it will block beforehand. There's no way to check capacity upfront, so you're forced to either over-allocate buffers (wasting memory) or catch exceptions and guess the right strategy.
|
|
33
41
|
|
|
34
|
-
`Writer` makes the state explicit and non-throwing. You check readiness with `writeable()`, then push with `write()`, which returns a `Boolean` indicating success or closure. The protocol is
|
|
42
|
+
`Writer` makes the state explicit and non-throwing. You check readiness with `writeable()`, then push with `write()`, which returns a `Boolean` indicating success or closure. The protocol is explicit: when `write()` returns `false`, the sink is permanently closed and you should stop. It is not exception-free, though — a writer closed with `fail` may throw the stored error on a subsequent `write()`.
|
|
35
43
|
|
|
36
44
|
## Quick Showcase
|
|
37
45
|
|
|
@@ -55,7 +63,7 @@ val w = new Writer[Int] {
|
|
|
55
63
|
override def fail(error: Throwable) = close()
|
|
56
64
|
override def writeable() = !isClosed
|
|
57
65
|
}
|
|
58
|
-
// w: Writer[Int] = repl.MdocSession$MdocApp0$$anon$2@
|
|
66
|
+
// w: Writer[Int] = repl.MdocSession$MdocApp0$$anon$2@6cb38411
|
|
59
67
|
|
|
60
68
|
// Push elements, checking writeable() before each write
|
|
61
69
|
def pushAll(elements: List[Int]): Unit = {
|
|
@@ -83,7 +91,7 @@ The fundamental protocol is: call `write(element)` to push an element. It return
|
|
|
83
91
|
import zio.blocks.streams.io.Writer
|
|
84
92
|
|
|
85
93
|
val w = Writer.single[Int]
|
|
86
|
-
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@
|
|
94
|
+
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@2086fd7a
|
|
87
95
|
println(s"First write: ${w.write(42)}") // true (accepted)
|
|
88
96
|
// First write: true
|
|
89
97
|
println(s"Second write: ${w.write(99)}") // false (writer auto-closed after one element)
|
|
@@ -92,14 +100,79 @@ println(s"Third write: ${w.write(77)}") // false (still closed)
|
|
|
92
100
|
// Third write: false
|
|
93
101
|
```
|
|
94
102
|
|
|
103
|
+
## Asynchronous Writes
|
|
104
|
+
|
|
105
|
+
Every effectful member of `Writer` has a deferred mirror whose name ends in `Async` and whose result is an `Async`. There are sixteen of them, and together they are the entire asynchronous surface of the type; the structural combinators `concat` and `contramap` are deliberately outside it.
|
|
106
|
+
|
|
107
|
+
A mirror does one thing. It wraps a single synchronous call in an effect that has not happened yet: constructing `writer.writeAsync(42)` performs no write at all, and driving the returned effect performs `write(42)` exactly once and yields its `Boolean`. All sixteen are `final` and delegate to one private helper, which builds them on the library's internal cancellable-defer primitive `Async.deferCancelable` (`Writer.scala:57`).
|
|
108
|
+
|
|
109
|
+
The mirrors group exactly as their synchronous twins do on this page:
|
|
110
|
+
|
|
111
|
+
| Group | Synchronous member | Deferred mirror | Result |
|
|
112
|
+
|--------------------|--------------------------------|-------------------------------------|-----------------------|
|
|
113
|
+
| Lifecycle | `close()` | `closeAsync()` | `Async[Unit]` |
|
|
114
|
+
| Lifecycle | `fail(error)` | `failAsync(error)` | `Async[Unit]` |
|
|
115
|
+
| Single element | `write(a)` | `writeAsync(a)` | `Async[Boolean]` |
|
|
116
|
+
| Bulk | `writeAll(chunk)` | `writeAllAsync(chunk)` | `Async[Chunk[Elem1]]` |
|
|
117
|
+
| Specialized | `writeInt(value)` | `writeIntAsync(value)` | `Async[Boolean]` |
|
|
118
|
+
| Specialized | `writeLong(value)` | `writeLongAsync(value)` | `Async[Boolean]` |
|
|
119
|
+
| Specialized | `writeFloat(value)` | `writeFloatAsync(value)` | `Async[Boolean]` |
|
|
120
|
+
| Specialized | `writeDouble(value)` | `writeDoubleAsync(value)` | `Async[Boolean]` |
|
|
121
|
+
| Byte and character | `writeByte(b)` | `writeByteAsync(b)` | `Async[Boolean]` |
|
|
122
|
+
| Byte and character | `writeBytes(buf, offset, len)` | `writeBytesAsync(buf, offset, len)` | `Async[Int]` |
|
|
123
|
+
| Byte and character | `writeChar(value)` | `writeCharAsync(value)` | `Async[Boolean]` |
|
|
124
|
+
| Byte and character | `writeShort(value)` | `writeShortAsync(value)` | `Async[Boolean]` |
|
|
125
|
+
| Byte and character | `writeBoolean(value)` | `writeBooleanAsync(value)` | `Async[Boolean]` |
|
|
126
|
+
| State checks | `isClosed` | `isClosedAsync` | `Async[Boolean]` |
|
|
127
|
+
| State checks | `writeable()` | `writeableAsync()` | `Async[Boolean]` |
|
|
128
|
+
| State checks | `jvmType` | `jvmTypeAsync` | `Async[JvmType]` |
|
|
129
|
+
|
|
130
|
+
Each mirror carries the same parameters and the same implicit evidence as its twin, so the specialized mirrors still ask for the subtype witness their twin asks for:
|
|
131
|
+
|
|
132
|
+
```scala
|
|
133
|
+
abstract class Writer[-Elem] {
|
|
134
|
+
final def closeAsync(): Async[Unit]
|
|
135
|
+
final def writeAsync(a: Elem): Async[Boolean]
|
|
136
|
+
final def writeAllAsync[Elem1 <: Elem](chunk: Chunk[Elem1]): Async[Chunk[Elem1]]
|
|
137
|
+
final def writeIntAsync(value: Int)(implicit ev: Int <:< Elem): Async[Boolean]
|
|
138
|
+
final def writeBytesAsync(buf: Array[Byte], offset: Int, len: Int)(implicit ev: Byte <:< Elem): Async[Int]
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`jvmTypeAsync` is the mirror of `jvmType`, the writer's element representation (`JvmType.AnyRef` unless a subclass overrides it). It is the only mirror whose twin is not otherwise documented on this page.
|
|
143
|
+
|
|
144
|
+
### What `*Async` Does and Does Not Do
|
|
145
|
+
|
|
146
|
+
These are **cancellation-aware deferral adapters, not asynchronous I/O**. The library's own scaladoc says so in as many words (`Writer.scala:51`), and it is worth repeating because sixteen methods named `*Async` invite the opposite conclusion.
|
|
147
|
+
|
|
148
|
+
What a mirror does:
|
|
149
|
+
|
|
150
|
+
- **It defers one synchronous operation.** The wrapped call is first evaluated when the effect is driven, never when it is constructed, and it runs at most once however many times the effect is composed.
|
|
151
|
+
- **It closes the writer on cancellation.** Every mirror installs `close()` as its cancellation hook. If cancellation wins before the operation finishes, the writer is closed and the operation's result is discarded rather than published — the run then delivers nothing at all, so a cancelled handle must never be given to `block`.
|
|
152
|
+
|
|
153
|
+
What a mirror does not do:
|
|
154
|
+
|
|
155
|
+
- **It does not move the write to another thread.** Driving `writeAsync` calls `write` on whichever thread is driving.
|
|
156
|
+
- **It does not make a blocking write nonblocking.** When `write` blocks — a bounded buffer with no space, a socket with a full send window — driving `writeAsync` blocks in the same place for the same duration. `writeBytesAsync` on a `Writer.fromOutputStream` is a `java.io.OutputStream.write` behind an `Async`, and that call blocks.
|
|
157
|
+
|
|
158
|
+
:::warning[These methods are not nonblocking I/O]
|
|
159
|
+
A `Writer` whose `write` blocks still blocks when you drive its `*Async` mirror. The mirrors buy you deferral and a cancellation hook; they do not buy you a nonblocking writer. If you need writes that genuinely suspend rather than block, that is a different writer, not a different method on this one.
|
|
160
|
+
:::
|
|
161
|
+
|
|
162
|
+
Cancellation is cooperative and interrupts no thread, so the hook cannot abort a call already inside a blocking `write`. It calls `close()`, and that helps exactly when closing the writer is what releases the blocked call — which is true of a writer whose blocking wait is woken by closure, and false of one that ignores its own closed flag while parked. See [Running#cancel](../../async.md#runningcancel) for what a cancelled run does and does not stop.
|
|
163
|
+
|
|
164
|
+
### Why There Is No `concatAsync` or `contramapAsync`
|
|
165
|
+
|
|
166
|
+
The structural combinators `concat` and `contramap` have no mirrors, and that is deliberate rather than an omission. The synchronous `Writer` protocol requires `write` to return its `Boolean` immediately. A composition callback that produced an `Async` would have no honest way to report that result: `write` cannot return a pending value, and inventing one — blocking on it, or guessing `true` — would break the very protocol the page opens with. Modelling asynchronous composition needs a separate async-writer architecture, not another method here.
|
|
167
|
+
|
|
95
168
|
## Capacity and Buffering
|
|
96
169
|
|
|
97
170
|
The default `writeable()` method returns `!isClosed`—it only tells you if the writer is closed, not whether the buffer has space. Bounded implementations can override `writeable()` to reflect remaining capacity, but this is not guaranteed by the interface. The important distinction:
|
|
98
171
|
|
|
99
172
|
- **`writeable()` returns `false`**: the writer is closed (permanent state)
|
|
100
|
-
- **`writeable()` returns `true` but `write()` would block**: the buffer is full but not closed
|
|
173
|
+
- **`writeable()` returns `true` but `write()` would block**: the buffer is full but not closed. What happens next is implementation-defined: a writer backed by a bounded buffer may block the calling thread until space becomes available, while the buffer-backed writers in this library instead auto-close and return `false`.
|
|
101
174
|
|
|
102
|
-
|
|
175
|
+
The writers behind `NioWriters.fromByteBuffer` and its typed variants auto-close when the buffer fills, turning the full state into closure. A writer you implement yourself may instead block indefinitely waiting for space.
|
|
103
176
|
|
|
104
177
|
## Error Handling
|
|
105
178
|
|
|
@@ -127,7 +200,7 @@ class ErrorStoringWriter extends Writer[Int] {
|
|
|
127
200
|
}
|
|
128
201
|
|
|
129
202
|
val w = new ErrorStoringWriter()
|
|
130
|
-
// w: ErrorStoringWriter = repl.MdocSession$MdocApp9$ErrorStoringWriter@
|
|
203
|
+
// w: ErrorStoringWriter = repl.MdocSession$MdocApp9$ErrorStoringWriter@2cf0305a
|
|
131
204
|
w.fail(new Exception("Stream error"))
|
|
132
205
|
try {
|
|
133
206
|
w.write(42) // throws the stored error
|
|
@@ -159,7 +232,7 @@ Create a pre-closed writer that rejects all writes:
|
|
|
159
232
|
import zio.blocks.streams.io.Writer
|
|
160
233
|
|
|
161
234
|
val w = Writer.closed
|
|
162
|
-
// w: Writer[Any] = zio.blocks.streams.io.Writer$$anon$1@
|
|
235
|
+
// w: Writer[Any] = zio.blocks.streams.io.Writer$$anon$1@475df1d1
|
|
163
236
|
println(w.write(42)) // false (closed)
|
|
164
237
|
// false
|
|
165
238
|
println(w.isClosed) // true
|
|
@@ -182,7 +255,7 @@ Create a writer that accepts exactly one element, then auto-closes:
|
|
|
182
255
|
import zio.blocks.streams.io.Writer
|
|
183
256
|
|
|
184
257
|
val w = Writer.single[Int]
|
|
185
|
-
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@
|
|
258
|
+
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@1959620c
|
|
186
259
|
println(w.write(42)) // true
|
|
187
260
|
// true
|
|
188
261
|
println(w.write(99)) // false (already accepted one element)
|
|
@@ -218,10 +291,10 @@ val inner = new Writer[Int] {
|
|
|
218
291
|
def write(a: Int) = { collected += a; true }
|
|
219
292
|
def close() = ()
|
|
220
293
|
}
|
|
221
|
-
// inner: Writer[Int] = repl.MdocSession$MdocApp19$$anon$23@
|
|
294
|
+
// inner: Writer[Int] = repl.MdocSession$MdocApp19$$anon$23@31c126b0
|
|
222
295
|
|
|
223
296
|
val limited = Writer.limited(inner, 2)
|
|
224
|
-
// limited: Writer[Int] = zio.blocks.streams.io.Writer$LimitedWriter@
|
|
297
|
+
// limited: Writer[Int] = zio.blocks.streams.io.Writer$LimitedWriter@2c2c5703
|
|
225
298
|
println(limited.write(1)) // true
|
|
226
299
|
// true
|
|
227
300
|
println(limited.write(2)) // true (space available)
|
|
@@ -254,6 +327,8 @@ object Writer {
|
|
|
254
327
|
|
|
255
328
|
The fundamental operations on `Writer` cover pushing elements one at a time, bulk operations, specialized writes for primitives, and state checks:
|
|
256
329
|
|
|
330
|
+
Each of these operations also has a deferred mirror, listed in [Asynchronous Writes](#asynchronous-writes) above.
|
|
331
|
+
|
|
257
332
|
### Writing Elements
|
|
258
333
|
|
|
259
334
|
`Writer#write` — Pushes one element to the writer. Returns `true` on success, `false` if the writer is closed and cannot accept more elements. Throws if the writer was closed with an error via `Writer#fail`:
|
|
@@ -270,7 +345,7 @@ Write elements and observe the return value indicating success or closure:
|
|
|
270
345
|
import zio.blocks.streams.io.Writer
|
|
271
346
|
|
|
272
347
|
val w = Writer.single[Int]
|
|
273
|
-
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@
|
|
348
|
+
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@40fc0d5d
|
|
274
349
|
val result1 = w.write(42)
|
|
275
350
|
// result1: Boolean = true
|
|
276
351
|
val result2 = w.write(99) // false, already closed
|
|
@@ -296,7 +371,7 @@ import zio.blocks.streams.io.Writer
|
|
|
296
371
|
import zio.blocks.chunk.Chunk
|
|
297
372
|
|
|
298
373
|
val w = Writer.single[Int]
|
|
299
|
-
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@
|
|
374
|
+
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@3d1c8bf6
|
|
300
375
|
val chunk = Chunk(1, 2, 3)
|
|
301
376
|
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
302
377
|
val remaining = w.writeAll(chunk)
|
|
@@ -307,13 +382,13 @@ println(s"Remaining: $remaining") // Chunk(2, 3)
|
|
|
307
382
|
|
|
308
383
|
### Specialized Writes
|
|
309
384
|
|
|
310
|
-
For primitive types, specialized write methods
|
|
385
|
+
For primitive types, specialized write methods take a subtype witness so that a writer backed by that primitive can override them and write the value without going through the generic `write`. The default bodies delegate to `write(value.asInstanceOf[Elem])`, so a writer that does not override them gains nothing.
|
|
311
386
|
|
|
312
387
|
`writeInt` — Specialized `Int` write. Requires implicit evidence that `Int` is a subtype of `Elem`:
|
|
313
388
|
|
|
314
389
|
```scala
|
|
315
390
|
abstract class Writer[-Elem] {
|
|
316
|
-
def writeInt(value: Int)(
|
|
391
|
+
def writeInt(value: Int)(implicit ev: Int <:< Elem): Boolean
|
|
317
392
|
}
|
|
318
393
|
```
|
|
319
394
|
|
|
@@ -321,7 +396,7 @@ abstract class Writer[-Elem] {
|
|
|
321
396
|
|
|
322
397
|
```scala
|
|
323
398
|
abstract class Writer[-Elem] {
|
|
324
|
-
def writeLong(value: Long)(
|
|
399
|
+
def writeLong(value: Long)(implicit ev: Long <:< Elem): Boolean
|
|
325
400
|
}
|
|
326
401
|
```
|
|
327
402
|
|
|
@@ -329,7 +404,7 @@ abstract class Writer[-Elem] {
|
|
|
329
404
|
|
|
330
405
|
```scala
|
|
331
406
|
abstract class Writer[-Elem] {
|
|
332
|
-
def writeFloat(value: Float)(
|
|
407
|
+
def writeFloat(value: Float)(implicit ev: Float <:< Elem): Boolean
|
|
333
408
|
}
|
|
334
409
|
```
|
|
335
410
|
|
|
@@ -337,7 +412,7 @@ abstract class Writer[-Elem] {
|
|
|
337
412
|
|
|
338
413
|
```scala
|
|
339
414
|
abstract class Writer[-Elem] {
|
|
340
|
-
def writeDouble(value: Double)(
|
|
415
|
+
def writeDouble(value: Double)(implicit ev: Double <:< Elem): Boolean
|
|
341
416
|
}
|
|
342
417
|
```
|
|
343
418
|
|
|
@@ -347,7 +422,7 @@ abstract class Writer[-Elem] {
|
|
|
347
422
|
|
|
348
423
|
```scala
|
|
349
424
|
abstract class Writer[-Elem] {
|
|
350
|
-
def writeByte(b: Byte)(
|
|
425
|
+
def writeByte(b: Byte)(implicit ev: Byte <:< Elem): Boolean
|
|
351
426
|
}
|
|
352
427
|
```
|
|
353
428
|
|
|
@@ -355,7 +430,7 @@ abstract class Writer[-Elem] {
|
|
|
355
430
|
|
|
356
431
|
```scala
|
|
357
432
|
abstract class Writer[-Elem] {
|
|
358
|
-
def writeBytes(buf: Array[Byte], offset: Int, len: Int)(
|
|
433
|
+
def writeBytes(buf: Array[Byte], offset: Int, len: Int)(implicit ev: Byte <:< Elem): Int
|
|
359
434
|
}
|
|
360
435
|
```
|
|
361
436
|
|
|
@@ -363,7 +438,7 @@ abstract class Writer[-Elem] {
|
|
|
363
438
|
|
|
364
439
|
```scala
|
|
365
440
|
abstract class Writer[-Elem] {
|
|
366
|
-
def writeChar(value: Char)(
|
|
441
|
+
def writeChar(value: Char)(implicit ev: Char <:< Elem): Boolean
|
|
367
442
|
}
|
|
368
443
|
```
|
|
369
444
|
|
|
@@ -371,7 +446,7 @@ abstract class Writer[-Elem] {
|
|
|
371
446
|
|
|
372
447
|
```scala
|
|
373
448
|
abstract class Writer[-Elem] {
|
|
374
|
-
def writeShort(value: Short)(
|
|
449
|
+
def writeShort(value: Short)(implicit ev: Short <:< Elem): Boolean
|
|
375
450
|
}
|
|
376
451
|
```
|
|
377
452
|
|
|
@@ -379,7 +454,7 @@ abstract class Writer[-Elem] {
|
|
|
379
454
|
|
|
380
455
|
```scala
|
|
381
456
|
abstract class Writer[-Elem] {
|
|
382
|
-
def writeBoolean(value: Boolean)(
|
|
457
|
+
def writeBoolean(value: Boolean)(implicit ev: Boolean <:< Elem): Boolean
|
|
383
458
|
}
|
|
384
459
|
```
|
|
385
460
|
|
|
@@ -393,7 +468,7 @@ abstract class Writer[-Elem] {
|
|
|
393
468
|
}
|
|
394
469
|
```
|
|
395
470
|
|
|
396
|
-
`writeable` — Returns `true` if the next `write()` would accept a value without blocking (space is available and the writer is not closed). Default returns `!isClosed`.
|
|
471
|
+
`writeable` — Returns `true` if the next `write()` would accept a value without blocking (space is available and the writer is not closed). Default returns `!isClosed`. A writer backed by a bounded buffer can override it for accuracy; none of the writers in this library does. Note the spelling: it is `writeable()`, not `writable()`; `Reader`'s counterpart is `readable()`.
|
|
397
472
|
|
|
398
473
|
```scala
|
|
399
474
|
abstract class Writer[-Elem] {
|
|
@@ -407,7 +482,7 @@ Check writer capacity before writing:
|
|
|
407
482
|
import zio.blocks.streams.io.Writer
|
|
408
483
|
|
|
409
484
|
val w = Writer.single[Int]
|
|
410
|
-
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@
|
|
485
|
+
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@71a80be
|
|
411
486
|
println(w.writeable()) // true
|
|
412
487
|
// true
|
|
413
488
|
w.write(42)
|
|
@@ -454,17 +529,17 @@ val w1 = new Writer[Int] {
|
|
|
454
529
|
}
|
|
455
530
|
def close() = ()
|
|
456
531
|
}
|
|
457
|
-
// w1: Writer[Int] = repl.MdocSession$MdocApp32$$anon$43@
|
|
532
|
+
// w1: Writer[Int] = repl.MdocSession$MdocApp32$$anon$43@2b669ef3
|
|
458
533
|
|
|
459
534
|
val w2 = new Writer[Int] {
|
|
460
535
|
def isClosed = false
|
|
461
536
|
def write(a: Int) = { collected += a * 10; true }
|
|
462
537
|
def close() = ()
|
|
463
538
|
}
|
|
464
|
-
// w2: Writer[Int] = repl.MdocSession$MdocApp32$$anon$45@
|
|
539
|
+
// w2: Writer[Int] = repl.MdocSession$MdocApp32$$anon$45@30a331ff
|
|
465
540
|
|
|
466
541
|
val combined = w1 ++ w2
|
|
467
|
-
// combined: Writer[Int] = zio.blocks.streams.io.Writer$ConcatWith@
|
|
542
|
+
// combined: Writer[Int] = zio.blocks.streams.io.Writer$ConcatWith@21dd14f1
|
|
468
543
|
combined.write(5)
|
|
469
544
|
// res33: Boolean = true
|
|
470
545
|
combined.write(20) // first writer rejects, switches to second
|
|
@@ -493,10 +568,10 @@ val stringWriter = new Writer[String] {
|
|
|
493
568
|
def write(a: String) = { println(s"Writing: $a"); true }
|
|
494
569
|
def close() = ()
|
|
495
570
|
}
|
|
496
|
-
// stringWriter: Writer[String] = repl.MdocSession$MdocApp36$$anon$51@
|
|
571
|
+
// stringWriter: Writer[String] = repl.MdocSession$MdocApp36$$anon$51@31113f62
|
|
497
572
|
|
|
498
573
|
val intWriter = stringWriter.contramap[Int](_.toString)
|
|
499
|
-
// intWriter: Writer[Int] = zio.blocks.streams.io.Writer$Contramapped@
|
|
574
|
+
// intWriter: Writer[Int] = zio.blocks.streams.io.Writer$Contramapped@6a721b4f
|
|
500
575
|
intWriter.write(42) // Prints: Writing: 42
|
|
501
576
|
// Writing: 42
|
|
502
577
|
// res37: Boolean = true
|
|
@@ -532,7 +607,7 @@ Close a writer with an error:
|
|
|
532
607
|
import zio.blocks.streams.io.Writer
|
|
533
608
|
|
|
534
609
|
val w = Writer.single[Int]
|
|
535
|
-
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@
|
|
610
|
+
// w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@11dd645a
|
|
536
611
|
w.write(42)
|
|
537
612
|
// res39: Boolean = true
|
|
538
613
|
w.fail(new RuntimeException("Error"))
|
|
@@ -555,11 +630,11 @@ val numberWriter = new Writer[Number] {
|
|
|
555
630
|
def write(a: Number) = { println(s"Number: $a"); true }
|
|
556
631
|
def close() = ()
|
|
557
632
|
}
|
|
558
|
-
// numberWriter: Writer[Number] = repl.MdocSession$MdocApp42$$anon$59@
|
|
633
|
+
// numberWriter: Writer[Number] = repl.MdocSession$MdocApp42$$anon$59@4a564d3b
|
|
559
634
|
|
|
560
635
|
// numberWriter is also a Writer[IntNum] due to contravariance
|
|
561
636
|
val intNumWriter: Writer[IntNum] = numberWriter
|
|
562
|
-
// intNumWriter: Writer[IntNum] = repl.MdocSession$MdocApp42$$anon$59@
|
|
637
|
+
// intNumWriter: Writer[IntNum] = repl.MdocSession$MdocApp42$$anon$59@4a564d3b
|
|
563
638
|
intNumWriter.write(IntNum(42))
|
|
564
639
|
// Number: IntNum(42)
|
|
565
640
|
// res43: Boolean = true
|
|
@@ -577,7 +652,7 @@ For typical stream usage, you'll see Writer indirectly when writing to files, ne
|
|
|
577
652
|
|
|
578
653
|
Understanding `Writer`'s design decisions helps you use it correctly and avoid common pitfalls:
|
|
579
654
|
|
|
580
|
-
### Push
|
|
655
|
+
### Push Vs Pull
|
|
581
656
|
|
|
582
657
|
`Writer` is push-based (producer-driven), contrasting with `Reader` which is pull-based (consumer-driven):
|
|
583
658
|
|
|
@@ -586,7 +661,7 @@ Understanding `Writer`'s design decisions helps you use it correctly and avoid c
|
|
|
586
661
|
| **Direction** | Source → Consumer (pull) | Producer → Sink (push) |
|
|
587
662
|
| **Variance** | Covariant (`+Elem`) | Contravariant (`−Elem`) |
|
|
588
663
|
| **Blocking** | `read()` may block | `write()` may block |
|
|
589
|
-
| **Signal end** |
|
|
664
|
+
| **Signal end** | Caller-supplied sentinel, or a negative count from a bulk read | `close()` or `fail()` |
|
|
590
665
|
| **Dual** | Sink drains Reader | Producer feeds Writer |
|
|
591
666
|
|
|
592
667
|
### Thread Safety
|
|
@@ -617,22 +692,6 @@ cd zio-blocks
|
|
|
617
692
|
This example demonstrates the most common writer factories: `Writer.single`, `Writer.limited`, `Writer.closed`, and custom writers via subclassing:
|
|
618
693
|
|
|
619
694
|
```scala title="streams-examples/src/main/scala/writer/WriterBasicConstructionExample.scala"
|
|
620
|
-
/*
|
|
621
|
-
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
622
|
-
*
|
|
623
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
624
|
-
* you may not use this file except in compliance with the License.
|
|
625
|
-
* You may obtain a copy of the License at
|
|
626
|
-
*
|
|
627
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
628
|
-
*
|
|
629
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
630
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
631
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
632
|
-
* See the License for the specific language governing permissions and
|
|
633
|
-
* limitations under the License.
|
|
634
|
-
*/
|
|
635
|
-
|
|
636
695
|
package writer
|
|
637
696
|
|
|
638
697
|
import zio.blocks.streams.io.Writer
|
|
@@ -714,22 +773,6 @@ sbt "streams-examples/runMain writer.WriterBasicConstructionExample"
|
|
|
714
773
|
This example shows writer composition with `Writer#++` (concat), transformation with `Writer#contramap`, and bulk writes with `Writer#writeAll`:
|
|
715
774
|
|
|
716
775
|
```scala title="streams-examples/src/main/scala/writer/WriterCompositionExample.scala"
|
|
717
|
-
/*
|
|
718
|
-
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
719
|
-
*
|
|
720
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
721
|
-
* you may not use this file except in compliance with the License.
|
|
722
|
-
* You may obtain a copy of the License at
|
|
723
|
-
*
|
|
724
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
725
|
-
*
|
|
726
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
727
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
728
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
729
|
-
* See the License for the specific language governing permissions and
|
|
730
|
-
* limitations under the License.
|
|
731
|
-
*/
|
|
732
|
-
|
|
733
776
|
package writer
|
|
734
777
|
|
|
735
778
|
import zio.blocks.streams.io.Writer
|
|
@@ -851,22 +894,6 @@ sbt "streams-examples/runMain writer.WriterCompositionExample"
|
|
|
851
894
|
This example demonstrates I/O integration with `Writer.fromOutputStream` and `Writer.fromWriter` for streaming to files or character streams:
|
|
852
895
|
|
|
853
896
|
```scala title="streams-examples/src/main/scala/writer/WriterIOAdapterExample.scala"
|
|
854
|
-
/*
|
|
855
|
-
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
856
|
-
*
|
|
857
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
858
|
-
* you may not use this file except in compliance with the License.
|
|
859
|
-
* You may obtain a copy of the License at
|
|
860
|
-
*
|
|
861
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
862
|
-
*
|
|
863
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
864
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
865
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
866
|
-
* See the License for the specific language governing permissions and
|
|
867
|
-
* limitations under the License.
|
|
868
|
-
*/
|
|
869
|
-
|
|
870
897
|
package writer
|
|
871
898
|
|
|
872
899
|
import zio.blocks.streams.io.Writer
|
|
@@ -965,22 +992,6 @@ sbt "streams-examples/runMain writer.WriterIOAdapterExample"
|
|
|
965
992
|
This example shows how to implement a bounded Writer that wraps a fixed-capacity container and auto-closes when full. It demonstrates the protocol: `write()` returns `false` only on closure (not buffer fullness), and `writeable()` reflects closure state:
|
|
966
993
|
|
|
967
994
|
```scala title="streams-examples/src/main/scala/writer/WriterBoundedImplementationExample.scala"
|
|
968
|
-
/*
|
|
969
|
-
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
970
|
-
*
|
|
971
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
972
|
-
* you may not use this file except in compliance with the License.
|
|
973
|
-
* You may obtain a copy of the License at
|
|
974
|
-
*
|
|
975
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
976
|
-
*
|
|
977
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
978
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
979
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
980
|
-
* See the License for the specific language governing permissions and
|
|
981
|
-
* limitations under the License.
|
|
982
|
-
*/
|
|
983
|
-
|
|
984
995
|
package writer
|
|
985
996
|
|
|
986
997
|
import zio.blocks.streams.io.Writer
|
|
@@ -1043,3 +1054,148 @@ Run this example with:
|
|
|
1043
1054
|
```bash
|
|
1044
1055
|
sbt "streams-examples/runMain writer.WriterBoundedImplementationExample"
|
|
1045
1056
|
```
|
|
1057
|
+
|
|
1058
|
+
### Deferred and Cancellable Writes
|
|
1059
|
+
|
|
1060
|
+
This example makes the `*Async` caveat concrete. It shows that constructing a mirror writes nothing while driving it writes once, composes three mirrors into one effect, and then cancels a driven `writeAsync` whose write is parked — observing that cancellation closed the writer, that no element was recorded, and that the run delivers no value at all:
|
|
1061
|
+
|
|
1062
|
+
```scala title="streams-examples/src/main/scala/writer/WriterAsyncExample.scala"
|
|
1063
|
+
package writer
|
|
1064
|
+
|
|
1065
|
+
import zio.blocks.async.*
|
|
1066
|
+
import zio.blocks.chunk.Chunk
|
|
1067
|
+
import zio.blocks.streams.io.Writer
|
|
1068
|
+
|
|
1069
|
+
import java.util.concurrent.{ConcurrentLinkedQueue, CountDownLatch}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* The `*Async` mirrors on `Writer`, and the two properties that define them:
|
|
1073
|
+
* each mirror defers exactly one synchronous writer operation until the
|
|
1074
|
+
* returned effect is driven, and cancellation closes the writer and suppresses
|
|
1075
|
+
* the stale result.
|
|
1076
|
+
*
|
|
1077
|
+
* Neither property makes a write nonblocking, and neither moves it to another
|
|
1078
|
+
* thread. The last section only observes cancellation at all because the
|
|
1079
|
+
* writer's own `close()` is what releases the parked write.
|
|
1080
|
+
*
|
|
1081
|
+
* JVM only, because it ends in `.block` to turn an `Async` into a value for
|
|
1082
|
+
* `main`.
|
|
1083
|
+
*/
|
|
1084
|
+
object WriterAsyncExample {
|
|
1085
|
+
|
|
1086
|
+
/** Records what actually reached the writer, so deferral is observable. */
|
|
1087
|
+
final class RecordingWriter extends Writer[Int] {
|
|
1088
|
+
private val recorded = scala.collection.mutable.ArrayBuffer.empty[Int]
|
|
1089
|
+
private var closed = false
|
|
1090
|
+
def isClosed: Boolean = closed
|
|
1091
|
+
def write(a: Int): Boolean = if (closed) false else { recorded += a; true }
|
|
1092
|
+
def close(): Unit = closed = true
|
|
1093
|
+
def snapshot: List[Int] = recorded.toList
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* A writer whose `write` parks until someone closes it. `close()` is the
|
|
1098
|
+
* cancellation hook every `*Async` mirror installs, so cancelling a driven
|
|
1099
|
+
* `writeAsync` is what wakes this writer up again.
|
|
1100
|
+
*/
|
|
1101
|
+
final class GatedWriter extends Writer[Int] {
|
|
1102
|
+
private val gate = new CountDownLatch(1)
|
|
1103
|
+
private val recorded = new ConcurrentLinkedQueue[Int]
|
|
1104
|
+
@volatile private var closed = false
|
|
1105
|
+
|
|
1106
|
+
/** Counts down once the deferred thunk has entered `write`. */
|
|
1107
|
+
val entered = new CountDownLatch(1)
|
|
1108
|
+
|
|
1109
|
+
/** Counts down once `close()` has run. */
|
|
1110
|
+
val wasClosed = new CountDownLatch(1)
|
|
1111
|
+
|
|
1112
|
+
/** Counts down once the parked `write` has returned. */
|
|
1113
|
+
val finished = new CountDownLatch(1)
|
|
1114
|
+
|
|
1115
|
+
def isClosed: Boolean = closed
|
|
1116
|
+
|
|
1117
|
+
def write(a: Int): Boolean = {
|
|
1118
|
+
entered.countDown()
|
|
1119
|
+
gate.await()
|
|
1120
|
+
val accepted =
|
|
1121
|
+
if (closed) false
|
|
1122
|
+
else { recorded.add(a); true }
|
|
1123
|
+
finished.countDown()
|
|
1124
|
+
accepted
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
def close(): Unit = {
|
|
1128
|
+
closed = true
|
|
1129
|
+
gate.countDown()
|
|
1130
|
+
wasClosed.countDown()
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
def recordedCount: Int = recorded.size
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
def main(args: Array[String]): Unit = {
|
|
1137
|
+
deferral()
|
|
1138
|
+
sequence()
|
|
1139
|
+
cancellation()
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/** Constructing a mirror writes nothing; driving it writes exactly once. */
|
|
1143
|
+
private def deferral(): Unit = {
|
|
1144
|
+
val writer = new RecordingWriter
|
|
1145
|
+
val pending = writer.writeAsync(1)
|
|
1146
|
+
|
|
1147
|
+
println(s"deferral: after construction recorded=${writer.snapshot}")
|
|
1148
|
+
println(s"deferral: after driving once accepted=${pending.block}, recorded=${writer.snapshot}")
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/** The mirrors compose like any other `Async`, one operation per step. */
|
|
1152
|
+
private def sequence(): Unit = {
|
|
1153
|
+
val writer = new RecordingWriter
|
|
1154
|
+
|
|
1155
|
+
val program: Async[Chunk[Int]] =
|
|
1156
|
+
writer
|
|
1157
|
+
.writeAsync(10)
|
|
1158
|
+
.flatMap(_ => writer.writeAllAsync(Chunk(20, 30, 40)))
|
|
1159
|
+
.flatMap(undelivered => writer.closeAsync().map(_ => undelivered))
|
|
1160
|
+
|
|
1161
|
+
val undelivered = program.block
|
|
1162
|
+
println(s"sequence: recorded=${writer.snapshot}, undelivered=$undelivered, closed=${writer.isClosed}")
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Cancellation closes the writer and discards the result it was about to
|
|
1167
|
+
* produce.
|
|
1168
|
+
*/
|
|
1169
|
+
private def cancellation(): Unit = {
|
|
1170
|
+
val writer = new GatedWriter
|
|
1171
|
+
val running = writer.writeAsync(99).start
|
|
1172
|
+
|
|
1173
|
+
// Wait until the deferred thunk is parked inside `write`, so cancellation
|
|
1174
|
+
// races a genuinely in-flight operation rather than an unstarted one.
|
|
1175
|
+
writer.entered.await()
|
|
1176
|
+
running.cancel()
|
|
1177
|
+
|
|
1178
|
+
// Cancellation ran `close()`, which released the parked `write`.
|
|
1179
|
+
writer.wasClosed.await()
|
|
1180
|
+
writer.finished.await()
|
|
1181
|
+
|
|
1182
|
+
// The `false` that `write` then returned lost the race to publish, so this
|
|
1183
|
+
// run never delivers a value. Never call `.block` on a cancelled handle.
|
|
1184
|
+
println(s"cancellation: closed=${writer.isClosed}, recorded=${writer.recordedCount}")
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
Run this example with:
|
|
1190
|
+
|
|
1191
|
+
```bash
|
|
1192
|
+
sbt "streams-examples/runMain writer.WriterAsyncExample"
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
## See Also
|
|
1196
|
+
|
|
1197
|
+
- [Asynchronous Stream Execution](../execution-and-compatibility/async-execution.md#cancellation) — how cancellation reaches a stream's resources, and the asynchronous stream API the deferred mirrors sit beside
|
|
1198
|
+
- [Async Reference](../../async.md#runningcancel) — what `Running#cancel` stops, why a cancelled run never delivers, and why the cancel hook cannot interrupt a blocked thread
|
|
1199
|
+
- [Reader](./reader.md) — the pull-based dual of this type, and the reader kinds a sink drains
|
|
1200
|
+
- [Sink](../core/sink.md) — the consumer side of a stream, which drains a `Reader` rather than feeding a `Writer`
|
|
1201
|
+
- [Zero-Boxing Streams](../execution-and-compatibility/zero-boxing.md) — why the specialized write family exists and how a primitive lane is chosen
|