fiber_stream 0.3.0 → 0.4.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.
@@ -10,10 +10,7 @@ module FiberStream
10
10
  new do |stream|
11
11
  values = []
12
12
 
13
- loop do
14
- value = stream.next
15
- break if Pull.done?(value)
16
-
13
+ Pull.each_value(stream) do |value|
17
14
  values << value
18
15
  end
19
16
 
@@ -45,10 +42,7 @@ module FiberStream
45
42
  new do |stream|
46
43
  accumulator = initial
47
44
 
48
- loop do
49
- value = stream.next
50
- break if Pull.done?(value)
51
-
45
+ Pull.each_value(stream) do |value|
52
46
  accumulator = block.call(accumulator, value)
53
47
  end
54
48
 
@@ -68,10 +62,7 @@ module FiberStream
68
62
  new do |stream|
69
63
  count = 0
70
64
 
71
- loop do
72
- value = stream.next
73
- break if Pull.done?(value)
74
-
65
+ Pull.each_value(stream) do |value|
75
66
  block.call(value)
76
67
  count += 1
77
68
  end
@@ -99,15 +90,17 @@ module FiberStream
99
90
  end
100
91
  end
101
92
 
93
+ def self.build(&run) # :nodoc:
94
+ new(&run)
95
+ end
96
+
102
97
  def initialize(&run)
103
98
  @run = run
104
99
  end
105
100
 
106
101
  private_class_method :new
107
102
 
108
- private
109
-
110
- def run(stream)
103
+ def run_stream(stream) # :nodoc:
111
104
  @run.call(stream)
112
105
  end
113
106
 
@@ -121,10 +114,7 @@ module FiberStream
121
114
  end
122
115
 
123
116
  def run(stream)
124
- loop do
125
- value = stream.next
126
- break if Pull.done?(value)
127
-
117
+ Pull.each_value(stream) do |value|
128
118
  write(value)
129
119
  end
130
120
 
@@ -47,7 +47,7 @@ module FiberStream
47
47
  raise TypeError, "ack_port must provide Ractor-style send"
48
48
  end
49
49
 
50
- Flow.__send__(:validate_ractor_transfer_policy!, :ack_transfer, ack_transfer)
50
+ Internal::RactorTransferPolicy.validate!(:ack_transfer, ack_transfer)
51
51
  raise TypeError, "cancel must be true or false" unless [true, false].include?(cancel)
52
52
 
53
53
  new(-> { Pull.ractor_port(port, ack_port, ack_transfer, cancel) })
@@ -65,12 +65,56 @@ module FiberStream
65
65
  def self.ractor_merge_ports(ports, ack_transfer: :copy, cancel: true)
66
66
  pairs = normalize_ractor_merge_port_pairs(ports)
67
67
 
68
- Flow.__send__(:validate_ractor_transfer_policy!, :ack_transfer, ack_transfer)
68
+ Internal::RactorTransferPolicy.validate!(:ack_transfer, ack_transfer)
69
69
  raise TypeError, "cancel must be true or false" unless [true, false].include?(cancel)
70
70
 
71
71
  new(-> { Pull.ractor_merge_ports(pairs, ack_transfer, cancel) })
72
72
  end
73
73
 
74
+ # Creates a source backed by one FiberStream-owned producer ractor.
75
+ #
76
+ # The producer ractor is started lazily on first downstream demand. The
77
+ # shareable block receives a `RactorProducer` context and the provided
78
+ # arguments. Calls to the context preserve one-outstanding-ack
79
+ # backpressure, and cleanup always requests cooperative cancellation.
80
+ def self.ractor_producer(*args, transfer: :copy, ack_transfer: :copy, &block)
81
+ raise ArgumentError, "missing block" unless block
82
+
83
+ Internal::RactorTransferPolicy.validate!(:transfer, transfer)
84
+ Internal::RactorTransferPolicy.validate!(:ack_transfer, ack_transfer)
85
+ raise TypeError, "block must be shareable" unless Ractor.shareable?(block)
86
+
87
+ group = RactorProducerGroup.new(transfer)
88
+ group.producer(*args, &block)
89
+ definitions = group.definitions
90
+
91
+ new(-> { Pull.ractor_producer(definitions, ack_transfer) })
92
+ end
93
+
94
+ # Creates a source backed by multiple FiberStream-owned producer ractors.
95
+ #
96
+ # The registration block runs at construction to collect producer
97
+ # definitions, but producer ractors and ports are started lazily on first
98
+ # downstream demand. Outputs are merged with the same ready-order semantics
99
+ # as `Source.ractor_merge_ports`.
100
+ def self.ractor_merge_producers(transfer: :copy, ack_transfer: :copy, &block)
101
+ raise ArgumentError, "missing block" unless block
102
+
103
+ Internal::RactorTransferPolicy.validate!(:transfer, transfer)
104
+ Internal::RactorTransferPolicy.validate!(:ack_transfer, ack_transfer)
105
+
106
+ group = RactorProducerGroup.new(transfer)
107
+ block.call(group)
108
+ definitions = group.definitions
109
+ raise ArgumentError, "ractor_merge_producers requires at least two producers" if definitions.size < 2
110
+
111
+ new(-> { Pull.ractor_merge_producers(definitions, ack_transfer) })
112
+ end
113
+
114
+ def self.build(source_factory, flows = []) # :nodoc:
115
+ new(source_factory, flows)
116
+ end
117
+
74
118
  def initialize(source_factory, flows = [])
75
119
  @source_factory = source_factory
76
120
  @flows = flows
@@ -83,7 +127,7 @@ module FiberStream
83
127
  def via(flow)
84
128
  raise TypeError, "expected FiberStream::Flow" unless flow.is_a?(Flow)
85
129
 
86
- self.class.__send__(:new, @source_factory, @flows + [flow])
130
+ self.class.build(@source_factory, @flows + [flow])
87
131
  end
88
132
 
89
133
  # Returns a new source definition that emits this source, then `source`.
@@ -95,10 +139,7 @@ module FiberStream
95
139
  def concat(source)
96
140
  raise TypeError, "expected FiberStream::Source" unless source.is_a?(Source)
97
141
 
98
- self.class.__send__(
99
- :new,
100
- -> { Pull.concat(materializer, source.__send__(:materializer)) }
101
- )
142
+ self.class.build(-> { Pull.concat(to_pull_materializer, source.to_pull_materializer) })
102
143
  end
103
144
 
104
145
  # Returns a new source definition that emits pairs from this source and
@@ -111,10 +152,7 @@ module FiberStream
111
152
  def zip(source)
112
153
  raise TypeError, "expected FiberStream::Source" unless source.is_a?(Source)
113
154
 
114
- self.class.__send__(
115
- :new,
116
- -> { Pull.zip(materializer, source.__send__(:materializer)) }
117
- )
155
+ self.class.build(-> { Pull.zip(to_pull_materializer, source.to_pull_materializer) })
118
156
  end
119
157
 
120
158
  # Returns a new source definition that emits values from this source and
@@ -128,10 +166,7 @@ module FiberStream
128
166
  def merge(source)
129
167
  raise TypeError, "expected FiberStream::Source" unless source.is_a?(Source)
130
168
 
131
- self.class.__send__(
132
- :new,
133
- -> { Pull.merge(materializer, source.__send__(:materializer)) }
134
- )
169
+ self.class.build(-> { Pull.merge(to_pull_materializer, source.to_pull_materializer) })
135
170
  end
136
171
 
137
172
  # Returns a new source definition that maps each element with `block`.
@@ -153,6 +188,18 @@ module FiberStream
153
188
  via(Flow.parallel_map(concurrency: concurrency, &block))
154
189
  end
155
190
 
191
+ # Returns a new source definition that maps elements concurrently and emits
192
+ # mapped values in completion order.
193
+ #
194
+ # This is a convenience wrapper around
195
+ # `via(FiberStream::Flow.parallel_unordered_map(concurrency:) { ... })`.
196
+ # The operation preserves the same scheduler requirement, validation,
197
+ # bounded upstream run-ahead, and cancellation behavior while making no
198
+ # input-order guarantee.
199
+ def parallel_unordered_map(concurrency:, &block)
200
+ via(Flow.parallel_unordered_map(concurrency: concurrency, &block))
201
+ end
202
+
156
203
  # Returns a new source definition that maps elements in Ractor workers.
157
204
  #
158
205
  # This is a convenience wrapper around
@@ -204,6 +251,15 @@ module FiberStream
204
251
  via(Flow.grouped(count))
205
252
  end
206
253
 
254
+ # Returns a new source definition that emits running accumulators.
255
+ #
256
+ # This is a convenience wrapper around
257
+ # `via(FiberStream::Flow.scan(initial) { ... })` and preserves the same
258
+ # reducer order, lazy construction, and pull-driven backpressure behavior.
259
+ def scan(initial, &block)
260
+ via(Flow.scan(initial, &block))
261
+ end
262
+
207
263
  # Returns a new source definition that emits leading elements while `block`
208
264
  # is truthy.
209
265
  #
@@ -270,7 +326,7 @@ module FiberStream
270
326
  def to(sink)
271
327
  raise TypeError, "expected FiberStream::Sink" unless sink.is_a?(Sink)
272
328
 
273
- Pipeline.__send__(:new, self, sink)
329
+ Pipeline.build(self, sink)
274
330
  end
275
331
 
276
332
  # Materializes and runs this source with `sink`.
@@ -286,7 +342,7 @@ module FiberStream
286
342
  begin
287
343
  stream = materialize
288
344
 
289
- sink.__send__(:run, stream)
345
+ sink.run_stream(stream)
290
346
  rescue StandardError => error
291
347
  primary_error = error
292
348
  raise
@@ -301,6 +357,10 @@ module FiberStream
301
357
 
302
358
  private_class_method :new
303
359
 
360
+ def to_pull_materializer # :nodoc:
361
+ method(:materialize)
362
+ end
363
+
304
364
  def self.normalize_ractor_merge_port_pairs(ports)
305
365
  raise TypeError, "ports must respond to each" unless ports.respond_to?(:each)
306
366
 
@@ -341,17 +401,13 @@ module FiberStream
341
401
 
342
402
  private
343
403
 
344
- def materializer
345
- -> { materialize }
346
- end
347
-
348
404
  def materialize
349
405
  stream = nil
350
406
 
351
407
  begin
352
408
  stream = @source_factory.call
353
409
  @flows.each do |flow|
354
- stream = flow.__send__(:attach, stream)
410
+ stream = flow.attach_to(stream)
355
411
  end
356
412
  stream
357
413
  rescue StandardError
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FiberStream
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/fiber_stream.rb CHANGED
@@ -3,7 +3,9 @@
3
3
  require_relative "fiber_stream/pull"
4
4
  require_relative "fiber_stream/version"
5
5
  require_relative "fiber_stream/errors"
6
+ require_relative "fiber_stream/internal/ractor_transfer_policy"
6
7
  require_relative "fiber_stream/ractor_port"
8
+ require_relative "fiber_stream/ractor_producer"
7
9
  require_relative "fiber_stream/flow"
8
10
  require_relative "fiber_stream/sink"
9
11
  require_relative "fiber_stream/running_pipeline"
data/sig/fiber_stream.rbs CHANGED
@@ -2,7 +2,7 @@ module FiberStream
2
2
  type ractor_transfer_policy = :copy | :move
3
3
  type ractor_port_pair = { port: untyped, ack_port: untyped }
4
4
  type ractor_map_error_kind = :input_transfer | :output_transfer | :worker | :worker_termination | :isolation
5
- type ractor_port_source_error_kind = :invalid_message | :producer_failure | :receive | :ack_transfer | :cancel_transfer
5
+ type ractor_port_source_error_kind = :invalid_message | :producer_failure | :receive | :ack_transfer | :cancel_transfer | :producer_setup
6
6
  type ractor_port_cancel_reason = :closed
7
7
 
8
8
  class SchedulerRequiredError < RuntimeError
@@ -48,22 +48,37 @@ module FiberStream
48
48
  end
49
49
  end
50
50
 
51
+ class RactorProducer
52
+ def emit: [Elem] (Elem value, ?transfer: ractor_transfer_policy?) -> bool
53
+ def complete: () -> bool
54
+ def fail: (?untyped error, ?cause_class_name: String?, ?cause_message: String?) -> bool
55
+ def cancelled?: () -> bool
56
+ end
57
+
58
+ class RactorProducerGroup
59
+ def producer: (*untyped args, ?transfer: ractor_transfer_policy?) { (RactorProducer producer, *untyped args) -> void } -> self
60
+ end
61
+
51
62
  class Source[Elem]
52
63
  def self.each: [Elem] (Enumerable[Elem] enumerable) -> Source[Elem]
53
64
  def self.io: (untyped io, ?chunk_size: Integer, ?close: bool) -> Source[String]
54
65
  def self.ractor_port: [Elem] (untyped port, ack_port: untyped, ?ack_transfer: ractor_transfer_policy, ?cancel: bool) -> Source[Elem]
55
66
  def self.ractor_merge_ports: [Elem] (Enumerable[ractor_port_pair] ports, ?ack_transfer: ractor_transfer_policy, ?cancel: bool) -> Source[Elem]
67
+ def self.ractor_producer: [Elem] (*untyped args, ?transfer: ractor_transfer_policy, ?ack_transfer: ractor_transfer_policy) { (RactorProducer producer, *untyped args) -> void } -> Source[Elem]
68
+ def self.ractor_merge_producers: [Elem] (?transfer: ractor_transfer_policy, ?ack_transfer: ractor_transfer_policy) { (RactorProducerGroup group) -> void } -> Source[Elem]
56
69
  def via: [Out] (Flow[Elem, Out] flow) -> Source[Out]
57
70
  def concat: [Other] (Source[Other] source) -> Source[Elem | Other]
58
71
  def zip: [Other] (Source[Other] source) -> Source[[Elem, Other]]
59
72
  def merge: [Other] (Source[Other] source) -> Source[Elem | Other]
60
73
  def map: [Out] () { (Elem) -> Out } -> Source[Out]
61
74
  def parallel_map: [Out] (concurrency: Integer) { (Elem) -> Out } -> Source[Out]
75
+ def parallel_unordered_map: [Out] (concurrency: Integer) { (Elem) -> Out } -> Source[Out]
62
76
  def ractor_map: [Out] (workers: Integer, ?input_transfer: ractor_transfer_policy, ?output_transfer: ractor_transfer_policy) { (Elem) -> Out } -> Source[Out]
63
77
  def select: () { (Elem) -> boolish } -> Source[Elem]
64
78
  def take: (Integer count) -> Source[Elem]
65
79
  def drop: (Integer count) -> Source[Elem]
66
80
  def grouped: (Integer count) -> Source[Array[Elem]]
81
+ def scan: [Acc] (Acc initial) { (Acc, Elem) -> Acc } -> Source[Acc]
67
82
  def take_while: () { (Elem) -> boolish } -> Source[Elem]
68
83
  def drop_while: () { (Elem) -> boolish } -> Source[Elem]
69
84
  def async: () -> Source[Elem]
@@ -77,11 +92,13 @@ module FiberStream
77
92
  class Flow[In, Out]
78
93
  def self.map: [In, Out] () { (In) -> Out } -> Flow[In, Out]
79
94
  def self.parallel_map: [In, Out] (concurrency: Integer) { (In) -> Out } -> Flow[In, Out]
95
+ def self.parallel_unordered_map: [In, Out] (concurrency: Integer) { (In) -> Out } -> Flow[In, Out]
80
96
  def self.ractor_map: [In, Out] (workers: Integer, ?input_transfer: ractor_transfer_policy, ?output_transfer: ractor_transfer_policy) { (In) -> Out } -> Flow[In, Out]
81
97
  def self.select: [Elem] () { (Elem) -> boolish } -> Flow[Elem, Elem]
82
98
  def self.take: [Elem] (Integer count) -> Flow[Elem, Elem]
83
99
  def self.drop: [Elem] (Integer count) -> Flow[Elem, Elem]
84
100
  def self.grouped: [Elem] (Integer count) -> Flow[Elem, Array[Elem]]
101
+ def self.scan: [Elem, Acc] (Acc initial) { (Acc, Elem) -> Acc } -> Flow[Elem, Acc]
85
102
  def self.take_while: [Elem] () { (Elem) -> boolish } -> Flow[Elem, Elem]
86
103
  def self.drop_while: [Elem] () { (Elem) -> boolish } -> Flow[Elem, Elem]
87
104
  def self.async: [Elem] () -> Flow[Elem, Elem]
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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dai Akatsuka
@@ -119,9 +119,11 @@ files:
119
119
  - examples/ractor_map_hashing.rb
120
120
  - examples/ractor_merge_ports_and_map.rb
121
121
  - examples/ractor_port_source.rb
122
+ - examples/ractor_producer_sources.rb
122
123
  - lib/fiber_stream.rb
123
124
  - lib/fiber_stream/errors.rb
124
125
  - lib/fiber_stream/flow.rb
126
+ - lib/fiber_stream/internal/ractor_transfer_policy.rb
125
127
  - lib/fiber_stream/pipeline.rb
126
128
  - lib/fiber_stream/pull.rb
127
129
  - lib/fiber_stream/pull/async_boundary.rb
@@ -136,15 +138,19 @@ files:
136
138
  - lib/fiber_stream/pull/map.rb
137
139
  - lib/fiber_stream/pull/merge.rb
138
140
  - lib/fiber_stream/pull/parallel_map_boundary.rb
141
+ - lib/fiber_stream/pull/parallel_unordered_map_boundary.rb
139
142
  - lib/fiber_stream/pull/ractor_map_boundary.rb
140
143
  - lib/fiber_stream/pull/ractor_merge_ports_source.rb
141
144
  - lib/fiber_stream/pull/ractor_port_source.rb
145
+ - lib/fiber_stream/pull/ractor_producer_source.rb
146
+ - lib/fiber_stream/pull/scan.rb
142
147
  - lib/fiber_stream/pull/select.rb
143
148
  - lib/fiber_stream/pull/split.rb
144
149
  - lib/fiber_stream/pull/take.rb
145
150
  - lib/fiber_stream/pull/take_while.rb
146
151
  - lib/fiber_stream/pull/zip.rb
147
152
  - lib/fiber_stream/ractor_port.rb
153
+ - lib/fiber_stream/ractor_producer.rb
148
154
  - lib/fiber_stream/running_pipeline.rb
149
155
  - lib/fiber_stream/sink.rb
150
156
  - lib/fiber_stream/source.rb
@@ -156,7 +162,7 @@ licenses:
156
162
  metadata:
157
163
  allowed_push_host: https://rubygems.org
158
164
  homepage_uri: https://github.com/dakatsuka/fiber_stream
159
- source_code_uri: https://github.com/dakatsuka/fiber_stream/tree/v0.3.0
165
+ source_code_uri: https://github.com/dakatsuka/fiber_stream/tree/v0.4.0
160
166
  changelog_uri: https://github.com/dakatsuka/fiber_stream/blob/main/CHANGELOG.md
161
167
  rubygems_mfa_required: 'true'
162
168
  rdoc_options: []
@@ -173,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
179
  - !ruby/object:Gem::Version
174
180
  version: '0'
175
181
  requirements: []
176
- rubygems_version: 4.0.6
182
+ rubygems_version: 4.0.10
177
183
  specification_version: 4
178
184
  summary: Asynchronous, non-blocking stream processing with backpressure.
179
185
  test_files: []