fiber_stream 0.1.0 → 0.3.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 +57 -0
- data/README.md +283 -12
- data/examples/README.md +10 -0
- data/examples/async_http_streaming_body.rb +115 -0
- data/examples/ractor_merge_ports_and_map.rb +116 -0
- data/lib/fiber_stream/errors.rb +4 -1
- data/lib/fiber_stream/flow.rb +74 -1
- data/lib/fiber_stream/pull/async_boundary.rb +28 -11
- data/lib/fiber_stream/pull/buffer_boundary.rb +28 -10
- data/lib/fiber_stream/pull/concat.rb +103 -0
- data/lib/fiber_stream/pull/drop.rb +58 -0
- data/lib/fiber_stream/pull/drop_while.rb +61 -0
- data/lib/fiber_stream/pull/grouped.rb +46 -0
- data/lib/fiber_stream/pull/merge.rb +230 -0
- data/lib/fiber_stream/pull/parallel_map_boundary.rb +28 -24
- data/lib/fiber_stream/pull/ractor_map_boundary.rb +103 -79
- data/lib/fiber_stream/pull/ractor_merge_ports_source.rb +358 -0
- data/lib/fiber_stream/pull/ractor_port_source.rb +14 -14
- data/lib/fiber_stream/pull/split.rb +134 -0
- data/lib/fiber_stream/pull/take_while.rb +42 -0
- data/lib/fiber_stream/pull/zip.rb +83 -0
- data/lib/fiber_stream/pull.rb +48 -3
- data/lib/fiber_stream/ractor_port.rb +3 -1
- data/lib/fiber_stream/running_pipeline.rb +18 -8
- data/lib/fiber_stream/sink.rb +24 -0
- data/lib/fiber_stream/source.rb +190 -7
- data/lib/fiber_stream/version.rb +1 -1
- data/sig/fiber_stream.rbs +18 -1
- metadata +27 -2
data/lib/fiber_stream/pull.rb
CHANGED
|
@@ -28,6 +28,22 @@ module FiberStream
|
|
|
28
28
|
RactorPortSource.new(port, ack_port, ack_transfer, cancel)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def self.ractor_merge_ports(port_pairs, ack_transfer, cancel)
|
|
32
|
+
RactorMergePortsSource.new(port_pairs, ack_transfer, cancel)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.concat(left_materializer, right_materializer)
|
|
36
|
+
Concat.new(left_materializer, right_materializer)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.zip(left_materializer, right_materializer)
|
|
40
|
+
Zip.new(left_materializer, right_materializer)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.merge(left_materializer, right_materializer)
|
|
44
|
+
Merge.new(left_materializer, right_materializer)
|
|
45
|
+
end
|
|
46
|
+
|
|
31
47
|
def self.map(upstream, transform)
|
|
32
48
|
Map.new(upstream, transform)
|
|
33
49
|
end
|
|
@@ -48,6 +64,22 @@ module FiberStream
|
|
|
48
64
|
Take.new(upstream, count)
|
|
49
65
|
end
|
|
50
66
|
|
|
67
|
+
def self.drop(upstream, count)
|
|
68
|
+
Drop.new(upstream, count)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.grouped(upstream, count)
|
|
72
|
+
Grouped.new(upstream, count)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.take_while(upstream, predicate)
|
|
76
|
+
TakeWhile.new(upstream, predicate)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.drop_while(upstream, predicate)
|
|
80
|
+
DropWhile.new(upstream, predicate)
|
|
81
|
+
end
|
|
82
|
+
|
|
51
83
|
def self.async(upstream)
|
|
52
84
|
AsyncBoundary.new(upstream)
|
|
53
85
|
end
|
|
@@ -60,6 +92,10 @@ module FiberStream
|
|
|
60
92
|
Lines.new(upstream, chomp, max_length)
|
|
61
93
|
end
|
|
62
94
|
|
|
95
|
+
def self.split(upstream, separator, keep_separator, max_length)
|
|
96
|
+
Split.new(upstream, separator, keep_separator, max_length)
|
|
97
|
+
end
|
|
98
|
+
|
|
63
99
|
private_constant :DONE
|
|
64
100
|
end
|
|
65
101
|
end
|
|
@@ -67,10 +103,19 @@ end
|
|
|
67
103
|
require_relative "pull/each"
|
|
68
104
|
require_relative "pull/io_source"
|
|
69
105
|
require_relative "pull/ractor_port_source"
|
|
106
|
+
require_relative "pull/ractor_merge_ports_source"
|
|
107
|
+
require_relative "pull/concat"
|
|
108
|
+
require_relative "pull/zip"
|
|
109
|
+
require_relative "pull/merge"
|
|
70
110
|
require_relative "pull/map"
|
|
71
111
|
require_relative "pull/select"
|
|
72
112
|
require_relative "pull/take"
|
|
113
|
+
require_relative "pull/drop"
|
|
114
|
+
require_relative "pull/grouped"
|
|
115
|
+
require_relative "pull/take_while"
|
|
116
|
+
require_relative "pull/drop_while"
|
|
73
117
|
require_relative "pull/lines"
|
|
118
|
+
require_relative "pull/split"
|
|
74
119
|
require_relative "pull/async_boundary"
|
|
75
120
|
require_relative "pull/buffer_boundary"
|
|
76
121
|
require_relative "pull/parallel_map_boundary"
|
|
@@ -78,8 +123,8 @@ require_relative "pull/ractor_map_boundary"
|
|
|
78
123
|
|
|
79
124
|
module FiberStream
|
|
80
125
|
module Pull
|
|
81
|
-
private_constant :Each, :IOSource, :RactorPortSource, :
|
|
82
|
-
:
|
|
83
|
-
:RactorMapBoundary
|
|
126
|
+
private_constant :Each, :IOSource, :RactorPortSource, :RactorMergePortsSource, :Concat, :Zip, :Merge, :Map,
|
|
127
|
+
:Select, :Take, :Drop, :Grouped, :TakeWhile, :DropWhile, :Lines, :Split, :AsyncBoundary,
|
|
128
|
+
:BufferBoundary, :ParallelMapBoundary, :RactorMapBoundary
|
|
84
129
|
end
|
|
85
130
|
end
|
|
@@ -6,7 +6,9 @@ module FiberStream
|
|
|
6
6
|
# Producers send `Element`, `Complete`, and `Failure` messages to the data
|
|
7
7
|
# port. FiberStream sends `Ack` and `Cancel` messages to the producer-owned
|
|
8
8
|
# acknowledgment port. The envelopes keep stream values distinct from control
|
|
9
|
-
# messages and support Ruby pattern matching.
|
|
9
|
+
# messages and support Ruby pattern matching. `Failure` cause metadata is
|
|
10
|
+
# producer-provided and is surfaced through `RactorPortSourceError`; producers
|
|
11
|
+
# should sanitize it before crossing trust boundaries.
|
|
10
12
|
module RactorPort
|
|
11
13
|
Element = ::Data.define(:value)
|
|
12
14
|
Complete = ::Data.define
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module FiberStream
|
|
4
4
|
class RunningPipeline
|
|
5
|
+
ValueMessage = Data.define(:value)
|
|
6
|
+
ErrorMessage = Data.define(:error)
|
|
7
|
+
CancelledMessage = Data.define(:error)
|
|
8
|
+
private_constant :ValueMessage, :ErrorMessage, :CancelledMessage
|
|
9
|
+
|
|
5
10
|
def initialize(scheduler, &run)
|
|
6
11
|
@scheduler = scheduler
|
|
7
12
|
@completion = nil
|
|
@@ -82,16 +87,19 @@ module FiberStream
|
|
|
82
87
|
private
|
|
83
88
|
|
|
84
89
|
def run_background(run)
|
|
85
|
-
complete(
|
|
90
|
+
complete(ValueMessage.new(value: run.call))
|
|
91
|
+
rescue SystemExit, SignalException => error
|
|
92
|
+
complete(ErrorMessage.new(error:))
|
|
93
|
+
raise
|
|
86
94
|
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
87
95
|
complete(classify_error(error))
|
|
88
96
|
end
|
|
89
97
|
|
|
90
98
|
def classify_error(error)
|
|
91
99
|
if cancellation_error?(error)
|
|
92
|
-
|
|
100
|
+
CancelledMessage.new(error:)
|
|
93
101
|
else
|
|
94
|
-
|
|
102
|
+
ErrorMessage.new(error:)
|
|
95
103
|
end
|
|
96
104
|
end
|
|
97
105
|
|
|
@@ -114,11 +122,13 @@ module FiberStream
|
|
|
114
122
|
end
|
|
115
123
|
|
|
116
124
|
def deliver(message)
|
|
117
|
-
case message
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
raise
|
|
125
|
+
case message
|
|
126
|
+
in ValueMessage[value:]
|
|
127
|
+
value
|
|
128
|
+
in ErrorMessage[error:]
|
|
129
|
+
raise error
|
|
130
|
+
in CancelledMessage[error:]
|
|
131
|
+
raise error
|
|
122
132
|
end
|
|
123
133
|
end
|
|
124
134
|
|
data/lib/fiber_stream/sink.rb
CHANGED
|
@@ -56,6 +56,30 @@ module FiberStream
|
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# Creates a sink that runs a block for each stream element.
|
|
60
|
+
#
|
|
61
|
+
# The sink consumes upstream until normal completion, calls the block once
|
|
62
|
+
# per element in input order, and returns the number of elements whose block
|
|
63
|
+
# completed successfully. Exceptions raised by the block fail the stream and
|
|
64
|
+
# are re-raised from `Source#run_with`.
|
|
65
|
+
def self.foreach(&block)
|
|
66
|
+
raise ArgumentError, "missing block" unless block
|
|
67
|
+
|
|
68
|
+
new do |stream|
|
|
69
|
+
count = 0
|
|
70
|
+
|
|
71
|
+
loop do
|
|
72
|
+
value = stream.next
|
|
73
|
+
break if Pull.done?(value)
|
|
74
|
+
|
|
75
|
+
block.call(value)
|
|
76
|
+
count += 1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
count
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
59
83
|
# Creates a sink that writes String chunks to an IO-like object.
|
|
60
84
|
#
|
|
61
85
|
# The sink consumes upstream until normal completion and returns the number
|
data/lib/fiber_stream/source.rb
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module FiberStream
|
|
4
4
|
class Source
|
|
5
|
+
RactorMergePortPair = Data.define(:port, :ack_port)
|
|
6
|
+
private_constant :RactorMergePortPair
|
|
7
|
+
|
|
5
8
|
# Creates a source definition from an Enumerable.
|
|
6
9
|
#
|
|
7
10
|
# The enumerable is not consumed until values are pulled by `run_with`. Each
|
|
@@ -17,7 +20,9 @@ module FiberStream
|
|
|
17
20
|
# The IO object is not read until values are pulled by `run_with`. Each
|
|
18
21
|
# materialization reads from the same IO object's current position; this
|
|
19
22
|
# source does not snapshot, reopen, or guarantee replayability. The IO is
|
|
20
|
-
# closed only when `close: true` is passed.
|
|
23
|
+
# closed only when `close: true` is passed. `chunk_size` is the maximum byte
|
|
24
|
+
# count passed to `readpartial` for one downstream pull; very large values
|
|
25
|
+
# may cause the IO implementation to attempt large allocations.
|
|
21
26
|
def self.io(io, chunk_size: 16 * 1024, close: false)
|
|
22
27
|
raise TypeError, "io must respond to readpartial" unless io.respond_to?(:readpartial)
|
|
23
28
|
raise TypeError, "chunk_size must be an Integer" unless chunk_size.is_a?(Integer)
|
|
@@ -34,7 +39,8 @@ module FiberStream
|
|
|
34
39
|
# producer-owned port that receives `RactorPort::Ack` and
|
|
35
40
|
# `RactorPort::Cancel` control messages. The producer must wait for an ack
|
|
36
41
|
# before sending each `RactorPort::Element`, `RactorPort::Complete`, or
|
|
37
|
-
# `RactorPort::Failure` message.
|
|
42
|
+
# `RactorPort::Failure` message. Failure metadata is producer-provided and
|
|
43
|
+
# should be sanitized before crossing trust boundaries.
|
|
38
44
|
def self.ractor_port(port, ack_port:, ack_transfer: :copy, cancel: true)
|
|
39
45
|
raise TypeError, "port must respond to receive" unless port.respond_to?(:receive)
|
|
40
46
|
unless ack_port.respond_to?(:send) && ack_port.method(:send).owner != Kernel
|
|
@@ -47,6 +53,24 @@ module FiberStream
|
|
|
47
53
|
new(-> { Pull.ractor_port(port, ack_port, ack_transfer, cancel) })
|
|
48
54
|
end
|
|
49
55
|
|
|
56
|
+
# Creates a backpressure-aware source definition from multiple Ractor port
|
|
57
|
+
# pairs.
|
|
58
|
+
#
|
|
59
|
+
# Each pair must be a Hash with `:port` and `:ack_port`. The source sends
|
|
60
|
+
# at most one outstanding `RactorPort::Ack` to each active producer and
|
|
61
|
+
# emits producer values in coordinator-observed ready order. Producer work
|
|
62
|
+
# is isolated in Ractors, so demanding this source does not require a
|
|
63
|
+
# `Fiber.scheduler`. Failure metadata is producer-provided and should be
|
|
64
|
+
# sanitized before crossing trust boundaries.
|
|
65
|
+
def self.ractor_merge_ports(ports, ack_transfer: :copy, cancel: true)
|
|
66
|
+
pairs = normalize_ractor_merge_port_pairs(ports)
|
|
67
|
+
|
|
68
|
+
Flow.__send__(:validate_ractor_transfer_policy!, :ack_transfer, ack_transfer)
|
|
69
|
+
raise TypeError, "cancel must be true or false" unless [true, false].include?(cancel)
|
|
70
|
+
|
|
71
|
+
new(-> { Pull.ractor_merge_ports(pairs, ack_transfer, cancel) })
|
|
72
|
+
end
|
|
73
|
+
|
|
50
74
|
def initialize(source_factory, flows = [])
|
|
51
75
|
@source_factory = source_factory
|
|
52
76
|
@flows = flows
|
|
@@ -62,6 +86,54 @@ module FiberStream
|
|
|
62
86
|
self.class.__send__(:new, @source_factory, @flows + [flow])
|
|
63
87
|
end
|
|
64
88
|
|
|
89
|
+
# Returns a new source definition that emits this source, then `source`.
|
|
90
|
+
#
|
|
91
|
+
# Construction is lazy. The appended source is not materialized or pulled
|
|
92
|
+
# until downstream demand observes completion from this source. Flows
|
|
93
|
+
# attached before concat stay scoped to their source; flows attached after
|
|
94
|
+
# concat apply to the combined output.
|
|
95
|
+
def concat(source)
|
|
96
|
+
raise TypeError, "expected FiberStream::Source" unless source.is_a?(Source)
|
|
97
|
+
|
|
98
|
+
self.class.__send__(
|
|
99
|
+
:new,
|
|
100
|
+
-> { Pull.concat(materializer, source.__send__(:materializer)) }
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Returns a new source definition that emits pairs from this source and
|
|
105
|
+
# `source`.
|
|
106
|
+
#
|
|
107
|
+
# Construction is lazy. The receiver side is materialized only when
|
|
108
|
+
# downstream demand reaches the zip stage; the other side is materialized
|
|
109
|
+
# only after the receiver produces an element for a pair. The zipped source
|
|
110
|
+
# completes when either input completes.
|
|
111
|
+
def zip(source)
|
|
112
|
+
raise TypeError, "expected FiberStream::Source" unless source.is_a?(Source)
|
|
113
|
+
|
|
114
|
+
self.class.__send__(
|
|
115
|
+
:new,
|
|
116
|
+
-> { Pull.zip(materializer, source.__send__(:materializer)) }
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Returns a new source definition that emits values from this source and
|
|
121
|
+
# `source` in scheduler-observed ready order.
|
|
122
|
+
#
|
|
123
|
+
# Construction is lazy. The merged source starts one scheduled producer
|
|
124
|
+
# fiber per input source only when downstream demand reaches the merge. Each
|
|
125
|
+
# input's own element order is preserved, but cross-input ordering is not
|
|
126
|
+
# deterministic and requires an installed `Fiber.scheduler` from a
|
|
127
|
+
# non-blocking fiber when demanded.
|
|
128
|
+
def merge(source)
|
|
129
|
+
raise TypeError, "expected FiberStream::Source" unless source.is_a?(Source)
|
|
130
|
+
|
|
131
|
+
self.class.__send__(
|
|
132
|
+
:new,
|
|
133
|
+
-> { Pull.merge(materializer, source.__send__(:materializer)) }
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
65
137
|
# Returns a new source definition that maps each element with `block`.
|
|
66
138
|
#
|
|
67
139
|
# This is a convenience wrapper around `via(FiberStream::Flow.map { ... })`
|
|
@@ -115,6 +187,43 @@ module FiberStream
|
|
|
115
187
|
via(Flow.take(count))
|
|
116
188
|
end
|
|
117
189
|
|
|
190
|
+
# Returns a new source definition that drops the first `count` elements.
|
|
191
|
+
#
|
|
192
|
+
# This is a convenience wrapper around `via(FiberStream::Flow.drop(count))`
|
|
193
|
+
# and preserves the same validation and pull-driven backpressure behavior.
|
|
194
|
+
def drop(count)
|
|
195
|
+
via(Flow.drop(count))
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Returns a new source definition that groups adjacent elements into arrays.
|
|
199
|
+
#
|
|
200
|
+
# This is a convenience wrapper around
|
|
201
|
+
# `via(FiberStream::Flow.grouped(count))` and preserves the same validation,
|
|
202
|
+
# ordering, final partial group, and pull-driven backpressure behavior.
|
|
203
|
+
def grouped(count)
|
|
204
|
+
via(Flow.grouped(count))
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Returns a new source definition that emits leading elements while `block`
|
|
208
|
+
# is truthy.
|
|
209
|
+
#
|
|
210
|
+
# This is a convenience wrapper around
|
|
211
|
+
# `via(FiberStream::Flow.take_while { ... })` and preserves the same
|
|
212
|
+
# predicate truthiness, early completion, and upstream close behavior.
|
|
213
|
+
def take_while(&block)
|
|
214
|
+
via(Flow.take_while(&block))
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Returns a new source definition that drops leading elements while `block`
|
|
218
|
+
# is truthy.
|
|
219
|
+
#
|
|
220
|
+
# This is a convenience wrapper around
|
|
221
|
+
# `via(FiberStream::Flow.drop_while { ... })` and preserves the same
|
|
222
|
+
# predicate truthiness, prefix-dropping, and pass-through behavior.
|
|
223
|
+
def drop_while(&block)
|
|
224
|
+
via(Flow.drop_while(&block))
|
|
225
|
+
end
|
|
226
|
+
|
|
118
227
|
# Returns a new source definition with an asynchronous boundary.
|
|
119
228
|
#
|
|
120
229
|
# This is a convenience wrapper around `via(FiberStream::Flow.async)` and
|
|
@@ -135,11 +244,25 @@ module FiberStream
|
|
|
135
244
|
# Returns a new source definition that splits String chunks into lines.
|
|
136
245
|
#
|
|
137
246
|
# This is a convenience wrapper around
|
|
138
|
-
# `via(FiberStream::Flow.lines(chomp:, max_length:))`.
|
|
247
|
+
# `via(FiberStream::Flow.lines(chomp:, max_length:))`. With
|
|
248
|
+
# `max_length: nil`, one unterminated line can buffer without bound. Set a
|
|
249
|
+
# positive `max_length` for untrusted, network-facing, or otherwise
|
|
250
|
+
# unbounded streams.
|
|
139
251
|
def lines(chomp: true, max_length: nil)
|
|
140
252
|
via(Flow.lines(chomp: chomp, max_length: max_length))
|
|
141
253
|
end
|
|
142
254
|
|
|
255
|
+
# Returns a new source definition that splits String chunks into frames.
|
|
256
|
+
#
|
|
257
|
+
# This is a convenience wrapper around
|
|
258
|
+
# `via(FiberStream::Flow.split(separator, keep_separator:, max_length:))`.
|
|
259
|
+
# With `max_length: nil`, one unterminated frame can buffer without bound.
|
|
260
|
+
# Set a positive `max_length` for untrusted, network-facing, or otherwise
|
|
261
|
+
# unbounded streams.
|
|
262
|
+
def split(separator, keep_separator: false, max_length: nil)
|
|
263
|
+
via(Flow.split(separator, keep_separator: keep_separator, max_length: max_length))
|
|
264
|
+
end
|
|
265
|
+
|
|
143
266
|
# Returns a runnable pipeline from this source to `sink`.
|
|
144
267
|
#
|
|
145
268
|
# Construction is lazy. The source and sink are not materialized until
|
|
@@ -161,10 +284,7 @@ module FiberStream
|
|
|
161
284
|
primary_error = nil
|
|
162
285
|
|
|
163
286
|
begin
|
|
164
|
-
stream =
|
|
165
|
-
@flows.each do |flow|
|
|
166
|
-
stream = flow.__send__(:attach, stream)
|
|
167
|
-
end
|
|
287
|
+
stream = materialize
|
|
168
288
|
|
|
169
289
|
sink.__send__(:run, stream)
|
|
170
290
|
rescue StandardError => error
|
|
@@ -180,5 +300,68 @@ module FiberStream
|
|
|
180
300
|
end
|
|
181
301
|
|
|
182
302
|
private_class_method :new
|
|
303
|
+
|
|
304
|
+
def self.normalize_ractor_merge_port_pairs(ports)
|
|
305
|
+
raise TypeError, "ports must respond to each" unless ports.respond_to?(:each)
|
|
306
|
+
|
|
307
|
+
data_port_ids = {}
|
|
308
|
+
ack_port_ids = {}
|
|
309
|
+
pairs =
|
|
310
|
+
ports.each.map do |pair|
|
|
311
|
+
normalize_ractor_merge_port_pair(pair, data_port_ids, ack_port_ids)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
raise ArgumentError, "ractor_merge_ports requires at least two port pairs" if pairs.size < 2
|
|
315
|
+
|
|
316
|
+
pairs.freeze
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def self.normalize_ractor_merge_port_pair(pair, data_port_ids, ack_port_ids)
|
|
320
|
+
raise TypeError, "port pair must be a Hash" unless pair.is_a?(Hash)
|
|
321
|
+
raise TypeError, "port pair must include :port and :ack_port" unless pair.key?(:port) && pair.key?(:ack_port)
|
|
322
|
+
|
|
323
|
+
port = pair.fetch(:port)
|
|
324
|
+
ack_port = pair.fetch(:ack_port)
|
|
325
|
+
raise TypeError, "port must respond to receive" unless port.respond_to?(:receive)
|
|
326
|
+
unless ack_port.respond_to?(:send) && ack_port.method(:send).owner != Kernel
|
|
327
|
+
raise TypeError, "ack_port must provide Ractor-style send"
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
port_id = port.object_id
|
|
331
|
+
ack_port_id = ack_port.object_id
|
|
332
|
+
raise ArgumentError, "data ports must be distinct" if data_port_ids.key?(port_id)
|
|
333
|
+
raise ArgumentError, "ack ports must be distinct" if ack_port_ids.key?(ack_port_id)
|
|
334
|
+
|
|
335
|
+
data_port_ids[port_id] = true
|
|
336
|
+
ack_port_ids[ack_port_id] = true
|
|
337
|
+
RactorMergePortPair.new(port:, ack_port:)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
private_class_method :normalize_ractor_merge_port_pairs, :normalize_ractor_merge_port_pair
|
|
341
|
+
|
|
342
|
+
private
|
|
343
|
+
|
|
344
|
+
def materializer
|
|
345
|
+
-> { materialize }
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def materialize
|
|
349
|
+
stream = nil
|
|
350
|
+
|
|
351
|
+
begin
|
|
352
|
+
stream = @source_factory.call
|
|
353
|
+
@flows.each do |flow|
|
|
354
|
+
stream = flow.__send__(:attach, stream)
|
|
355
|
+
end
|
|
356
|
+
stream
|
|
357
|
+
rescue StandardError
|
|
358
|
+
begin
|
|
359
|
+
stream&.close
|
|
360
|
+
rescue StandardError
|
|
361
|
+
nil
|
|
362
|
+
end
|
|
363
|
+
raise
|
|
364
|
+
end
|
|
365
|
+
end
|
|
183
366
|
end
|
|
184
367
|
end
|
data/lib/fiber_stream/version.rb
CHANGED
data/sig/fiber_stream.rbs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
module FiberStream
|
|
2
2
|
type ractor_transfer_policy = :copy | :move
|
|
3
|
+
type ractor_port_pair = { port: untyped, ack_port: untyped }
|
|
3
4
|
type ractor_map_error_kind = :input_transfer | :output_transfer | :worker | :worker_termination | :isolation
|
|
4
5
|
type ractor_port_source_error_kind = :invalid_message | :producer_failure | :receive | :ack_transfer | :cancel_transfer
|
|
6
|
+
type ractor_port_cancel_reason = :closed
|
|
5
7
|
|
|
6
8
|
class SchedulerRequiredError < RuntimeError
|
|
7
9
|
end
|
|
@@ -42,7 +44,7 @@ module FiberStream
|
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
class Cancel < Data
|
|
45
|
-
attr_reader reason:
|
|
47
|
+
attr_reader reason: ractor_port_cancel_reason
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
|
|
@@ -50,15 +52,24 @@ module FiberStream
|
|
|
50
52
|
def self.each: [Elem] (Enumerable[Elem] enumerable) -> Source[Elem]
|
|
51
53
|
def self.io: (untyped io, ?chunk_size: Integer, ?close: bool) -> Source[String]
|
|
52
54
|
def self.ractor_port: [Elem] (untyped port, ack_port: untyped, ?ack_transfer: ractor_transfer_policy, ?cancel: bool) -> Source[Elem]
|
|
55
|
+
def self.ractor_merge_ports: [Elem] (Enumerable[ractor_port_pair] ports, ?ack_transfer: ractor_transfer_policy, ?cancel: bool) -> Source[Elem]
|
|
53
56
|
def via: [Out] (Flow[Elem, Out] flow) -> Source[Out]
|
|
57
|
+
def concat: [Other] (Source[Other] source) -> Source[Elem | Other]
|
|
58
|
+
def zip: [Other] (Source[Other] source) -> Source[[Elem, Other]]
|
|
59
|
+
def merge: [Other] (Source[Other] source) -> Source[Elem | Other]
|
|
54
60
|
def map: [Out] () { (Elem) -> Out } -> Source[Out]
|
|
55
61
|
def parallel_map: [Out] (concurrency: Integer) { (Elem) -> Out } -> Source[Out]
|
|
56
62
|
def ractor_map: [Out] (workers: Integer, ?input_transfer: ractor_transfer_policy, ?output_transfer: ractor_transfer_policy) { (Elem) -> Out } -> Source[Out]
|
|
57
63
|
def select: () { (Elem) -> boolish } -> Source[Elem]
|
|
58
64
|
def take: (Integer count) -> Source[Elem]
|
|
65
|
+
def drop: (Integer count) -> Source[Elem]
|
|
66
|
+
def grouped: (Integer count) -> Source[Array[Elem]]
|
|
67
|
+
def take_while: () { (Elem) -> boolish } -> Source[Elem]
|
|
68
|
+
def drop_while: () { (Elem) -> boolish } -> Source[Elem]
|
|
59
69
|
def async: () -> Source[Elem]
|
|
60
70
|
def buffer: (Integer count) -> Source[Elem]
|
|
61
71
|
def lines: (?chomp: bool, ?max_length: Integer?) -> Source[String]
|
|
72
|
+
def split: (String separator, ?keep_separator: bool, ?max_length: Integer?) -> Source[String]
|
|
62
73
|
def to: [Mat] (Sink[Elem, Mat] sink) -> Pipeline[Mat]
|
|
63
74
|
def run_with: [Mat] (Sink[Elem, Mat] sink) -> Mat
|
|
64
75
|
end
|
|
@@ -69,9 +80,14 @@ module FiberStream
|
|
|
69
80
|
def self.ractor_map: [In, Out] (workers: Integer, ?input_transfer: ractor_transfer_policy, ?output_transfer: ractor_transfer_policy) { (In) -> Out } -> Flow[In, Out]
|
|
70
81
|
def self.select: [Elem] () { (Elem) -> boolish } -> Flow[Elem, Elem]
|
|
71
82
|
def self.take: [Elem] (Integer count) -> Flow[Elem, Elem]
|
|
83
|
+
def self.drop: [Elem] (Integer count) -> Flow[Elem, Elem]
|
|
84
|
+
def self.grouped: [Elem] (Integer count) -> Flow[Elem, Array[Elem]]
|
|
85
|
+
def self.take_while: [Elem] () { (Elem) -> boolish } -> Flow[Elem, Elem]
|
|
86
|
+
def self.drop_while: [Elem] () { (Elem) -> boolish } -> Flow[Elem, Elem]
|
|
72
87
|
def self.async: [Elem] () -> Flow[Elem, Elem]
|
|
73
88
|
def self.buffer: [Elem] (Integer count) -> Flow[Elem, Elem]
|
|
74
89
|
def self.lines: (?chomp: bool, ?max_length: Integer?) -> Flow[String, String]
|
|
90
|
+
def self.split: (String separator, ?keep_separator: bool, ?max_length: Integer?) -> Flow[String, String]
|
|
75
91
|
def via: [Next] (Flow[Out, Next] flow) -> Flow[In, Next]
|
|
76
92
|
def to: [Mat] (Sink[Out, Mat] sink) -> Sink[In, Mat]
|
|
77
93
|
end
|
|
@@ -80,6 +96,7 @@ module FiberStream
|
|
|
80
96
|
def self.to_a: [Elem] () -> Sink[Elem, Array[Elem]]
|
|
81
97
|
def self.first: [Elem] () -> Sink[Elem, Elem?]
|
|
82
98
|
def self.fold: [Elem, Acc] (Acc initial) { (Acc, Elem) -> Acc } -> Sink[Elem, Acc]
|
|
99
|
+
def self.foreach: [Elem] () { (Elem) -> void } -> Sink[Elem, Integer]
|
|
83
100
|
def self.io: (untyped io, ?close: bool, ?flush: bool) -> Sink[String, Integer]
|
|
84
101
|
end
|
|
85
102
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fiber_stream
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dai Akatsuka
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: async-http
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.95'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.95'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: minitest
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -95,6 +109,7 @@ files:
|
|
|
95
109
|
- README.md
|
|
96
110
|
- examples/README.md
|
|
97
111
|
- examples/async_http_requests.rb
|
|
112
|
+
- examples/async_http_streaming_body.rb
|
|
98
113
|
- examples/background_execution.rb
|
|
99
114
|
- examples/backpressure_buffer.rb
|
|
100
115
|
- examples/basic_pipeline.rb
|
|
@@ -102,6 +117,7 @@ files:
|
|
|
102
117
|
- examples/file_copy.rb
|
|
103
118
|
- examples/line_processing.rb
|
|
104
119
|
- examples/ractor_map_hashing.rb
|
|
120
|
+
- examples/ractor_merge_ports_and_map.rb
|
|
105
121
|
- examples/ractor_port_source.rb
|
|
106
122
|
- lib/fiber_stream.rb
|
|
107
123
|
- lib/fiber_stream/errors.rb
|
|
@@ -110,15 +126,24 @@ files:
|
|
|
110
126
|
- lib/fiber_stream/pull.rb
|
|
111
127
|
- lib/fiber_stream/pull/async_boundary.rb
|
|
112
128
|
- lib/fiber_stream/pull/buffer_boundary.rb
|
|
129
|
+
- lib/fiber_stream/pull/concat.rb
|
|
130
|
+
- lib/fiber_stream/pull/drop.rb
|
|
131
|
+
- lib/fiber_stream/pull/drop_while.rb
|
|
113
132
|
- lib/fiber_stream/pull/each.rb
|
|
133
|
+
- lib/fiber_stream/pull/grouped.rb
|
|
114
134
|
- lib/fiber_stream/pull/io_source.rb
|
|
115
135
|
- lib/fiber_stream/pull/lines.rb
|
|
116
136
|
- lib/fiber_stream/pull/map.rb
|
|
137
|
+
- lib/fiber_stream/pull/merge.rb
|
|
117
138
|
- lib/fiber_stream/pull/parallel_map_boundary.rb
|
|
118
139
|
- lib/fiber_stream/pull/ractor_map_boundary.rb
|
|
140
|
+
- lib/fiber_stream/pull/ractor_merge_ports_source.rb
|
|
119
141
|
- lib/fiber_stream/pull/ractor_port_source.rb
|
|
120
142
|
- lib/fiber_stream/pull/select.rb
|
|
143
|
+
- lib/fiber_stream/pull/split.rb
|
|
121
144
|
- lib/fiber_stream/pull/take.rb
|
|
145
|
+
- lib/fiber_stream/pull/take_while.rb
|
|
146
|
+
- lib/fiber_stream/pull/zip.rb
|
|
122
147
|
- lib/fiber_stream/ractor_port.rb
|
|
123
148
|
- lib/fiber_stream/running_pipeline.rb
|
|
124
149
|
- lib/fiber_stream/sink.rb
|
|
@@ -131,7 +156,7 @@ licenses:
|
|
|
131
156
|
metadata:
|
|
132
157
|
allowed_push_host: https://rubygems.org
|
|
133
158
|
homepage_uri: https://github.com/dakatsuka/fiber_stream
|
|
134
|
-
source_code_uri: https://github.com/dakatsuka/fiber_stream/tree/v0.
|
|
159
|
+
source_code_uri: https://github.com/dakatsuka/fiber_stream/tree/v0.3.0
|
|
135
160
|
changelog_uri: https://github.com/dakatsuka/fiber_stream/blob/main/CHANGELOG.md
|
|
136
161
|
rubygems_mfa_required: 'true'
|
|
137
162
|
rdoc_options: []
|