fiber_stream 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/README.md +167 -43
- data/examples/README.md +11 -0
- data/examples/ractor_merge_ports_and_map.rb +116 -0
- data/examples/ractor_producer_sources.rb +43 -0
- data/lib/fiber_stream/errors.rb +4 -1
- data/lib/fiber_stream/flow.rb +75 -16
- data/lib/fiber_stream/internal/ractor_transfer_policy.rb +17 -0
- data/lib/fiber_stream/pipeline.rb +5 -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 +9 -1
- 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/parallel_unordered_map_boundary.rb +311 -0
- data/lib/fiber_stream/pull/ractor_map_boundary.rb +112 -89
- data/lib/fiber_stream/pull/ractor_merge_ports_source.rb +373 -0
- data/lib/fiber_stream/pull/ractor_port_source.rb +53 -20
- data/lib/fiber_stream/pull/ractor_producer_source.rb +349 -0
- data/lib/fiber_stream/pull/scan.rb +38 -0
- data/lib/fiber_stream/pull/split.rb +134 -0
- data/lib/fiber_stream/pull.rb +74 -5
- data/lib/fiber_stream/ractor_port.rb +3 -1
- data/lib/fiber_stream/ractor_producer.rb +167 -0
- data/lib/fiber_stream/running_pipeline.rb +22 -8
- data/lib/fiber_stream/sink.rb +9 -19
- data/lib/fiber_stream/source.rb +177 -19
- data/lib/fiber_stream/version.rb +1 -1
- data/lib/fiber_stream.rb +2 -0
- data/sig/fiber_stream.rbs +25 -1
- metadata +14 -3
|
@@ -10,7 +10,18 @@ module FiberStream
|
|
|
10
10
|
# is below `workers`, preserving bounded backpressure and ordered output.
|
|
11
11
|
class RactorMapBoundary
|
|
12
12
|
TERMINAL_RESULT_CAPACITY = 1
|
|
13
|
-
|
|
13
|
+
Job = ::Data.define(:sequence, :value)
|
|
14
|
+
Shutdown = ::Data.define
|
|
15
|
+
Ready = ::Data.define(:worker_id)
|
|
16
|
+
WorkerValue = ::Data.define(:worker_id, :sequence, :value)
|
|
17
|
+
WorkerFailure = ::Data.define(:worker_id, :sequence, :kind, :cause_class_name, :cause_message)
|
|
18
|
+
Stopped = ::Data.define(:worker_id)
|
|
19
|
+
ResultValue = ::Data.define(:sequence, :value)
|
|
20
|
+
ResultDone = ::Data.define(:sequence)
|
|
21
|
+
ResultError = ::Data.define(:sequence, :error)
|
|
22
|
+
|
|
23
|
+
private_constant :Job, :Shutdown, :Ready, :WorkerValue, :WorkerFailure, :Stopped
|
|
24
|
+
private_constant :ResultValue, :ResultDone, :ResultError
|
|
14
25
|
|
|
15
26
|
def initialize(upstream, workers, input_transfer, output_transfer, transform)
|
|
16
27
|
@upstream = upstream
|
|
@@ -53,6 +64,8 @@ module FiberStream
|
|
|
53
64
|
@done = true
|
|
54
65
|
close_error = close_upstream
|
|
55
66
|
close_admission(close_upstream: false)
|
|
67
|
+
close_ready_queue
|
|
68
|
+
close_result_queue
|
|
56
69
|
request_worker_shutdown
|
|
57
70
|
wait_for_workers
|
|
58
71
|
close_error ||= @upstream_close_error
|
|
@@ -67,8 +80,7 @@ module FiberStream
|
|
|
67
80
|
@started = true
|
|
68
81
|
@result_port = Ractor::Port.new
|
|
69
82
|
@workers_count.times do |worker_id|
|
|
70
|
-
@workers << self.class.
|
|
71
|
-
:spawn_worker,
|
|
83
|
+
@workers << self.class.spawn_worker(
|
|
72
84
|
worker_id,
|
|
73
85
|
@result_port,
|
|
74
86
|
@transform,
|
|
@@ -103,7 +115,7 @@ module FiberStream
|
|
|
103
115
|
break unless worker
|
|
104
116
|
|
|
105
117
|
message = pull_job_message
|
|
106
|
-
if message.
|
|
118
|
+
if message.is_a?(Job)
|
|
107
119
|
@in_flight += 1
|
|
108
120
|
break unless deliver_job(worker, message)
|
|
109
121
|
else
|
|
@@ -120,19 +132,23 @@ module FiberStream
|
|
|
120
132
|
|
|
121
133
|
sequence = @next_sequence
|
|
122
134
|
@next_sequence += 1
|
|
123
|
-
|
|
135
|
+
Job.new(sequence, value)
|
|
124
136
|
rescue StandardError => error
|
|
125
137
|
close_upstream(record_error: false)
|
|
126
|
-
|
|
138
|
+
ResultError.new(sequence: @next_sequence, error:)
|
|
127
139
|
end
|
|
128
140
|
|
|
129
141
|
def terminal_done_message
|
|
130
142
|
close_error = close_upstream
|
|
131
|
-
|
|
143
|
+
if close_error
|
|
144
|
+
ResultError.new(sequence: @next_sequence, error: close_error)
|
|
145
|
+
else
|
|
146
|
+
ResultDone.new(sequence: @next_sequence)
|
|
147
|
+
end
|
|
132
148
|
end
|
|
133
149
|
|
|
134
150
|
def deliver_job(worker, message)
|
|
135
|
-
sequence = message.
|
|
151
|
+
sequence = message.sequence
|
|
136
152
|
track_worker_job(worker, sequence)
|
|
137
153
|
|
|
138
154
|
if @input_transfer == :move
|
|
@@ -143,8 +159,7 @@ module FiberStream
|
|
|
143
159
|
true
|
|
144
160
|
rescue StandardError => error
|
|
145
161
|
clear_worker_job(worker)
|
|
146
|
-
sequence
|
|
147
|
-
record_result([:error, sequence, build_ractor_map_error(sequence, :input_transfer, error)])
|
|
162
|
+
record_result(ResultError.new(sequence:, error: build_ractor_map_error(sequence, :input_transfer, error)))
|
|
148
163
|
false
|
|
149
164
|
end
|
|
150
165
|
|
|
@@ -162,28 +177,23 @@ module FiberStream
|
|
|
162
177
|
end
|
|
163
178
|
|
|
164
179
|
def emit(message)
|
|
165
|
-
case message
|
|
166
|
-
|
|
167
|
-
emit_value(
|
|
168
|
-
|
|
180
|
+
case message
|
|
181
|
+
in ResultValue[sequence:, value:]
|
|
182
|
+
emit_value(sequence, value)
|
|
183
|
+
in ResultDone
|
|
169
184
|
complete
|
|
170
|
-
|
|
171
|
-
fail_with_ordered_error(
|
|
185
|
+
in ResultError[sequence:, error:]
|
|
186
|
+
fail_with_ordered_error(sequence, error)
|
|
172
187
|
end
|
|
173
188
|
end
|
|
174
189
|
|
|
175
|
-
def emit_value(
|
|
176
|
-
sequence = message.fetch(1)
|
|
177
|
-
value = message.fetch(2)
|
|
190
|
+
def emit_value(sequence, value)
|
|
178
191
|
@next_emit_sequence = sequence + 1
|
|
179
192
|
@in_flight -= 1 if @in_flight.positive?
|
|
180
193
|
value
|
|
181
194
|
end
|
|
182
195
|
|
|
183
|
-
def fail_with_ordered_error(
|
|
184
|
-
sequence = message.fetch(1)
|
|
185
|
-
error = message.fetch(2)
|
|
186
|
-
|
|
196
|
+
def fail_with_ordered_error(sequence, error)
|
|
187
197
|
if @failure_sequence && sequence > @failure_sequence
|
|
188
198
|
@next_emit_sequence = sequence + 1
|
|
189
199
|
@in_flight -= 1 if @in_flight.positive?
|
|
@@ -207,14 +217,14 @@ module FiberStream
|
|
|
207
217
|
end
|
|
208
218
|
|
|
209
219
|
def record_result(message)
|
|
210
|
-
if message.
|
|
211
|
-
sequence = message.
|
|
220
|
+
if message.is_a?(ResultError)
|
|
221
|
+
sequence = message.sequence
|
|
212
222
|
@failure_sequence = sequence if @failure_sequence.nil? || sequence < @failure_sequence
|
|
213
223
|
close_admission
|
|
214
224
|
request_worker_shutdown
|
|
215
225
|
end
|
|
216
226
|
|
|
217
|
-
@pending[message.
|
|
227
|
+
@pending[message.sequence] = message
|
|
218
228
|
end
|
|
219
229
|
|
|
220
230
|
def drain_available_results
|
|
@@ -282,39 +292,39 @@ module FiberStream
|
|
|
282
292
|
end
|
|
283
293
|
|
|
284
294
|
def handle_worker_message(message, live_workers)
|
|
285
|
-
case message
|
|
286
|
-
|
|
287
|
-
deliver_ready_worker(
|
|
295
|
+
case message
|
|
296
|
+
in Ready[worker_id]
|
|
297
|
+
deliver_ready_worker(worker_id)
|
|
288
298
|
0
|
|
289
|
-
|
|
299
|
+
in WorkerValue
|
|
290
300
|
handle_worker_value_message(message)
|
|
291
301
|
0
|
|
292
|
-
|
|
302
|
+
in WorkerFailure
|
|
293
303
|
handle_worker_error_message(message)
|
|
294
304
|
0
|
|
295
|
-
|
|
305
|
+
in Stopped
|
|
296
306
|
handle_worker_stopped_message(message, live_workers)
|
|
297
307
|
end
|
|
298
308
|
end
|
|
299
309
|
|
|
300
310
|
def handle_worker_value_message(message)
|
|
301
|
-
worker = worker_for_id(message.
|
|
302
|
-
sequence = message.
|
|
303
|
-
value = message.
|
|
311
|
+
worker = worker_for_id(message.worker_id)
|
|
312
|
+
sequence = message.sequence
|
|
313
|
+
value = message.value
|
|
304
314
|
|
|
305
315
|
clear_worker_job(worker)
|
|
306
|
-
deliver_result(
|
|
316
|
+
deliver_result(ResultValue.new(sequence:, value:))
|
|
307
317
|
end
|
|
308
318
|
|
|
309
319
|
def handle_worker_error_message(message)
|
|
310
|
-
worker = worker_for_id(message.
|
|
320
|
+
worker = worker_for_id(message.worker_id)
|
|
311
321
|
|
|
312
322
|
clear_worker_job(worker)
|
|
313
323
|
deliver_result(normalize_worker_error_message(message))
|
|
314
324
|
end
|
|
315
325
|
|
|
316
326
|
def handle_worker_stopped_message(message, live_workers)
|
|
317
|
-
worker = worker_for_id(message.
|
|
327
|
+
worker = worker_for_id(message.worker_id)
|
|
318
328
|
live_workers.delete(worker)
|
|
319
329
|
sequence = clear_worker_job(worker)
|
|
320
330
|
deliver_worker_termination_error(worker, sequence) if sequence && !@closed && !@worker_shutdown_sent
|
|
@@ -339,38 +349,34 @@ module FiberStream
|
|
|
339
349
|
cause: cause
|
|
340
350
|
)
|
|
341
351
|
|
|
342
|
-
deliver_result(
|
|
352
|
+
deliver_result(ResultError.new(sequence:, error:))
|
|
343
353
|
end
|
|
344
354
|
|
|
345
355
|
def deliver_ready_worker(worker_id)
|
|
346
356
|
return if @closed
|
|
347
357
|
|
|
348
|
-
push_until_delivered_or_closed(@ready_workers, worker_for_id(worker_id)
|
|
358
|
+
push_until_delivered_or_closed(@ready_workers, worker_for_id(worker_id))
|
|
349
359
|
end
|
|
350
360
|
|
|
351
361
|
def deliver_result(message)
|
|
352
362
|
return if @closed
|
|
353
363
|
|
|
354
|
-
push_until_delivered_or_closed(@results, message
|
|
364
|
+
push_until_delivered_or_closed(@results, message)
|
|
355
365
|
end
|
|
356
366
|
|
|
357
|
-
def push_until_delivered_or_closed(queue, message
|
|
358
|
-
|
|
359
|
-
return if @closed && suppress_data
|
|
360
|
-
return if @closed && !suppress_data
|
|
367
|
+
def push_until_delivered_or_closed(queue, message)
|
|
368
|
+
return if @closed
|
|
361
369
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
sleep READY_WAIT_INTERVAL
|
|
366
|
-
end
|
|
370
|
+
queue.push(message)
|
|
371
|
+
rescue ThreadError, ClosedQueueError
|
|
372
|
+
nil
|
|
367
373
|
end
|
|
368
374
|
|
|
369
375
|
def normalize_worker_error_message(message)
|
|
370
|
-
sequence = message.
|
|
371
|
-
kind = message.
|
|
372
|
-
cause_class_name = message.
|
|
373
|
-
cause_message = message.
|
|
376
|
+
sequence = message.sequence
|
|
377
|
+
kind = message.kind
|
|
378
|
+
cause_class_name = message.cause_class_name
|
|
379
|
+
cause_message = message.cause_message
|
|
374
380
|
error =
|
|
375
381
|
RactorMapError.new(
|
|
376
382
|
sequence: sequence,
|
|
@@ -379,7 +385,7 @@ module FiberStream
|
|
|
379
385
|
cause_message: cause_message
|
|
380
386
|
)
|
|
381
387
|
|
|
382
|
-
|
|
388
|
+
ResultError.new(sequence:, error:)
|
|
383
389
|
end
|
|
384
390
|
|
|
385
391
|
def worker_for_id(worker_id)
|
|
@@ -411,7 +417,7 @@ module FiberStream
|
|
|
411
417
|
|
|
412
418
|
@worker_shutdown_sent = true
|
|
413
419
|
@workers.each do |worker|
|
|
414
|
-
worker.send(
|
|
420
|
+
worker.send(Shutdown.new)
|
|
415
421
|
rescue StandardError
|
|
416
422
|
nil
|
|
417
423
|
end
|
|
@@ -420,7 +426,6 @@ module FiberStream
|
|
|
420
426
|
def wait_for_workers
|
|
421
427
|
return unless @coordinator
|
|
422
428
|
|
|
423
|
-
sleep READY_WAIT_INTERVAL while @coordinator.alive?
|
|
424
429
|
@coordinator.join
|
|
425
430
|
end
|
|
426
431
|
|
|
@@ -453,48 +458,66 @@ module FiberStream
|
|
|
453
458
|
)
|
|
454
459
|
end
|
|
455
460
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
461
|
+
class << self
|
|
462
|
+
def spawn_worker(worker_id, result_port, transform, output_transfer) # :nodoc:
|
|
463
|
+
Ractor.new(worker_id, result_port, transform, output_transfer) do |id, port, mapper, transfer|
|
|
464
|
+
current_sequence = nil
|
|
465
|
+
send_control =
|
|
466
|
+
lambda do |message|
|
|
467
|
+
port.send(message)
|
|
468
|
+
true
|
|
469
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
470
|
+
false
|
|
471
|
+
end
|
|
472
|
+
send_failure =
|
|
473
|
+
lambda do |sequence, kind, error|
|
|
474
|
+
send_control.call(WorkerFailure.new(id, sequence, kind, error.class.name, error.message))
|
|
475
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
476
|
+
false
|
|
477
|
+
end
|
|
462
478
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
479
|
+
begin
|
|
480
|
+
if send_control.call(Ready.new(id))
|
|
481
|
+
loop do
|
|
482
|
+
message = Ractor.receive
|
|
483
|
+
case message
|
|
484
|
+
in Shutdown
|
|
485
|
+
break
|
|
486
|
+
in Job[sequence, value]
|
|
487
|
+
current_sequence = sequence
|
|
488
|
+
else
|
|
489
|
+
raise TypeError, "invalid ractor_map worker message: #{message.class}"
|
|
490
|
+
end
|
|
466
491
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
472
|
-
port.send([:error, id, current_sequence, :worker, error.class.name, error.message])
|
|
473
|
-
else
|
|
474
|
-
begin
|
|
475
|
-
if transfer == :move
|
|
476
|
-
port.send([:value, id, current_sequence, mapped_value], move: true)
|
|
492
|
+
begin
|
|
493
|
+
mapped_value = mapper.call(value)
|
|
494
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
495
|
+
break unless send_failure.call(current_sequence, :worker, error)
|
|
477
496
|
else
|
|
478
|
-
|
|
497
|
+
begin
|
|
498
|
+
if transfer == :move
|
|
499
|
+
port.send(WorkerValue.new(id, current_sequence, mapped_value), move: true)
|
|
500
|
+
else
|
|
501
|
+
port.send(WorkerValue.new(id, current_sequence, mapped_value))
|
|
502
|
+
end
|
|
503
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
504
|
+
break unless send_failure.call(current_sequence, :output_transfer, error)
|
|
505
|
+
end
|
|
479
506
|
end
|
|
480
|
-
|
|
481
|
-
|
|
507
|
+
|
|
508
|
+
current_sequence = nil
|
|
509
|
+
break unless send_control.call(Ready.new(id))
|
|
482
510
|
end
|
|
483
511
|
end
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
512
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
513
|
+
sequence = current_sequence || -1
|
|
514
|
+
send_failure.call(sequence, :worker_termination, error)
|
|
515
|
+
ensure
|
|
516
|
+
send_control.call(Stopped.new(id))
|
|
487
517
|
end
|
|
488
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
489
|
-
sequence = current_sequence || -1
|
|
490
|
-
port.send([:error, id, sequence, :worker_termination, error.class.name, error.message])
|
|
491
|
-
ensure
|
|
492
|
-
port.send([:stopped, id])
|
|
493
518
|
end
|
|
494
519
|
end
|
|
495
520
|
end
|
|
496
|
-
|
|
497
|
-
private_class_method :spawn_worker
|
|
498
521
|
end
|
|
499
522
|
end
|
|
500
523
|
end
|