fiber_stream 0.3.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +179 -61
- data/examples/README.md +6 -0
- data/examples/ractor_producer_sources.rb +43 -0
- data/lib/fiber_stream/flow.rb +141 -15
- data/lib/fiber_stream/internal/ractor_transfer_policy.rb +17 -0
- data/lib/fiber_stream/pipeline.rb +5 -1
- data/lib/fiber_stream/pull/compact.rb +39 -0
- data/lib/fiber_stream/pull/filter_map.rb +41 -0
- data/lib/fiber_stream/pull/map_concat.rb +56 -0
- data/lib/fiber_stream/pull/parallel_unordered_map_boundary.rb +311 -0
- data/lib/fiber_stream/pull/ractor_map_boundary.rb +50 -51
- data/lib/fiber_stream/pull/ractor_merge_ports_source.rb +18 -3
- data/lib/fiber_stream/pull/ractor_port_source.rb +39 -6
- data/lib/fiber_stream/pull/ractor_producer_source.rb +349 -0
- data/lib/fiber_stream/pull/reject.rb +40 -0
- data/lib/fiber_stream/pull/scan.rb +38 -0
- data/lib/fiber_stream/pull/tap.rb +38 -0
- data/lib/fiber_stream/pull/throttle.rb +43 -0
- data/lib/fiber_stream/pull.rb +84 -5
- data/lib/fiber_stream/ractor_producer.rb +167 -0
- data/lib/fiber_stream/rate_limiter.rb +163 -0
- data/lib/fiber_stream/running_pipeline.rb +4 -0
- data/lib/fiber_stream/sink.rb +25 -19
- data/lib/fiber_stream/source.rb +125 -22
- data/lib/fiber_stream/version.rb +1 -1
- data/lib/fiber_stream.rb +3 -0
- data/sig/fiber_stream.rbs +43 -1
- metadata +16 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 504a7400182e09bb66a5a07981bbb2cfab350397d09c23ea036e856f29afe1d2
|
|
4
|
+
data.tar.gz: 67c7ddead203d42bf869dcba7d840e7d09514d888b90b84b77d97a8cedc19e1e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9c8d80faca53d9c6e059326bc9e6ffe7497aa8a6cbcad3b42eefe0ef3a3d8d29f08a3afbec8fc2b02b2064a745bb00ea644e816f913aa624a2ad11f1b087b99
|
|
7
|
+
data.tar.gz: 9e6f87b74cbfe2ccaaa9c32990f9786af3ee3ee3d7f2c3dd605a88efa8f3e436e4a1051d65c1eb1664859af212a2fecc82c66dc585cf9333dd799917c2a9035e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 - 2026-06-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `Flow.tap { |element| ... }` for lazy pass-through observation without
|
|
8
|
+
changing emitted elements.
|
|
9
|
+
- `Flow.filter_map { |element| ... }` and `Source#filter_map` for combined
|
|
10
|
+
transformation and falsey-value dropping.
|
|
11
|
+
- `Flow.reject { |element| ... }` and `Source#reject` for complement
|
|
12
|
+
predicate filtering.
|
|
13
|
+
- `Flow.compact` and `Source#compact` for nil-only filtering while preserving
|
|
14
|
+
`false`.
|
|
15
|
+
- `Flow.map_concat { |element| enumerable }` and `Source#map_concat` for
|
|
16
|
+
one-to-many element expansion.
|
|
17
|
+
- `Flow.throttle(...)`, `Source#throttle(...)`, and `RateLimiter` for
|
|
18
|
+
pull-driven rate limiting with optional shared quota state.
|
|
19
|
+
- `Sink.count` for counting stream elements without accumulating them.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Expanded README and website reference coverage for the flow operators and
|
|
24
|
+
rate limiter added in 0.5.0.
|
|
25
|
+
- Promoted completed flow product specs and design docs from draft to accepted
|
|
26
|
+
status.
|
|
27
|
+
|
|
28
|
+
## 0.4.0 - 2026-06-09
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- `Flow.parallel_unordered_map(concurrency:)` and
|
|
33
|
+
`Source#parallel_unordered_map(concurrency:)` for scheduler-backed mapping
|
|
34
|
+
that emits results in completion order instead of preserving input order.
|
|
35
|
+
- `Source.ractor_producer` for FiberStream-owned single producer Ractors with
|
|
36
|
+
one-outstanding-ack backpressure and cooperative cleanup.
|
|
37
|
+
- `Source.ractor_merge_producers` for ready-order fan-in from multiple
|
|
38
|
+
FiberStream-owned producer Ractors without requiring a `Fiber.scheduler`.
|
|
39
|
+
- `Flow.scan(initial)` and `Source#scan(initial)` for lazy running
|
|
40
|
+
accumulators using `Sink.fold`-style reducer semantics.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- Updated README and website reference coverage for owned Ractor producers,
|
|
45
|
+
unordered parallel mapping, and scan.
|
|
46
|
+
- Prefer high-level owned Ractor producer examples in user-facing
|
|
47
|
+
documentation while keeping low-level port APIs documented for externally
|
|
48
|
+
owned producers.
|
|
49
|
+
- Updated the project Ruby pin to 4.0.5.
|
|
50
|
+
|
|
3
51
|
## 0.3.0 - 2026-06-06
|
|
4
52
|
|
|
5
53
|
### Added
|
data/README.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# FiberStream
|
|
2
2
|
|
|
3
|
-
FiberStream is
|
|
4
|
-
|
|
3
|
+
FiberStream is a Ruby library for linear stream processing with pull-based
|
|
4
|
+
backpressure.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
It builds lazy `Source` definitions, transforms values with `Flow` stages, and
|
|
7
|
+
materializes results with `Sink` objects.
|
|
8
|
+
|
|
9
|
+
[](https://badge.fury.io/rb/fiber_stream)
|
|
8
10
|
|
|
9
11
|
## Quick Start
|
|
12
|
+
Please see the project [documentation](https://dakatsuka.github.io/fiber_stream/) for more details.
|
|
10
13
|
|
|
11
14
|
```ruby
|
|
12
15
|
require "fiber_stream"
|
|
@@ -27,12 +30,15 @@ FiberStream currently supports linear pipelines only.
|
|
|
27
30
|
|
|
28
31
|
Implemented capabilities:
|
|
29
32
|
|
|
30
|
-
- in-memory, IO,
|
|
33
|
+
- in-memory, IO, FiberStream-owned Ractor producer, backpressure-aware Ractor
|
|
34
|
+
port, and Ractor port merge sources
|
|
31
35
|
- lazy source concatenation, zipping, and scheduler-backed merging
|
|
32
|
-
- mapping, filtering,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
- mapping, filtering, transform-and-filter, nil compaction, side-effect
|
|
37
|
+
observation, one-to-many expansion, limiting, predicate-based limiting and
|
|
38
|
+
dropping, fixed-prefix dropping, fixed-size grouping, line splitting,
|
|
39
|
+
buffering, async boundaries, throttling, ordered and unordered parallel
|
|
40
|
+
mapping, and ordered Ractor-backed mapping
|
|
41
|
+
- array, first-element, count, fold, foreach, and IO sinks
|
|
36
42
|
- reusable flow composition and runnable pipelines
|
|
37
43
|
- foreground and scheduler-backed background pipeline execution
|
|
38
44
|
- public RBS signatures
|
|
@@ -96,64 +102,51 @@ chunks =
|
|
|
96
102
|
end.wait
|
|
97
103
|
```
|
|
98
104
|
|
|
99
|
-
Ractor
|
|
100
|
-
The producer
|
|
101
|
-
|
|
105
|
+
Owned Ractor producer sources run producer blocks in FiberStream-managed
|
|
106
|
+
Ractors. The producer block receives a `RactorProducer` context and emits one
|
|
107
|
+
value per downstream demand:
|
|
102
108
|
|
|
103
109
|
```ruby
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
producer
|
|
108
|
-
Ractor.new(data_port, setup_port) do |outbox, setup|
|
|
109
|
-
ack_port = Ractor::Port.new
|
|
110
|
-
setup.send(ack_port)
|
|
111
|
-
|
|
112
|
-
values = [1, 2, 3].to_enum
|
|
113
|
-
|
|
114
|
-
loop do
|
|
115
|
-
case ack_port.receive
|
|
116
|
-
in FiberStream::RactorPort::Ack
|
|
117
|
-
begin
|
|
118
|
-
outbox.send(FiberStream::RactorPort::Element.new(values.next))
|
|
119
|
-
rescue StopIteration
|
|
120
|
-
outbox.send(FiberStream::RactorPort::Complete.new)
|
|
121
|
-
break
|
|
122
|
-
end
|
|
123
|
-
in FiberStream::RactorPort::Cancel
|
|
124
|
-
break
|
|
125
|
-
end
|
|
110
|
+
PRODUCE_VALUES =
|
|
111
|
+
Ractor.shareable_proc do |producer, values|
|
|
112
|
+
values.each do |value|
|
|
113
|
+
break unless producer.emit(value)
|
|
126
114
|
end
|
|
127
115
|
end
|
|
128
116
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
FiberStream::Source.ractor_port(data_port, ack_port: ack_port)
|
|
117
|
+
FiberStream::Source.ractor_producer([1, 2, 3], &PRODUCE_VALUES)
|
|
132
118
|
.run_with(FiberStream::Sink.to_a)
|
|
133
119
|
# => [1, 2, 3]
|
|
134
|
-
|
|
135
|
-
producer.value
|
|
136
120
|
```
|
|
137
121
|
|
|
138
|
-
|
|
139
|
-
`
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Multiple producer Ractors can be merged directly without a scheduler-backed
|
|
143
|
-
`Source#merge`. Each producer still receives at most one outstanding ack:
|
|
122
|
+
Multiple owned producer Ractors can be merged directly without a
|
|
123
|
+
scheduler-backed `Source#merge`. Each producer still receives at most one
|
|
124
|
+
outstanding ack:
|
|
144
125
|
|
|
145
126
|
```ruby
|
|
127
|
+
PRODUCE_TAGGED_VALUES =
|
|
128
|
+
Ractor.shareable_proc do |producer, tag, values|
|
|
129
|
+
values.each do |value|
|
|
130
|
+
break unless producer.emit([tag, value])
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
146
134
|
source =
|
|
147
|
-
FiberStream::Source.
|
|
148
|
-
[
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
]
|
|
152
|
-
)
|
|
135
|
+
FiberStream::Source.ractor_merge_producers do |group|
|
|
136
|
+
group.producer(:a, [1, 2], &PRODUCE_TAGGED_VALUES)
|
|
137
|
+
group.producer(:b, [3, 4], &PRODUCE_TAGGED_VALUES)
|
|
138
|
+
end
|
|
153
139
|
|
|
154
|
-
|
|
140
|
+
source.run_with(FiberStream::Sink.to_a)
|
|
141
|
+
# Example result: [[:a, 1], [:b, 3], [:a, 2], [:b, 4]]
|
|
155
142
|
```
|
|
156
143
|
|
|
144
|
+
Use the lower-level `Source.ractor_port` and `Source.ractor_merge_ports` APIs
|
|
145
|
+
when producer Ractors are owned outside FiberStream or need custom lifecycle
|
|
146
|
+
handling. `RactorPort::Failure` cause metadata is producer-provided and is
|
|
147
|
+
surfaced on `RactorPortSourceError`; redact sensitive details before sending
|
|
148
|
+
failures across trust boundaries.
|
|
149
|
+
|
|
157
150
|
Streaming HTTP response bodies that implement `#each`, such as
|
|
158
151
|
`async-http` response bodies, can be used with `Source.each` without buffering
|
|
159
152
|
the full body first. Use the HTTP client's block form or an explicit `ensure`
|
|
@@ -216,6 +209,62 @@ FiberStream::Source.each([" a ", "", " b "])
|
|
|
216
209
|
# => ["a", "b"]
|
|
217
210
|
```
|
|
218
211
|
|
|
212
|
+
Use `reject` when the predicate names values to drop. Truthy predicate results
|
|
213
|
+
drop the original element; `false` and `nil` results pass it through unchanged:
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
result =
|
|
217
|
+
FiberStream::Source.each([1, 2, 3, 4])
|
|
218
|
+
.reject(&:even?)
|
|
219
|
+
.run_with(FiberStream::Sink.to_a)
|
|
220
|
+
|
|
221
|
+
result # => [1, 3]
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Use `filter_map` when filtering and transformation are one decision. Truthy
|
|
225
|
+
block results are emitted as transformed values; `false` and `nil` are
|
|
226
|
+
dropped:
|
|
227
|
+
|
|
228
|
+
```ruby
|
|
229
|
+
ids =
|
|
230
|
+
FiberStream::Source.each([{ id: 1 }, {}, { id: 3 }])
|
|
231
|
+
.filter_map { |record| record[:id] }
|
|
232
|
+
.run_with(FiberStream::Sink.to_a)
|
|
233
|
+
|
|
234
|
+
ids # => [1, 3]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Use `compact` to drop only `nil` while keeping `false`, and `map_concat` to
|
|
238
|
+
expand one upstream element into zero or more downstream elements:
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
tokens =
|
|
242
|
+
FiberStream::Source.each(["alpha beta", nil, "gamma"])
|
|
243
|
+
.compact
|
|
244
|
+
.map_concat { |line| line.split }
|
|
245
|
+
.run_with(FiberStream::Sink.to_a)
|
|
246
|
+
|
|
247
|
+
tokens # => ["alpha", "beta", "gamma"]
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Use `Flow.tap` for observation inside a reusable flow without changing the
|
|
251
|
+
element:
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
seen = []
|
|
255
|
+
|
|
256
|
+
observed =
|
|
257
|
+
FiberStream::Flow.tap { |value| seen << value }
|
|
258
|
+
.via(FiberStream::Flow.map { |value| value * 10 })
|
|
259
|
+
|
|
260
|
+
FiberStream::Source.each([1, 2])
|
|
261
|
+
.via(observed)
|
|
262
|
+
.run_with(FiberStream::Sink.to_a)
|
|
263
|
+
# => [10, 20]
|
|
264
|
+
|
|
265
|
+
seen # => [1, 2]
|
|
266
|
+
```
|
|
267
|
+
|
|
219
268
|
Use `parallel_map` for ordered scheduler-backed mapping when each element
|
|
220
269
|
waits on non-blocking IO. It preserves input order while allowing up to
|
|
221
270
|
`concurrency` mapping operations to be in flight:
|
|
@@ -240,6 +289,26 @@ profiles =
|
|
|
240
289
|
profiles.map { |profile| profile.fetch(:id) } # => [1, 2, 3, 4]
|
|
241
290
|
```
|
|
242
291
|
|
|
292
|
+
Use `parallel_unordered_map` when every result can be handled independently
|
|
293
|
+
and lower head-of-line blocking matters more than input order. It still limits
|
|
294
|
+
in-flight mapping work to `concurrency`, but emits values as mapping jobs
|
|
295
|
+
finish:
|
|
296
|
+
|
|
297
|
+
```ruby
|
|
298
|
+
require "async"
|
|
299
|
+
require "fiber_stream"
|
|
300
|
+
|
|
301
|
+
responses =
|
|
302
|
+
Sync do
|
|
303
|
+
FiberStream::Source.each(["/a", "/slow", "/b"])
|
|
304
|
+
.parallel_unordered_map(concurrency: 3) { |path| fetch_path(path) }
|
|
305
|
+
.run_with(FiberStream::Sink.to_a)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Results are in completion order, not necessarily input order.
|
|
309
|
+
responses
|
|
310
|
+
```
|
|
311
|
+
|
|
243
312
|
Use `ractor_map` for ordered CPU-bound mapping in Ractor workers. The mapper
|
|
244
313
|
must be shareable, usually by creating it with `Ractor.shareable_proc`.
|
|
245
314
|
|
|
@@ -284,6 +353,14 @@ FiberStream::Source.each([1, 2, 3])
|
|
|
284
353
|
# => 6
|
|
285
354
|
```
|
|
286
355
|
|
|
356
|
+
Use `Sink.count` when only the number of elements matters:
|
|
357
|
+
|
|
358
|
+
```ruby
|
|
359
|
+
FiberStream::Source.each([1, 2, 3])
|
|
360
|
+
.run_with(FiberStream::Sink.count)
|
|
361
|
+
# => 3
|
|
362
|
+
```
|
|
363
|
+
|
|
287
364
|
Use `Sink.foreach` when the terminal operation is a side effect and the stream
|
|
288
365
|
values should not be accumulated:
|
|
289
366
|
|
|
@@ -384,6 +461,17 @@ batches =
|
|
|
384
461
|
batches # => [[1, 2], [3, 4], [5]]
|
|
385
462
|
```
|
|
386
463
|
|
|
464
|
+
`Flow.scan` emits the updated accumulator for each upstream element:
|
|
465
|
+
|
|
466
|
+
```ruby
|
|
467
|
+
running_totals =
|
|
468
|
+
FiberStream::Source.each([1, 2, 3, 4])
|
|
469
|
+
.scan(0) { |sum, number| sum + number }
|
|
470
|
+
.run_with(FiberStream::Sink.to_a)
|
|
471
|
+
|
|
472
|
+
running_totals # => [1, 3, 6, 10]
|
|
473
|
+
```
|
|
474
|
+
|
|
387
475
|
`Flow.take_while` emits the leading prefix while a predicate is truthy, then
|
|
388
476
|
closes upstream at the first false or nil result:
|
|
389
477
|
|
|
@@ -450,14 +538,18 @@ merged =
|
|
|
450
538
|
|
|
451
539
|
`merge` does not make scheduler-unaware blocking source work non-blocking and
|
|
452
540
|
does not provide CPU parallelism. Use producer ractors with
|
|
453
|
-
`Source.
|
|
454
|
-
true isolation.
|
|
455
|
-
|
|
456
|
-
`Flow.buffer(count)` allows bounded prefetch. `Flow.
|
|
457
|
-
|
|
458
|
-
`
|
|
459
|
-
|
|
460
|
-
scheduler and
|
|
541
|
+
`Source.ractor_producer` or `Source.ractor_merge_producers` when producer work
|
|
542
|
+
needs true isolation.
|
|
543
|
+
|
|
544
|
+
`Flow.buffer(count)` allows bounded prefetch. `Flow.throttle(rate:, per:)`
|
|
545
|
+
paces elements before downstream side effects. `Flow.async`, `Flow.buffer`,
|
|
546
|
+
`Flow.parallel_map`, `Flow.parallel_unordered_map`, `Source.io`,
|
|
547
|
+
`Source#merge`, `Sink.io`, and `Pipeline#run_async` require an installed
|
|
548
|
+
`Fiber.scheduler` and a non-blocking current fiber when demanded or started.
|
|
549
|
+
`Flow.throttle` requires that scheduler context only when it needs to wait.
|
|
550
|
+
Pass `throttle(limiter:)` with a `FiberStream::RateLimiter` when multiple
|
|
551
|
+
pipelines or repeated runs should share quota state. FiberStream does not
|
|
552
|
+
install a scheduler and does not depend on Async at runtime.
|
|
461
553
|
|
|
462
554
|
## API Surface
|
|
463
555
|
|
|
@@ -465,6 +557,8 @@ Sources:
|
|
|
465
557
|
|
|
466
558
|
- `FiberStream::Source.each(enumerable)`
|
|
467
559
|
- `FiberStream::Source.io(io, chunk_size: 16 * 1024, close: false)`
|
|
560
|
+
- `FiberStream::Source.ractor_producer(*args, transfer: :copy, ack_transfer: :copy) { |producer, *args| ... }`
|
|
561
|
+
- `FiberStream::Source.ractor_merge_producers(transfer: :copy, ack_transfer: :copy) { |group| ... }`
|
|
468
562
|
- `FiberStream::Source.ractor_port(port, ack_port:, ack_transfer: :copy, cancel: true)`
|
|
469
563
|
- `FiberStream::Source.ractor_merge_ports(ports, ack_transfer: :copy, cancel: true)`
|
|
470
564
|
|
|
@@ -475,16 +569,24 @@ Source convenience methods:
|
|
|
475
569
|
- `Source#zip(source)`
|
|
476
570
|
- `Source#merge(source)`
|
|
477
571
|
- `Source#map { |element| ... }`
|
|
572
|
+
- `Source#filter_map { |element| ... }`
|
|
573
|
+
- `Source#compact`
|
|
574
|
+
- `Source#map_concat { |element| enumerable }`
|
|
478
575
|
- `Source#parallel_map(concurrency:) { |element| ... }`
|
|
576
|
+
- `Source#parallel_unordered_map(concurrency:) { |element| ... }`
|
|
479
577
|
- `Source#ractor_map(workers:, input_transfer: :copy, output_transfer: :copy) { |element| ... }`
|
|
480
578
|
- `Source#select { |element| ... }`
|
|
579
|
+
- `Source#reject { |element| ... }`
|
|
481
580
|
- `Source#take(count)`
|
|
482
581
|
- `Source#drop(count)`
|
|
483
582
|
- `Source#grouped(count)`
|
|
583
|
+
- `Source#scan(initial) { |accumulator, element| ... }`
|
|
484
584
|
- `Source#take_while { |element| ... }`
|
|
485
585
|
- `Source#drop_while { |element| ... }`
|
|
486
586
|
- `Source#async`
|
|
487
587
|
- `Source#buffer(count)`
|
|
588
|
+
- `Source#throttle(rate:, per: 1, burst: nil)`
|
|
589
|
+
- `Source#throttle(limiter:)`
|
|
488
590
|
- `Source#lines(chomp: true, max_length: nil)`
|
|
489
591
|
- `Source#split(separator, keep_separator: false, max_length: nil)`
|
|
490
592
|
- `Source#to(sink)`
|
|
@@ -493,20 +595,31 @@ Source convenience methods:
|
|
|
493
595
|
Flows:
|
|
494
596
|
|
|
495
597
|
- `FiberStream::Flow.map { |element| ... }`
|
|
598
|
+
- `FiberStream::Flow.filter_map { |element| ... }`
|
|
599
|
+
- `FiberStream::Flow.compact`
|
|
600
|
+
- `FiberStream::Flow.map_concat { |element| enumerable }`
|
|
601
|
+
- `FiberStream::Flow.tap { |element| ... }`
|
|
496
602
|
- `FiberStream::Flow.parallel_map(concurrency:) { |element| ... }`
|
|
603
|
+
- `FiberStream::Flow.parallel_unordered_map(concurrency:) { |element| ... }`
|
|
497
604
|
- `FiberStream::Flow.ractor_map(workers:, input_transfer: :copy, output_transfer: :copy) { |element| ... }`
|
|
498
605
|
- `FiberStream::Flow.select { |element| ... }`
|
|
606
|
+
- `FiberStream::Flow.reject { |element| ... }`
|
|
499
607
|
- `FiberStream::Flow.take(count)`
|
|
500
608
|
- `FiberStream::Flow.drop(count)`
|
|
501
609
|
- `FiberStream::Flow.grouped(count)`
|
|
610
|
+
- `FiberStream::Flow.scan(initial) { |accumulator, element| ... }`
|
|
502
611
|
- `FiberStream::Flow.take_while { |element| ... }`
|
|
503
612
|
- `FiberStream::Flow.drop_while { |element| ... }`
|
|
504
613
|
- `FiberStream::Flow.async`
|
|
505
614
|
- `FiberStream::Flow.buffer(count)`
|
|
615
|
+
- `FiberStream::Flow.throttle(rate:, per: 1, burst: nil)`
|
|
616
|
+
- `FiberStream::Flow.throttle(limiter:)`
|
|
506
617
|
- `FiberStream::Flow.lines(chomp: true, max_length: nil)`
|
|
507
618
|
- `FiberStream::Flow.split(separator, keep_separator: false, max_length: nil)`
|
|
508
619
|
- `Flow#via(flow)`
|
|
509
620
|
- `Flow#to(sink)`
|
|
621
|
+
- `FiberStream::RateLimiter.new(rate:, per: 1, burst: nil)`
|
|
622
|
+
- `FiberStream::RateLimiter#acquire(permits: 1)`
|
|
510
623
|
|
|
511
624
|
`lines` and `split` default to `max_length: nil`, which allows one
|
|
512
625
|
unterminated line or frame to buffer without bound. Set a positive
|
|
@@ -516,6 +629,7 @@ Sinks:
|
|
|
516
629
|
|
|
517
630
|
- `FiberStream::Sink.to_a`
|
|
518
631
|
- `FiberStream::Sink.first`
|
|
632
|
+
- `FiberStream::Sink.count`
|
|
519
633
|
- `FiberStream::Sink.fold(initial) { |accumulator, element| ... }`
|
|
520
634
|
- `FiberStream::Sink.foreach { |element| ... }`
|
|
521
635
|
- `FiberStream::Sink.io(io, close: false, flush: false)`
|
|
@@ -541,6 +655,7 @@ bundle exec ruby examples/file_copy.rb
|
|
|
541
655
|
bundle exec ruby examples/backpressure_buffer.rb
|
|
542
656
|
bundle exec ruby examples/background_execution.rb
|
|
543
657
|
bundle exec ruby examples/ractor_map_hashing.rb
|
|
658
|
+
bundle exec ruby examples/ractor_producer_sources.rb
|
|
544
659
|
bundle exec ruby examples/ractor_port_source.rb
|
|
545
660
|
bundle exec ruby examples/ractor_merge_ports_and_map.rb
|
|
546
661
|
bundle exec ruby examples/async_http_requests.rb
|
|
@@ -553,6 +668,9 @@ events so the difference between direct demand and bounded prefetch is visible.
|
|
|
553
668
|
`examples/ractor_map_hashing.rb` demonstrates ordered Ractor-backed hashing
|
|
554
669
|
with a shareable mapper proc and `input_transfer: :move`.
|
|
555
670
|
|
|
671
|
+
`examples/ractor_producer_sources.rb` demonstrates high-level owned producer
|
|
672
|
+
Ractors with `Source.ractor_producer` and `Source.ractor_merge_producers`.
|
|
673
|
+
|
|
556
674
|
`examples/ractor_port_source.rb` demonstrates a producer Ractor that waits for
|
|
557
675
|
`RactorPort::Ack` before sending each `RactorPort::Element`.
|
|
558
676
|
|
|
@@ -578,7 +696,7 @@ bundle exec ruby benchmarks/heavy_cpu_map.rb
|
|
|
578
696
|
|
|
579
697
|
## Development
|
|
580
698
|
|
|
581
|
-
This project targets Ruby 4.x. The repository currently pins Ruby 4.0.
|
|
699
|
+
This project targets Ruby 4.x. The repository currently pins Ruby 4.0.5 in
|
|
582
700
|
`mise.toml`.
|
|
583
701
|
|
|
584
702
|
Install dependencies:
|
data/examples/README.md
CHANGED
|
@@ -11,6 +11,7 @@ bundle exec ruby examples/backpressure_buffer.rb
|
|
|
11
11
|
bundle exec ruby examples/background_execution.rb
|
|
12
12
|
bundle exec ruby examples/ractor_map_hashing.rb
|
|
13
13
|
bundle exec ruby examples/ractor_port_source.rb
|
|
14
|
+
bundle exec ruby examples/ractor_producer_sources.rb
|
|
14
15
|
bundle exec ruby examples/ractor_merge_ports_and_map.rb
|
|
15
16
|
bundle exec ruby examples/async_http_requests.rb
|
|
16
17
|
bundle exec ruby examples/async_http_streaming_body.rb
|
|
@@ -48,6 +49,11 @@ pipeline runs.
|
|
|
48
49
|
`RactorPort::Ack`, and sends one typed `RactorPort::Element` per downstream
|
|
49
50
|
demand.
|
|
50
51
|
|
|
52
|
+
`ractor_producer_sources.rb` demonstrates the high-level owned-producer APIs:
|
|
53
|
+
`Source.ractor_producer` for one producer and `Source.ractor_merge_producers`
|
|
54
|
+
for ready-order fan-in from multiple producers. FiberStream creates the ports,
|
|
55
|
+
producer Ractors, and cooperative cleanup path.
|
|
56
|
+
|
|
51
57
|
`ractor_merge_ports_and_map.rb` runs CPU-bound work in multiple producer
|
|
52
58
|
Ractors, merges their port outputs with `Source.ractor_merge_ports`, then runs
|
|
53
59
|
another CPU-bound verification stage with `ractor_map`.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
4
|
+
|
|
5
|
+
require "fiber_stream"
|
|
6
|
+
|
|
7
|
+
EMIT_NUMBERS =
|
|
8
|
+
Ractor.shareable_proc do |producer, range|
|
|
9
|
+
range.each do |number|
|
|
10
|
+
break unless producer.emit(number)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
EMIT_TAGGED_NUMBERS =
|
|
15
|
+
Ractor.shareable_proc do |producer, tag, range|
|
|
16
|
+
range.each do |number|
|
|
17
|
+
break unless producer.emit([tag, number])
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
squares =
|
|
22
|
+
FiberStream::Source.ractor_producer(1..5, &EMIT_NUMBERS)
|
|
23
|
+
.map { |number| number * number }
|
|
24
|
+
.run_with(FiberStream::Sink.to_a)
|
|
25
|
+
|
|
26
|
+
puts "Squares from one FiberStream-owned producer Ractor:"
|
|
27
|
+
puts squares.join(", ")
|
|
28
|
+
|
|
29
|
+
merged =
|
|
30
|
+
FiberStream::Source.ractor_merge_producers do |group|
|
|
31
|
+
group.producer(:low, 1..3, &EMIT_TAGGED_NUMBERS)
|
|
32
|
+
group.producer(:high, 10..12, &EMIT_TAGGED_NUMBERS)
|
|
33
|
+
end.run_with(FiberStream::Sink.to_a)
|
|
34
|
+
|
|
35
|
+
puts
|
|
36
|
+
puts "Merged values from two owned producer Ractors:"
|
|
37
|
+
merged.each do |tag, number|
|
|
38
|
+
puts "- #{tag}: #{number}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
puts
|
|
42
|
+
puts "Producer blocks use RactorProducer#emit and stop when it returns false."
|
|
43
|
+
puts "FiberStream creates the data ports, ack ports, producer Ractors, and cleanup path."
|