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
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FiberStream
|
|
4
|
+
module Pull
|
|
5
|
+
# Pull stream for `Source.ractor_merge_ports`.
|
|
6
|
+
#
|
|
7
|
+
# A coordinator thread owns blocking Ractor waits and forwards producer
|
|
8
|
+
# messages through a bounded result mailbox. Each producer receives at most
|
|
9
|
+
# one outstanding ack, and downstream demand replenishes only the producer
|
|
10
|
+
# that emitted the previous value.
|
|
11
|
+
class RactorMergePortsSource
|
|
12
|
+
PortPair = Data.define(:side, :port, :ack_port, :producer_ractor)
|
|
13
|
+
StartCommand = Data.define
|
|
14
|
+
RequestAckCommand = Data.define(:side)
|
|
15
|
+
ShutdownCommand = Data.define
|
|
16
|
+
ValueResult = Data.define(:side, :value)
|
|
17
|
+
DoneResult = Data.define(:side)
|
|
18
|
+
ErrorResult = Data.define(:side, :error)
|
|
19
|
+
private_constant :PortPair, :StartCommand, :RequestAckCommand, :ShutdownCommand
|
|
20
|
+
private_constant :ValueResult, :DoneResult, :ErrorResult
|
|
21
|
+
|
|
22
|
+
def initialize(port_pairs, ack_transfer, cancel)
|
|
23
|
+
@pairs = port_pairs.each_with_index.map do |pair, side|
|
|
24
|
+
producer_ractor = pair.respond_to?(:producer_ractor) ? pair.producer_ractor : nil
|
|
25
|
+
PortPair.new(side:, port: pair.port, ack_port: pair.ack_port, producer_ractor:)
|
|
26
|
+
end.freeze
|
|
27
|
+
@ack_transfer = ack_transfer
|
|
28
|
+
@cancel_enabled = cancel
|
|
29
|
+
@result_mailbox = RactorMergeResultMailbox.new(@pairs.size)
|
|
30
|
+
@control_port = nil
|
|
31
|
+
@coordinator = nil
|
|
32
|
+
@state_mutex = Mutex.new
|
|
33
|
+
@producer_terminal = @pairs.to_h { |pair| [pair.side, false] }
|
|
34
|
+
@side_done = @pairs.to_h { |pair| [pair.side, false] }
|
|
35
|
+
@cancel_sent = @pairs.to_h { |pair| [pair.side, false] }
|
|
36
|
+
@pending_ack_sides = {}
|
|
37
|
+
@started = false
|
|
38
|
+
@closed = false
|
|
39
|
+
@done = false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def next
|
|
43
|
+
return DONE if closed_or_done?
|
|
44
|
+
|
|
45
|
+
start
|
|
46
|
+
request_pending_acks
|
|
47
|
+
next_result
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def close
|
|
51
|
+
started = mark_closed
|
|
52
|
+
return if started.nil?
|
|
53
|
+
return unless started
|
|
54
|
+
|
|
55
|
+
close_result_mailbox
|
|
56
|
+
wake_coordinator
|
|
57
|
+
wait_for_coordinator
|
|
58
|
+
cancel_error = cancel_non_terminal_producers
|
|
59
|
+
raise cancel_error if cancel_error
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def closed_or_done?
|
|
65
|
+
@state_mutex.synchronize { @closed || @done }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def mark_closed
|
|
69
|
+
already_closed = false
|
|
70
|
+
started = false
|
|
71
|
+
|
|
72
|
+
@state_mutex.synchronize do
|
|
73
|
+
already_closed = @closed
|
|
74
|
+
started = @started
|
|
75
|
+
@closed = true
|
|
76
|
+
@done = true
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
already_closed ? nil : started
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def start
|
|
83
|
+
started_now = false
|
|
84
|
+
|
|
85
|
+
@state_mutex.synchronize do
|
|
86
|
+
return if @started || @closed
|
|
87
|
+
|
|
88
|
+
@control_port = Ractor::Port.new
|
|
89
|
+
@started = true
|
|
90
|
+
@coordinator = Thread.new { run_coordinator }
|
|
91
|
+
started_now = true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
send_control_command(StartCommand.new) if started_now && !closed_or_done?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def request_pending_acks
|
|
98
|
+
sides =
|
|
99
|
+
@state_mutex.synchronize do
|
|
100
|
+
pending = @pending_ack_sides.keys
|
|
101
|
+
@pending_ack_sides.clear
|
|
102
|
+
pending
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
sides.each { |side| send_control_command(RequestAckCommand.new(side:)) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def next_result
|
|
109
|
+
loop do
|
|
110
|
+
result = @result_mailbox.pop
|
|
111
|
+
return complete if result.nil?
|
|
112
|
+
|
|
113
|
+
case result
|
|
114
|
+
in ValueResult[side:, value:]
|
|
115
|
+
record_pending_ack(side)
|
|
116
|
+
return value
|
|
117
|
+
in DoneResult[side:]
|
|
118
|
+
mark_side_done(side)
|
|
119
|
+
return complete if all_done?
|
|
120
|
+
in ErrorResult[error:]
|
|
121
|
+
mark_done
|
|
122
|
+
raise_error(error)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
rescue RactorMergeResultMailbox::Closed
|
|
126
|
+
complete
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def record_pending_ack(side)
|
|
130
|
+
@state_mutex.synchronize do
|
|
131
|
+
@pending_ack_sides[side] = true unless @closed || @producer_terminal.fetch(side)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def mark_side_done(side)
|
|
136
|
+
@state_mutex.synchronize { @side_done[side] = true }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def all_done?
|
|
140
|
+
@state_mutex.synchronize { @side_done.values.all? }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def mark_done
|
|
144
|
+
@state_mutex.synchronize { @done = true }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def complete
|
|
148
|
+
mark_done
|
|
149
|
+
DONE
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def run_coordinator
|
|
153
|
+
outstanding_ack = @pairs.to_h { |pair| [pair.side, false] }
|
|
154
|
+
active_ports = @pairs.map(&:port)
|
|
155
|
+
active_ractors = @pairs.filter_map(&:producer_ractor)
|
|
156
|
+
pair_by_port = @pairs.to_h { |pair| [pair.port, pair] }
|
|
157
|
+
pair_by_ractor =
|
|
158
|
+
@pairs.each_with_object({}) do |pair, pairs|
|
|
159
|
+
pairs[pair.producer_ractor] = pair if pair.producer_ractor
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
loop do
|
|
163
|
+
break if active_ports.empty?
|
|
164
|
+
|
|
165
|
+
selected, message = Ractor.select(@control_port, *active_ports, *active_ractors)
|
|
166
|
+
if selected == @control_port
|
|
167
|
+
break if handle_control_message(message, outstanding_ack)
|
|
168
|
+
elsif pair_by_ractor.key?(selected)
|
|
169
|
+
pair = pair_by_ractor.fetch(selected)
|
|
170
|
+
active_ractors.delete(selected)
|
|
171
|
+
case message
|
|
172
|
+
in ProducerTerminal | ProducerCancelled
|
|
173
|
+
next
|
|
174
|
+
else
|
|
175
|
+
deliver_result(ErrorResult.new(side: pair.side, error: Pull.ractor_producer_termination_error(message)))
|
|
176
|
+
end
|
|
177
|
+
else
|
|
178
|
+
pair = pair_by_port.fetch(selected)
|
|
179
|
+
outstanding_ack[pair.side] = false
|
|
180
|
+
result = build_result(pair, message)
|
|
181
|
+
active_ports.delete(pair.port) if producer_terminal?(pair.side)
|
|
182
|
+
deliver_result(result)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
rescue StandardError => error
|
|
186
|
+
deliver_result(ErrorResult.new(side: nil, error: build_error(:receive, error)))
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def handle_control_message(message, outstanding_ack)
|
|
190
|
+
case message
|
|
191
|
+
in StartCommand
|
|
192
|
+
@pairs.each { |pair| ack_pair(pair, outstanding_ack) }
|
|
193
|
+
false
|
|
194
|
+
in RequestAckCommand[side:]
|
|
195
|
+
pair = @pairs.fetch(side)
|
|
196
|
+
ack_pair(pair, outstanding_ack)
|
|
197
|
+
false
|
|
198
|
+
in ShutdownCommand
|
|
199
|
+
true
|
|
200
|
+
else
|
|
201
|
+
false
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def ack_pair(pair, outstanding_ack)
|
|
206
|
+
return if outstanding_ack.fetch(pair.side)
|
|
207
|
+
return if producer_terminal?(pair.side)
|
|
208
|
+
|
|
209
|
+
ack_error = send_ack(pair)
|
|
210
|
+
if ack_error
|
|
211
|
+
deliver_result(ErrorResult.new(side: pair.side, error: ack_error))
|
|
212
|
+
else
|
|
213
|
+
outstanding_ack[pair.side] = true
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def send_ack(pair)
|
|
218
|
+
send_control(pair.ack_port, RactorPort::Ack.new)
|
|
219
|
+
nil
|
|
220
|
+
rescue StandardError => error
|
|
221
|
+
build_error(:ack_transfer, error)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def build_result(pair, message)
|
|
225
|
+
side = pair.side
|
|
226
|
+
|
|
227
|
+
case message
|
|
228
|
+
in RactorPort::Element[value]
|
|
229
|
+
ValueResult.new(side:, value:)
|
|
230
|
+
in RactorPort::Complete
|
|
231
|
+
mark_producer_terminal(side)
|
|
232
|
+
DoneResult.new(side:)
|
|
233
|
+
in RactorPort::Failure[String => cause_class_name, String => cause_message]
|
|
234
|
+
mark_producer_terminal(side)
|
|
235
|
+
ErrorResult.new(
|
|
236
|
+
side:,
|
|
237
|
+
error: RactorPortSourceError.new(
|
|
238
|
+
kind: :producer_failure,
|
|
239
|
+
cause_class_name: cause_class_name,
|
|
240
|
+
cause_message: cause_message
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
in RactorPort::Failure
|
|
244
|
+
ErrorResult.new(side:, error: invalid_message_error(message, "Failure payloads must be Strings"))
|
|
245
|
+
else
|
|
246
|
+
ErrorResult.new(side:, error: invalid_message_error(message, "invalid RactorPort message"))
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def invalid_message_error(message, cause_message)
|
|
251
|
+
RactorPortSourceError.new(
|
|
252
|
+
kind: :invalid_message,
|
|
253
|
+
cause_class_name: message.class.name,
|
|
254
|
+
cause_message: cause_message
|
|
255
|
+
)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def build_error(kind, error)
|
|
259
|
+
RactorPortSourceError.new(
|
|
260
|
+
kind: kind,
|
|
261
|
+
cause_class_name: error.class.name,
|
|
262
|
+
cause_message: error.message,
|
|
263
|
+
cause: error
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def mark_producer_terminal(side)
|
|
268
|
+
@state_mutex.synchronize { @producer_terminal[side] = true }
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def producer_terminal?(side)
|
|
272
|
+
@state_mutex.synchronize { @producer_terminal.fetch(side) }
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def cancel_non_terminal_producers
|
|
276
|
+
first_error = nil
|
|
277
|
+
|
|
278
|
+
@pairs.each do |pair|
|
|
279
|
+
next unless should_cancel_pair?(pair)
|
|
280
|
+
|
|
281
|
+
cancel_error = send_cancel(pair)
|
|
282
|
+
first_error ||= cancel_error
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
first_error
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def should_cancel_pair?(pair)
|
|
289
|
+
@state_mutex.synchronize do
|
|
290
|
+
return false unless @cancel_enabled
|
|
291
|
+
return false if @producer_terminal.fetch(pair.side)
|
|
292
|
+
return false if @cancel_sent.fetch(pair.side)
|
|
293
|
+
|
|
294
|
+
@cancel_sent[pair.side] = true
|
|
295
|
+
true
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def send_cancel(pair)
|
|
300
|
+
send_control(pair.ack_port, RactorPort::Cancel.new(:closed))
|
|
301
|
+
nil
|
|
302
|
+
rescue StandardError => error
|
|
303
|
+
build_error(:cancel_transfer, error)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def send_control(port, message)
|
|
307
|
+
if @ack_transfer == :move
|
|
308
|
+
port.send(message, move: true)
|
|
309
|
+
else
|
|
310
|
+
port.send(message)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def raise_error(error)
|
|
315
|
+
if error.is_a?(RactorPortSourceError) && error.original_cause
|
|
316
|
+
raise error, cause: error.original_cause
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
raise error
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def deliver_result(result)
|
|
323
|
+
@result_mailbox.push(result)
|
|
324
|
+
rescue RactorMergeResultMailbox::Closed
|
|
325
|
+
nil
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def send_control_command(command)
|
|
329
|
+
@control_port&.send(command)
|
|
330
|
+
rescue StandardError
|
|
331
|
+
nil
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def wake_coordinator
|
|
335
|
+
send_control_command(ShutdownCommand.new)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def wait_for_coordinator
|
|
339
|
+
return unless @coordinator
|
|
340
|
+
|
|
341
|
+
@coordinator.join
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def close_result_mailbox
|
|
345
|
+
@result_mailbox.close
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
class RactorMergeResultMailbox
|
|
349
|
+
Closed = Class.new(StandardError)
|
|
350
|
+
|
|
351
|
+
def initialize(capacity)
|
|
352
|
+
@queue = Thread::SizedQueue.new(capacity)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def push(result)
|
|
356
|
+
@queue << result
|
|
357
|
+
rescue ClosedQueueError
|
|
358
|
+
raise Closed
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def pop
|
|
362
|
+
@queue.pop
|
|
363
|
+
rescue ClosedQueueError
|
|
364
|
+
raise Closed
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def close
|
|
368
|
+
@queue.close
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
@@ -9,13 +9,21 @@ module FiberStream
|
|
|
9
9
|
# a coordinator thread so scheduler-managed fibers do not call Ractor wait
|
|
10
10
|
# APIs directly.
|
|
11
11
|
class RactorPortSource
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
ProtocolMessage = Data.define(:message)
|
|
13
|
+
ErrorMessage = Data.define(:error)
|
|
14
|
+
ClosedMessage = Data.define
|
|
15
|
+
SelectedProtocol = Data.define(:message)
|
|
16
|
+
SelectedShutdown = Data.define
|
|
17
|
+
SelectedProducerTerminated = Data.define(:result)
|
|
18
|
+
private_constant :ProtocolMessage, :ErrorMessage, :ClosedMessage
|
|
19
|
+
private_constant :SelectedProtocol, :SelectedShutdown, :SelectedProducerTerminated
|
|
20
|
+
|
|
21
|
+
def initialize(port, ack_port, ack_transfer, cancel, producer_ractor = nil)
|
|
15
22
|
@port = port
|
|
16
23
|
@ack_port = ack_port
|
|
17
24
|
@ack_transfer = ack_transfer
|
|
18
25
|
@cancel_enabled = cancel
|
|
26
|
+
@producer_ractor = producer_ractor
|
|
19
27
|
@demands = Thread::SizedQueue.new(1)
|
|
20
28
|
@results = Thread::SizedQueue.new(1)
|
|
21
29
|
@shutdown_port = nil
|
|
@@ -25,6 +33,7 @@ module FiberStream
|
|
|
25
33
|
@closed = false
|
|
26
34
|
@done = false
|
|
27
35
|
@producer_terminal = false
|
|
36
|
+
@producer_ractor_terminal = false
|
|
28
37
|
@cancel_sent = false
|
|
29
38
|
end
|
|
30
39
|
|
|
@@ -81,15 +90,13 @@ module FiberStream
|
|
|
81
90
|
end
|
|
82
91
|
|
|
83
92
|
def handle_result(result)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
handle_protocol_message(result.fetch(1))
|
|
89
|
-
when :error
|
|
93
|
+
case result
|
|
94
|
+
in ProtocolMessage[message:]
|
|
95
|
+
handle_protocol_message(message)
|
|
96
|
+
in ErrorMessage[error:]
|
|
90
97
|
mark_done
|
|
91
|
-
raise_error(
|
|
92
|
-
|
|
98
|
+
raise_error(error)
|
|
99
|
+
in ClosedMessage
|
|
93
100
|
DONE
|
|
94
101
|
end
|
|
95
102
|
end
|
|
@@ -151,23 +158,50 @@ module FiberStream
|
|
|
151
158
|
|
|
152
159
|
ack_error = send_ack
|
|
153
160
|
if ack_error
|
|
154
|
-
deliver_result(
|
|
161
|
+
deliver_result(ErrorMessage.new(error: ack_error))
|
|
155
162
|
break
|
|
156
163
|
end
|
|
157
164
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
case select_message
|
|
166
|
+
in SelectedShutdown
|
|
167
|
+
break
|
|
168
|
+
in SelectedProducerTerminated[result:]
|
|
169
|
+
deliver_result(ErrorMessage.new(error: Pull.ractor_producer_termination_error(result)))
|
|
170
|
+
break
|
|
171
|
+
in SelectedProtocol[message:]
|
|
172
|
+
deliver_result(ProtocolMessage.new(message:))
|
|
173
|
+
end
|
|
162
174
|
end
|
|
163
175
|
rescue StandardError => error
|
|
164
|
-
deliver_result(
|
|
176
|
+
deliver_result(ErrorMessage.new(error: build_error(:receive, error)))
|
|
165
177
|
ensure
|
|
166
|
-
deliver_result(
|
|
178
|
+
deliver_result(ClosedMessage.new) if closed?
|
|
167
179
|
end
|
|
168
180
|
|
|
169
181
|
def select_message
|
|
170
|
-
|
|
182
|
+
loop do
|
|
183
|
+
selected, message =
|
|
184
|
+
if @producer_ractor && !@producer_ractor_terminal
|
|
185
|
+
Ractor.select(@port, @shutdown_port, @producer_ractor)
|
|
186
|
+
else
|
|
187
|
+
Ractor.select(@port, @shutdown_port)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
if selected == @producer_ractor
|
|
191
|
+
@producer_ractor_terminal = true
|
|
192
|
+
case message
|
|
193
|
+
in ProducerTerminal | ProducerCancelled
|
|
194
|
+
next
|
|
195
|
+
else
|
|
196
|
+
return SelectedProducerTerminated.new(result: message)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
return SelectedShutdown.new if selected == @shutdown_port || closed?
|
|
202
|
+
|
|
203
|
+
return SelectedProtocol.new(message:)
|
|
204
|
+
end
|
|
171
205
|
end
|
|
172
206
|
|
|
173
207
|
def send_ack
|
|
@@ -218,7 +252,6 @@ module FiberStream
|
|
|
218
252
|
def wait_for_coordinator
|
|
219
253
|
return unless @coordinator
|
|
220
254
|
|
|
221
|
-
sleep WAIT_INTERVAL while @coordinator.alive?
|
|
222
255
|
@coordinator.join
|
|
223
256
|
end
|
|
224
257
|
|