cdc-parallel 0.2.3 → 0.2.4
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 +12 -1
- data/lib/cdc/parallel/configuration.rb +5 -1
- data/lib/cdc/parallel/processor_pool.rb +348 -37
- data/lib/cdc/parallel/result_collector.rb +6 -0
- data/lib/cdc/parallel/runtime.rb +71 -4
- data/lib/cdc/parallel/transaction_pool.rb +33 -2
- data/lib/cdc/parallel/version.rb +1 -1
- data/lib/cdc/parallel.rb +6 -0
- data/sig/cdc/parallel/configuration.rbs +7 -1
- data/sig/cdc/parallel/processor_pool.rbs +94 -23
- data/sig/cdc/parallel/runtime.rbs +35 -7
- data/sig/cdc/parallel/transaction_pool.rbs +13 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e6e397f5b9c12042cf5521d0cc689604560c0a4180a9a53911915ebc38d31e27
|
|
4
|
+
data.tar.gz: a7736d3c1958ca24183939c4221fa9812a558fab0daab814988d2ba225afc4aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 75fbe128470e921d71ccb21f98969751379bf63a2b2fc8e1fae9fb45d5557ce281a025e6c67e4a02a3438300380671d76812535c8fe9b271225b7356605e8b6f
|
|
7
|
+
data.tar.gz: 46960241a8cdc582a26c63c6725e2bae4cce316c3206019e03c3123ed178b891206e30a883b2932e5984f8a03822b640dea6f7672ff54747d77fbc570438be3d
|
data/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,18 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## [0.2.4] - 2026-06-28
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Started `Runtime` processors before internal pools shareablize the processor object graph.
|
|
14
|
+
- Forwarded `Runtime` supervisor tuning options to both event and transaction pools.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Added fatal-worker supervisor regression tests for respawn, degraded cooldown, and shutdown behavior.
|
|
19
|
+
- Added runtime regression tests for lifecycle ordering and supervisor option forwarding.
|
|
20
|
+
- Updated RBS signatures and supervisor plan documentation for the new runtime contract.
|
|
10
21
|
|
|
11
22
|
## [0.2.3] - 2026-06-03
|
|
12
23
|
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module CDC
|
|
4
4
|
module Parallel
|
|
5
|
+
# :nodoc:
|
|
6
|
+
ConfigurationData = Data.define(:size, :timeout)
|
|
7
|
+
private_constant :ConfigurationData
|
|
8
|
+
|
|
5
9
|
# Immutable configuration shared by cdc-parallel runtime objects.
|
|
6
10
|
#
|
|
7
11
|
# `Configuration` validates worker sizing and timeout values at construction
|
|
@@ -22,7 +26,7 @@ module CDC
|
|
|
22
26
|
# @!attribute [r] timeout
|
|
23
27
|
# @return [Numeric, nil] Optional wait timeout in seconds.
|
|
24
28
|
# @api public
|
|
25
|
-
class Configuration <
|
|
29
|
+
class Configuration < ConfigurationData
|
|
26
30
|
# Create a validated runtime configuration.
|
|
27
31
|
#
|
|
28
32
|
# @param size [Integer]
|
|
@@ -109,22 +109,70 @@ module CDC
|
|
|
109
109
|
# @raise [ArgumentError]
|
|
110
110
|
# Raised by {Configuration} when `size` or `timeout` is invalid.
|
|
111
111
|
# @return [void]
|
|
112
|
-
|
|
112
|
+
# @param supervision [Boolean]
|
|
113
|
+
# Whether worker Ractors should be respawned after unexpected death.
|
|
114
|
+
# @param max_respawns [Integer]
|
|
115
|
+
# Maximum crash count inside `respawn_window` before a worker slot enters
|
|
116
|
+
# cooldown.
|
|
117
|
+
# @param respawn_window [Numeric]
|
|
118
|
+
# Time window, in seconds, used by the crash-loop circuit breaker.
|
|
119
|
+
# @param respawn_cooldown [Numeric]
|
|
120
|
+
# Cooldown, in seconds, before a crash-looping slot is revived again.
|
|
121
|
+
# @param manage_lifecycle [Boolean]
|
|
122
|
+
# When `true` (default), the pool calls `processor.start` during
|
|
123
|
+
# initialization and `processor.flush` + `processor.stop` during shutdown.
|
|
124
|
+
# Set to `false` when a higher-level runtime (e.g. {Runtime}) owns the
|
|
125
|
+
# processor lifecycle so that `start`/`stop`/`flush` are not called
|
|
126
|
+
# multiple times when the same processor is shared across pools.
|
|
127
|
+
# rubocop:disable Metrics/MethodLength
|
|
128
|
+
def initialize(
|
|
129
|
+
processor:,
|
|
130
|
+
size: Etc.nprocessors,
|
|
131
|
+
timeout: nil,
|
|
132
|
+
supervision: true,
|
|
133
|
+
max_respawns: 3,
|
|
134
|
+
respawn_window: 60,
|
|
135
|
+
respawn_cooldown: 5,
|
|
136
|
+
manage_lifecycle: true
|
|
137
|
+
)
|
|
113
138
|
validate_processor!(processor)
|
|
114
139
|
|
|
140
|
+
processor.start if manage_lifecycle
|
|
115
141
|
@processor = ::Ractor.make_shareable(processor)
|
|
142
|
+
@manage_lifecycle = manage_lifecycle
|
|
116
143
|
@configuration = Configuration.new(size:, timeout:)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
@slots = Array.new(@configuration.size) do |index|
|
|
145
|
+
WorkerSlot.new(
|
|
146
|
+
index:,
|
|
147
|
+
processor: @processor,
|
|
148
|
+
supervision:,
|
|
149
|
+
max_respawns:,
|
|
150
|
+
respawn_window:,
|
|
151
|
+
respawn_cooldown:
|
|
152
|
+
)
|
|
153
|
+
end.freeze
|
|
154
|
+
@workers = @slots.map(&:worker).freeze
|
|
155
|
+
@worker_inboxes = @slots.map(&:inbox).freeze
|
|
123
156
|
|
|
124
157
|
@next_worker = 0
|
|
125
158
|
@dispatch_mutex = Mutex.new
|
|
126
159
|
@shutdown = false
|
|
127
160
|
end
|
|
161
|
+
# rubocop:enable Metrics/MethodLength
|
|
162
|
+
|
|
163
|
+
# Return total worker-slot respawns since this pool booted.
|
|
164
|
+
#
|
|
165
|
+
# @return [Integer]
|
|
166
|
+
def respawns
|
|
167
|
+
@slots.sum(&:respawns)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Return whether any worker slot is currently in crash-loop cooldown.
|
|
171
|
+
#
|
|
172
|
+
# @return [Boolean]
|
|
173
|
+
def degraded?
|
|
174
|
+
@slots.any?(&:degraded?)
|
|
175
|
+
end
|
|
128
176
|
|
|
129
177
|
# Process one work item synchronously.
|
|
130
178
|
#
|
|
@@ -161,9 +209,9 @@ module CDC
|
|
|
161
209
|
work_items = items.map { |item| ::Ractor.make_shareable(item) }
|
|
162
210
|
reply_port = ::Ractor::Port.new
|
|
163
211
|
|
|
164
|
-
dispatch(work_items, reply_port)
|
|
212
|
+
assignments = dispatch(work_items, reply_port)
|
|
165
213
|
|
|
166
|
-
collect_results(reply_port, work_items.length)
|
|
214
|
+
collect_results(reply_port, work_items.length, assignments).compact.freeze
|
|
167
215
|
ensure
|
|
168
216
|
reply_port&.close
|
|
169
217
|
end
|
|
@@ -184,6 +232,10 @@ module CDC
|
|
|
184
232
|
end
|
|
185
233
|
|
|
186
234
|
wait_for_workers
|
|
235
|
+
return unless @manage_lifecycle
|
|
236
|
+
|
|
237
|
+
@processor.flush
|
|
238
|
+
@processor.stop
|
|
187
239
|
end
|
|
188
240
|
|
|
189
241
|
private
|
|
@@ -192,25 +244,25 @@ module CDC
|
|
|
192
244
|
@dispatch_mutex.synchronize do
|
|
193
245
|
raise ShutdownError, "processor pool has been shut down" if @shutdown
|
|
194
246
|
|
|
247
|
+
assignments = Array.new(work_items.length)
|
|
195
248
|
work_items.each_with_index do |item, index|
|
|
196
|
-
|
|
249
|
+
slot = next_worker_slot
|
|
250
|
+
assignments[index] = slot
|
|
251
|
+
slot.send_work(index, item, reply_port)
|
|
197
252
|
end
|
|
253
|
+
assignments
|
|
198
254
|
end
|
|
199
255
|
end
|
|
200
256
|
|
|
201
257
|
def signal_workers
|
|
202
|
-
@
|
|
203
|
-
inbox.send(nil)
|
|
204
|
-
rescue Ractor::ClosedError
|
|
205
|
-
# Already stopped.
|
|
206
|
-
end
|
|
258
|
+
@slots.each(&:shutdown)
|
|
207
259
|
end
|
|
208
260
|
|
|
209
261
|
def wait_for_workers
|
|
210
262
|
if @configuration.timeout
|
|
211
263
|
wait_for_workers_with_timeout
|
|
212
264
|
else
|
|
213
|
-
@
|
|
265
|
+
@slots.each(&:join)
|
|
214
266
|
end
|
|
215
267
|
end
|
|
216
268
|
|
|
@@ -220,11 +272,11 @@ module CDC
|
|
|
220
272
|
|
|
221
273
|
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
222
274
|
|
|
223
|
-
@
|
|
275
|
+
@slots.each do |slot|
|
|
224
276
|
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
225
277
|
break unless remaining.positive?
|
|
226
278
|
|
|
227
|
-
::Timeout.timeout(remaining, TimeoutError) {
|
|
279
|
+
::Timeout.timeout(remaining, TimeoutError) { slot.join }
|
|
228
280
|
rescue TimeoutError
|
|
229
281
|
break
|
|
230
282
|
end
|
|
@@ -238,16 +290,7 @@ module CDC
|
|
|
238
290
|
"#{processor.class} must declare ractor_safe!"
|
|
239
291
|
end
|
|
240
292
|
|
|
241
|
-
def
|
|
242
|
-
boot_port = ::Ractor::Port.new
|
|
243
|
-
worker = start_worker(processor, boot_port)
|
|
244
|
-
|
|
245
|
-
[worker, boot_port.receive]
|
|
246
|
-
ensure
|
|
247
|
-
boot_port.close
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
def start_worker(processor, boot_port)
|
|
293
|
+
def self.start_worker(processor, boot_port)
|
|
251
294
|
::Ractor.new(processor, boot_port) do |safe_processor, ready_port|
|
|
252
295
|
inbox = ::Ractor::Port.new
|
|
253
296
|
ready_port << inbox
|
|
@@ -255,6 +298,7 @@ module CDC
|
|
|
255
298
|
CDC::Parallel::ProcessorPool.send(:run_worker_loop, safe_processor, inbox)
|
|
256
299
|
end
|
|
257
300
|
end
|
|
301
|
+
private_class_method :start_worker
|
|
258
302
|
|
|
259
303
|
def self.run_worker_loop(safe_processor, inbox)
|
|
260
304
|
loop do
|
|
@@ -282,36 +326,37 @@ module CDC
|
|
|
282
326
|
end
|
|
283
327
|
private_class_method :worker_response
|
|
284
328
|
|
|
285
|
-
def
|
|
286
|
-
|
|
329
|
+
def next_worker_slot
|
|
330
|
+
slot = @slots[@next_worker]
|
|
287
331
|
|
|
288
332
|
@next_worker += 1
|
|
289
|
-
@next_worker = 0 if @next_worker >= @
|
|
333
|
+
@next_worker = 0 if @next_worker >= @slots.length
|
|
290
334
|
|
|
291
|
-
|
|
335
|
+
slot
|
|
292
336
|
end
|
|
293
337
|
|
|
294
|
-
def collect_results(reply_port, count)
|
|
338
|
+
def collect_results(reply_port, count, assignments = [])
|
|
295
339
|
results = Array.new(count)
|
|
296
340
|
return results.freeze if count.zero?
|
|
297
341
|
|
|
298
342
|
if @configuration.timeout
|
|
299
|
-
collect_results_with_timeout(reply_port, results)
|
|
343
|
+
collect_results_with_timeout(reply_port, results, assignments)
|
|
300
344
|
else
|
|
301
|
-
collect_results_without_timeout(reply_port, results)
|
|
345
|
+
collect_results_without_timeout(reply_port, results, assignments)
|
|
302
346
|
end
|
|
303
347
|
end
|
|
304
348
|
|
|
305
|
-
def collect_results_without_timeout(reply_port, results)
|
|
349
|
+
def collect_results_without_timeout(reply_port, results, assignments = [])
|
|
306
350
|
results.length.times do
|
|
307
351
|
index, response = reply_port.receive
|
|
308
352
|
results[index] = ResultCollector.normalize(response)
|
|
353
|
+
assignments[index]&.complete(index)
|
|
309
354
|
end
|
|
310
355
|
|
|
311
356
|
results.freeze
|
|
312
357
|
end
|
|
313
358
|
|
|
314
|
-
def collect_results_with_timeout(reply_port, results)
|
|
359
|
+
def collect_results_with_timeout(reply_port, results, assignments = [])
|
|
315
360
|
timeout = @configuration.timeout
|
|
316
361
|
return results.freeze unless timeout
|
|
317
362
|
|
|
@@ -323,6 +368,7 @@ module CDC
|
|
|
323
368
|
|
|
324
369
|
index, response = ::Timeout.timeout(remaining, TimeoutError) { reply_port.receive }
|
|
325
370
|
results[index] = ResultCollector.normalize(response)
|
|
371
|
+
assignments[index]&.complete(index)
|
|
326
372
|
rescue TimeoutError
|
|
327
373
|
return timeout_results(results)
|
|
328
374
|
end
|
|
@@ -340,6 +386,271 @@ module CDC
|
|
|
340
386
|
result || CDC::Core::ProcessorResult.failure(timeout_error)
|
|
341
387
|
end.freeze
|
|
342
388
|
end
|
|
389
|
+
|
|
390
|
+
# One supervised worker position in the pool.
|
|
391
|
+
#
|
|
392
|
+
# A slot keeps its identity while its underlying Ractor can be replaced
|
|
393
|
+
# after unexpected death. The slot also owns in-flight reply ports so a
|
|
394
|
+
# fatal worker exit can be reported back to callers instead of leaving
|
|
395
|
+
# them blocked forever.
|
|
396
|
+
#
|
|
397
|
+
# @api private
|
|
398
|
+
# rubocop:disable Metrics/ClassLength
|
|
399
|
+
class WorkerSlot
|
|
400
|
+
# Number of times this slot has booted a replacement worker.
|
|
401
|
+
#
|
|
402
|
+
# @return [Integer]
|
|
403
|
+
# @api private
|
|
404
|
+
attr_reader :respawns
|
|
405
|
+
|
|
406
|
+
# Create a supervised worker slot.
|
|
407
|
+
#
|
|
408
|
+
# @param index [Integer]
|
|
409
|
+
# Stable slot index inside the owning pool.
|
|
410
|
+
# @param processor [CDC::Core::Processor]
|
|
411
|
+
# Shareable processor instance used by the worker.
|
|
412
|
+
# @param supervision [Boolean]
|
|
413
|
+
# Whether unexpected worker death should trigger respawn.
|
|
414
|
+
# @param max_respawns [Integer]
|
|
415
|
+
# Maximum crash count inside the respawn window before cooldown.
|
|
416
|
+
# @param respawn_window [Numeric]
|
|
417
|
+
# Sliding crash-loop window in seconds.
|
|
418
|
+
# @param respawn_cooldown [Numeric]
|
|
419
|
+
# Cooldown duration in seconds after repeated crashes.
|
|
420
|
+
# @return [void]
|
|
421
|
+
# @api private
|
|
422
|
+
def initialize(index:, processor:, supervision:, max_respawns:, respawn_window:, respawn_cooldown:)
|
|
423
|
+
@index = index
|
|
424
|
+
@processor = processor
|
|
425
|
+
@supervision = supervision
|
|
426
|
+
@max_respawns = Integer(max_respawns)
|
|
427
|
+
@respawn_window = Float(respawn_window)
|
|
428
|
+
@respawn_cooldown = Float(respawn_cooldown)
|
|
429
|
+
@lock = Mutex.new
|
|
430
|
+
@inflight = {}
|
|
431
|
+
@crashes = []
|
|
432
|
+
@respawns = 0
|
|
433
|
+
@shutdown = false
|
|
434
|
+
@degraded_until = nil
|
|
435
|
+
boot!
|
|
436
|
+
@supervisor_thread = Thread.new { supervise }
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Return the current worker Ractor.
|
|
440
|
+
#
|
|
441
|
+
# @return [Ractor]
|
|
442
|
+
# @api private
|
|
443
|
+
def worker
|
|
444
|
+
@lock.synchronize { @worker }
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# Return the current worker inbox port.
|
|
448
|
+
#
|
|
449
|
+
# @return [Ractor::Port]
|
|
450
|
+
# @api private
|
|
451
|
+
def inbox
|
|
452
|
+
@lock.synchronize { @inbox }
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Send one indexed work item to the current worker.
|
|
456
|
+
#
|
|
457
|
+
# If the slot is cooling down or the worker inbox is already closed, a
|
|
458
|
+
# serialized failure response is sent to the caller reply port.
|
|
459
|
+
#
|
|
460
|
+
# @param index [Integer]
|
|
461
|
+
# @param item [Object]
|
|
462
|
+
# @param reply_port [Ractor::Port]
|
|
463
|
+
# @return [void]
|
|
464
|
+
# @api private
|
|
465
|
+
def send_work(index, item, reply_port)
|
|
466
|
+
target = nil
|
|
467
|
+
immediate_failure = nil
|
|
468
|
+
|
|
469
|
+
@lock.synchronize do
|
|
470
|
+
if degraded_locked?
|
|
471
|
+
immediate_failure = RuntimeError.new("worker slot #{@index} is cooling down after repeated crashes")
|
|
472
|
+
else
|
|
473
|
+
@inflight[index] = reply_port
|
|
474
|
+
target = @inbox
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
return send_failure(reply_port, index, require_failure(immediate_failure)) if immediate_failure
|
|
479
|
+
|
|
480
|
+
send_to_inbox(target, [index, item, reply_port])
|
|
481
|
+
rescue Ractor::ClosedError => e
|
|
482
|
+
complete(index)
|
|
483
|
+
send_failure(reply_port, index, e)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Mark an in-flight work item as completed.
|
|
487
|
+
#
|
|
488
|
+
# @param index [Integer]
|
|
489
|
+
# @return [void]
|
|
490
|
+
# @api private
|
|
491
|
+
def complete(index)
|
|
492
|
+
@lock.synchronize { @inflight.delete(index) }
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Return whether this slot is currently in crash-loop cooldown.
|
|
496
|
+
#
|
|
497
|
+
# @return [Boolean]
|
|
498
|
+
# @api private
|
|
499
|
+
def degraded?
|
|
500
|
+
@lock.synchronize { degraded_locked? }
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# Return whether this slot is degraded while the caller holds the lock.
|
|
504
|
+
#
|
|
505
|
+
# @return [Boolean]
|
|
506
|
+
# @api private
|
|
507
|
+
def degraded_locked?
|
|
508
|
+
degraded_until = @degraded_until
|
|
509
|
+
return false unless degraded_until
|
|
510
|
+
|
|
511
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= degraded_until
|
|
512
|
+
@degraded_until = nil
|
|
513
|
+
false
|
|
514
|
+
else
|
|
515
|
+
true
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# Request worker shutdown and prevent future respawns.
|
|
520
|
+
#
|
|
521
|
+
# @return [void]
|
|
522
|
+
# @api private
|
|
523
|
+
def shutdown
|
|
524
|
+
target = nil
|
|
525
|
+
@lock.synchronize do
|
|
526
|
+
@shutdown = true
|
|
527
|
+
target = @inbox
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
send_to_inbox(target, nil)
|
|
531
|
+
rescue Ractor::ClosedError
|
|
532
|
+
nil
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# Return a non-nil failure object for Steep and runtime dispatch.
|
|
536
|
+
#
|
|
537
|
+
# @param failure [Exception, nil]
|
|
538
|
+
# @return [Exception]
|
|
539
|
+
# @raise [RuntimeError] if no failure was supplied
|
|
540
|
+
# @api private
|
|
541
|
+
def require_failure(failure)
|
|
542
|
+
raise "worker failure unavailable" if failure.nil?
|
|
543
|
+
|
|
544
|
+
failure
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
# Send a message to a worker inbox.
|
|
548
|
+
#
|
|
549
|
+
# Ractor::Port supports both #send and #<<. Some tests replace the inbox
|
|
550
|
+
# with a small sentinel object that only implements #send so the closed
|
|
551
|
+
# inbox path can be exercised without relying on a live port. Keep this
|
|
552
|
+
# helper deliberately typed as an inbox boundary rather than forcing every
|
|
553
|
+
# caller to narrow the test double shape.
|
|
554
|
+
#
|
|
555
|
+
# @param inbox [#send, nil]
|
|
556
|
+
# @param message [Object]
|
|
557
|
+
# @return [void]
|
|
558
|
+
# @raise [Ractor::ClosedError] when the worker inbox has already closed
|
|
559
|
+
# @api private
|
|
560
|
+
def send_to_inbox(inbox, message)
|
|
561
|
+
raise Ractor::ClosedError, "worker inbox closed" if inbox.nil?
|
|
562
|
+
|
|
563
|
+
inbox.send(message)
|
|
564
|
+
nil
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
# Wait for the supervisor thread to finish.
|
|
568
|
+
#
|
|
569
|
+
# @return [Thread]
|
|
570
|
+
# @api private
|
|
571
|
+
def join
|
|
572
|
+
@supervisor_thread.join
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
private
|
|
576
|
+
|
|
577
|
+
def boot!
|
|
578
|
+
boot_port = ::Ractor::Port.new
|
|
579
|
+
worker = CDC::Parallel::ProcessorPool.send(:start_worker, @processor, boot_port)
|
|
580
|
+
inbox = boot_port.receive
|
|
581
|
+
|
|
582
|
+
@lock.synchronize do
|
|
583
|
+
@worker = worker
|
|
584
|
+
@inbox = inbox
|
|
585
|
+
end
|
|
586
|
+
ensure
|
|
587
|
+
boot_port&.close
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def supervise
|
|
591
|
+
loop do
|
|
592
|
+
current_worker = worker
|
|
593
|
+
cause = wait_for_worker_death(current_worker)
|
|
594
|
+
break if shutting_down?
|
|
595
|
+
|
|
596
|
+
fail_inflight(cause)
|
|
597
|
+
break unless @supervision
|
|
598
|
+
|
|
599
|
+
cool_down_if_needed
|
|
600
|
+
break if shutting_down?
|
|
601
|
+
|
|
602
|
+
boot!
|
|
603
|
+
@lock.synchronize { @respawns += 1 }
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def wait_for_worker_death(current_worker)
|
|
608
|
+
current_worker.value
|
|
609
|
+
RuntimeError.new("worker slot #{@index} exited unexpectedly")
|
|
610
|
+
rescue Ractor::Error => e
|
|
611
|
+
cause = e.respond_to?(:cause) ? e.cause : nil
|
|
612
|
+
cause.is_a?(Exception) ? cause : e
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
def fail_inflight(cause)
|
|
616
|
+
pending = {} # : Hash[Integer, Ractor::Port]
|
|
617
|
+
@lock.synchronize do
|
|
618
|
+
pending = @inflight.dup
|
|
619
|
+
@inflight.clear
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
pending.each { |index, reply_port| send_failure(reply_port, index, cause) }
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
def send_failure(reply_port, index, error)
|
|
626
|
+
reply_port << [index, ResultCollector.worker_failure(error)]
|
|
627
|
+
rescue Ractor::ClosedError
|
|
628
|
+
nil
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
def cool_down_if_needed
|
|
632
|
+
return unless crash_loop?
|
|
633
|
+
|
|
634
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @respawn_cooldown
|
|
635
|
+
@lock.synchronize { @degraded_until = deadline }
|
|
636
|
+
sleep @respawn_cooldown
|
|
637
|
+
@lock.synchronize { @degraded_until = nil }
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
def crash_loop?
|
|
641
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
642
|
+
@lock.synchronize do
|
|
643
|
+
@crashes = @crashes.select { |timestamp| now - timestamp <= @respawn_window }
|
|
644
|
+
@crashes << now
|
|
645
|
+
@crashes.length > @max_respawns
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def shutting_down?
|
|
650
|
+
@lock.synchronize { @shutdown }
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
# rubocop:enable Metrics/ClassLength
|
|
343
654
|
end
|
|
344
655
|
end
|
|
345
656
|
end
|
|
@@ -90,6 +90,12 @@ module CDC
|
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
# Return whether a value is a serialized worker failure payload.
|
|
94
|
+
#
|
|
95
|
+
# @param value [Object]
|
|
96
|
+
# Raw worker response.
|
|
97
|
+
# @return [Boolean]
|
|
98
|
+
# @api private
|
|
93
99
|
def self.worker_failure?(value)
|
|
94
100
|
value.is_a?(Hash) && value[:type] == FAILURE_MARKER
|
|
95
101
|
end
|
data/lib/cdc/parallel/runtime.rb
CHANGED
|
@@ -48,14 +48,39 @@ module CDC
|
|
|
48
48
|
# Number of worker Ractors per internal pool.
|
|
49
49
|
# @param timeout [Numeric, nil]
|
|
50
50
|
# Optional timeout in seconds for result collection and shutdown waits.
|
|
51
|
+
# @param supervision [Boolean]
|
|
52
|
+
# Whether worker Ractors should be respawned after unexpected death.
|
|
53
|
+
# @param max_respawns [Integer]
|
|
54
|
+
# Maximum crash count inside `respawn_window` before a worker slot enters
|
|
55
|
+
# cooldown.
|
|
56
|
+
# @param respawn_window [Numeric]
|
|
57
|
+
# Time window, in seconds, used by the crash-loop circuit breaker.
|
|
58
|
+
# @param respawn_cooldown [Numeric]
|
|
59
|
+
# Cooldown, in seconds, before a crash-looping slot is revived again.
|
|
51
60
|
# @raise [UnsafeProcessorError]
|
|
52
61
|
# Raised when the processor class has not declared `ractor_safe!`.
|
|
53
62
|
# @raise [ArgumentError]
|
|
54
63
|
# Raised when size or timeout is invalid.
|
|
55
64
|
# @return [void]
|
|
56
|
-
def initialize(
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
def initialize(
|
|
66
|
+
processor:,
|
|
67
|
+
size: Etc.nprocessors,
|
|
68
|
+
timeout: nil,
|
|
69
|
+
supervision: true,
|
|
70
|
+
max_respawns: 3,
|
|
71
|
+
respawn_window: 60,
|
|
72
|
+
respawn_cooldown: 5
|
|
73
|
+
)
|
|
74
|
+
@processor = processor
|
|
75
|
+
@processor.start
|
|
76
|
+
|
|
77
|
+
pool_options = build_pool_options(
|
|
78
|
+
processor:, size:, timeout:, supervision:,
|
|
79
|
+
max_respawns:, respawn_window:, respawn_cooldown:
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
@processor_pool = ProcessorPool.new(**pool_options)
|
|
83
|
+
@transaction_pool = TransactionPool.new(**pool_options)
|
|
59
84
|
@router = Router.new(processor_pool: @processor_pool, transaction_pool: @transaction_pool)
|
|
60
85
|
@shutdown = false
|
|
61
86
|
end
|
|
@@ -94,7 +119,9 @@ module CDC
|
|
|
94
119
|
# Shut down all runtime resources.
|
|
95
120
|
#
|
|
96
121
|
# Shutdown is idempotent and cascades to the internal event and transaction
|
|
97
|
-
# pools.
|
|
122
|
+
# pools. The processor's `flush` and `stop` lifecycle hooks are called once
|
|
123
|
+
# after all pools have been drained. After shutdown, {#process} raises
|
|
124
|
+
# {ShutdownError}.
|
|
98
125
|
#
|
|
99
126
|
# @return [void]
|
|
100
127
|
def shutdown
|
|
@@ -103,6 +130,46 @@ module CDC
|
|
|
103
130
|
@shutdown = true
|
|
104
131
|
@processor_pool.shutdown
|
|
105
132
|
@transaction_pool.shutdown
|
|
133
|
+
@processor.flush
|
|
134
|
+
@processor.stop
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
# Build keyword arguments shared by the event and transaction pools.
|
|
140
|
+
#
|
|
141
|
+
# The runtime owns the processor lifecycle, so internal pools receive
|
|
142
|
+
# `manage_lifecycle: false` to avoid duplicate `start`, `flush`, or `stop`
|
|
143
|
+
# calls for the same processor instance.
|
|
144
|
+
#
|
|
145
|
+
# @param processor [CDC::Core::Processor]
|
|
146
|
+
# @param size [Integer]
|
|
147
|
+
# @param timeout [Numeric, nil]
|
|
148
|
+
# @param supervision [Boolean]
|
|
149
|
+
# @param max_respawns [Integer]
|
|
150
|
+
# @param respawn_window [Numeric]
|
|
151
|
+
# @param respawn_cooldown [Numeric]
|
|
152
|
+
# @return [Hash]
|
|
153
|
+
# @api private
|
|
154
|
+
def build_pool_options(
|
|
155
|
+
processor:,
|
|
156
|
+
size:,
|
|
157
|
+
timeout:,
|
|
158
|
+
supervision:,
|
|
159
|
+
max_respawns:,
|
|
160
|
+
respawn_window:,
|
|
161
|
+
respawn_cooldown:
|
|
162
|
+
)
|
|
163
|
+
{
|
|
164
|
+
processor:,
|
|
165
|
+
size:,
|
|
166
|
+
timeout:,
|
|
167
|
+
supervision:,
|
|
168
|
+
max_respawns:,
|
|
169
|
+
respawn_window:,
|
|
170
|
+
respawn_cooldown:,
|
|
171
|
+
manage_lifecycle: false
|
|
172
|
+
}
|
|
106
173
|
end
|
|
107
174
|
end
|
|
108
175
|
end
|
|
@@ -38,11 +38,42 @@ module CDC
|
|
|
38
38
|
# Number of worker Ractors in the underlying processor pool.
|
|
39
39
|
# @param timeout [Numeric, nil]
|
|
40
40
|
# Optional timeout in seconds for result collection and shutdown waits.
|
|
41
|
+
# @param supervision [Boolean]
|
|
42
|
+
# Whether worker Ractors should be respawned after unexpected death.
|
|
43
|
+
# @param max_respawns [Integer]
|
|
44
|
+
# Maximum crash count inside `respawn_window` before a worker slot enters
|
|
45
|
+
# cooldown.
|
|
46
|
+
# @param respawn_window [Numeric]
|
|
47
|
+
# Time window, in seconds, used by the crash-loop circuit breaker.
|
|
48
|
+
# @param respawn_cooldown [Numeric]
|
|
49
|
+
# Cooldown, in seconds, before a crash-looping slot is revived again.
|
|
50
|
+
# @param manage_lifecycle [Boolean]
|
|
51
|
+
# When `true` (default), the pool calls `processor.start` during
|
|
52
|
+
# initialization and `processor.flush` + `processor.stop` during shutdown.
|
|
53
|
+
# Set to `false` when a higher-level runtime owns the processor lifecycle.
|
|
41
54
|
# @raise [UnsafeProcessorError]
|
|
42
55
|
# Raised when the processor class has not declared `ractor_safe!`.
|
|
43
56
|
# @return [void]
|
|
44
|
-
def initialize(
|
|
45
|
-
|
|
57
|
+
def initialize(
|
|
58
|
+
processor:,
|
|
59
|
+
size: Etc.nprocessors,
|
|
60
|
+
timeout: nil,
|
|
61
|
+
supervision: true,
|
|
62
|
+
max_respawns: 3,
|
|
63
|
+
respawn_window: 60,
|
|
64
|
+
respawn_cooldown: 5,
|
|
65
|
+
manage_lifecycle: true
|
|
66
|
+
)
|
|
67
|
+
@processor_pool = ProcessorPool.new(
|
|
68
|
+
processor:,
|
|
69
|
+
size:,
|
|
70
|
+
timeout:,
|
|
71
|
+
supervision:,
|
|
72
|
+
max_respawns:,
|
|
73
|
+
respawn_window:,
|
|
74
|
+
respawn_cooldown:,
|
|
75
|
+
manage_lifecycle:
|
|
76
|
+
)
|
|
46
77
|
end
|
|
47
78
|
|
|
48
79
|
# Process all events inside a transaction envelope.
|
data/lib/cdc/parallel/version.rb
CHANGED
data/lib/cdc/parallel.rb
CHANGED
|
@@ -12,6 +12,12 @@ require_relative "parallel/transaction_pool"
|
|
|
12
12
|
require_relative "parallel/router"
|
|
13
13
|
require_relative "parallel/runtime"
|
|
14
14
|
|
|
15
|
+
# Namespace for Change Data Capture ecosystem components.
|
|
16
|
+
#
|
|
17
|
+
# The `cdc-parallel` gem adds the {CDC::Parallel} runtime namespace under
|
|
18
|
+
# this shared ecosystem root.
|
|
19
|
+
#
|
|
20
|
+
# @api public
|
|
15
21
|
module CDC
|
|
16
22
|
# Optional parallel Change Data Capture runtime for `cdc-core` processors.
|
|
17
23
|
#
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
module CDC
|
|
2
2
|
module Parallel
|
|
3
|
+
# Private Data superclass used to keep YARD documentation stable for Configuration.
|
|
4
|
+
class ConfigurationData < Data
|
|
5
|
+
attr_reader size: Integer
|
|
6
|
+
attr_reader timeout: Numeric?
|
|
7
|
+
end
|
|
8
|
+
|
|
3
9
|
# Immutable configuration for Ractor runtimes.
|
|
4
|
-
class Configuration <
|
|
10
|
+
class Configuration < ConfigurationData
|
|
5
11
|
attr_reader size: Integer
|
|
6
12
|
|
|
7
13
|
attr_reader timeout: Numeric?
|
|
@@ -7,25 +7,46 @@ module CDC
|
|
|
7
7
|
type worker_response = result | Hash[Symbol, untyped] | untyped
|
|
8
8
|
type reply_port = Ractor::Port
|
|
9
9
|
type worker_inbox = Ractor::Port
|
|
10
|
-
@processor: CDC::Core::Processor
|
|
11
10
|
|
|
11
|
+
@processor: CDC::Core::Processor
|
|
12
12
|
@configuration: Configuration
|
|
13
|
-
|
|
13
|
+
@slots: Array[WorkerSlot]
|
|
14
14
|
@workers: Array[Ractor]
|
|
15
|
-
|
|
16
15
|
@worker_inboxes: Array[worker_inbox]
|
|
17
|
-
|
|
18
16
|
@next_worker: Integer
|
|
19
|
-
|
|
20
17
|
@dispatch_mutex: Mutex
|
|
21
|
-
|
|
22
18
|
@shutdown: bool
|
|
19
|
+
@manage_lifecycle: bool
|
|
23
20
|
|
|
24
21
|
# @param processor [CDC::Core::Processor]
|
|
25
22
|
# @param size [Integer]
|
|
26
|
-
# @param timeout [
|
|
23
|
+
# @param timeout [Numeric, nil]
|
|
24
|
+
# @param supervision [Boolean]
|
|
25
|
+
# @param max_respawns [Integer]
|
|
26
|
+
# @param respawn_window [Numeric]
|
|
27
|
+
# @param respawn_cooldown [Numeric]
|
|
28
|
+
# @param manage_lifecycle [Boolean]
|
|
27
29
|
# @return [void]
|
|
28
|
-
def initialize: (
|
|
30
|
+
def initialize: (
|
|
31
|
+
processor: CDC::Core::Processor,
|
|
32
|
+
?size: Integer,
|
|
33
|
+
?timeout: Numeric?,
|
|
34
|
+
?supervision: bool,
|
|
35
|
+
?max_respawns: Integer,
|
|
36
|
+
?respawn_window: Numeric,
|
|
37
|
+
?respawn_cooldown: Numeric,
|
|
38
|
+
?manage_lifecycle: bool
|
|
39
|
+
) -> void
|
|
40
|
+
|
|
41
|
+
# Total worker-slot respawns since pool boot.
|
|
42
|
+
#
|
|
43
|
+
# @return [Integer]
|
|
44
|
+
def respawns: () -> Integer
|
|
45
|
+
|
|
46
|
+
# Whether any worker slot is in crash-loop cooldown.
|
|
47
|
+
#
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def degraded?: () -> bool
|
|
29
50
|
|
|
30
51
|
# Process one work item synchronously.
|
|
31
52
|
#
|
|
@@ -39,40 +60,90 @@ module CDC
|
|
|
39
60
|
# @return [Array<CDC::Core::ProcessorResult>]
|
|
40
61
|
def process_many: (Array[work_item] items) -> Array[result]
|
|
41
62
|
|
|
42
|
-
# Shut down the pool.
|
|
63
|
+
# Shut down the pool and call processor lifecycle hooks if manage_lifecycle is true.
|
|
43
64
|
#
|
|
44
65
|
# @return [void]
|
|
45
|
-
def shutdown: () ->
|
|
66
|
+
def shutdown: () -> void
|
|
46
67
|
|
|
47
68
|
private
|
|
48
69
|
|
|
70
|
+
def self.start_worker: (CDC::Core::Processor processor, Ractor::Port boot_port) -> Ractor
|
|
71
|
+
|
|
49
72
|
def self.run_worker_loop: (untyped safe_processor, untyped inbox) -> nil
|
|
50
73
|
|
|
51
74
|
def self.worker_response: (untyped safe_processor, work_item item) -> worker_response
|
|
52
75
|
|
|
53
|
-
def dispatch: (Array[work_item] work_items, reply_port reply_port) -> Array[
|
|
76
|
+
def dispatch: (Array[work_item] work_items, reply_port reply_port) -> Array[WorkerSlot?]
|
|
54
77
|
|
|
55
|
-
def signal_workers: () ->
|
|
78
|
+
def signal_workers: () -> void
|
|
56
79
|
|
|
57
|
-
def wait_for_workers: () ->
|
|
80
|
+
def wait_for_workers: () -> void
|
|
58
81
|
|
|
59
|
-
def wait_for_workers_with_timeout: () ->
|
|
82
|
+
def wait_for_workers_with_timeout: () -> void
|
|
60
83
|
|
|
61
|
-
def validate_processor!: (CDC::Core::Processor processor) ->
|
|
84
|
+
def validate_processor!: (CDC::Core::Processor processor) -> void
|
|
62
85
|
|
|
63
|
-
def
|
|
86
|
+
def next_worker_slot: () -> WorkerSlot
|
|
64
87
|
|
|
65
|
-
def
|
|
88
|
+
def collect_results: (reply_port reply_port, Integer count, ?Array[WorkerSlot?] assignments) -> Array[result?]
|
|
66
89
|
|
|
67
|
-
def
|
|
90
|
+
def collect_results_without_timeout: (reply_port reply_port, Array[result?] results, Array[WorkerSlot?] assignments) -> Array[result?]
|
|
68
91
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
def collect_results_without_timeout: (reply_port reply_port, Array[result?] results) -> untyped
|
|
72
|
-
|
|
73
|
-
def collect_results_with_timeout: (reply_port reply_port, Array[result?] results) -> untyped
|
|
92
|
+
def collect_results_with_timeout: (reply_port reply_port, Array[result?] results, Array[WorkerSlot?] assignments) -> Array[result?]
|
|
74
93
|
|
|
75
94
|
def timeout_results: (Array[result?] results) -> Array[result]
|
|
95
|
+
|
|
96
|
+
# One supervised worker position in the pool.
|
|
97
|
+
class WorkerSlot
|
|
98
|
+
@index: Integer
|
|
99
|
+
@processor: CDC::Core::Processor
|
|
100
|
+
@supervision: bool
|
|
101
|
+
@max_respawns: Integer
|
|
102
|
+
@respawn_window: Float
|
|
103
|
+
@respawn_cooldown: Float
|
|
104
|
+
@lock: Mutex
|
|
105
|
+
@worker: Ractor
|
|
106
|
+
@inbox: Ractor::Port
|
|
107
|
+
@inflight: Hash[Integer, Ractor::Port]
|
|
108
|
+
@crashes: Array[Float]
|
|
109
|
+
@respawns: Integer
|
|
110
|
+
@shutdown: bool
|
|
111
|
+
@degraded_until: Float?
|
|
112
|
+
@supervisor_thread: Thread
|
|
113
|
+
|
|
114
|
+
attr_reader respawns: Integer
|
|
115
|
+
|
|
116
|
+
def initialize: (
|
|
117
|
+
index: Integer,
|
|
118
|
+
processor: CDC::Core::Processor,
|
|
119
|
+
supervision: bool,
|
|
120
|
+
max_respawns: Integer,
|
|
121
|
+
respawn_window: Numeric,
|
|
122
|
+
respawn_cooldown: Numeric
|
|
123
|
+
) -> void
|
|
124
|
+
|
|
125
|
+
def worker: () -> Ractor
|
|
126
|
+
def inbox: () -> Ractor::Port
|
|
127
|
+
def send_work: (Integer index, untyped item, Ractor::Port reply_port) -> void
|
|
128
|
+
def complete: (Integer index) -> void
|
|
129
|
+
def degraded?: () -> bool
|
|
130
|
+
def shutdown: () -> void
|
|
131
|
+
def join: () -> void
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def boot!: () -> void
|
|
136
|
+
def supervise: () -> void
|
|
137
|
+
def wait_for_worker_death: (Ractor current_worker) -> Exception
|
|
138
|
+
def fail_inflight: (Exception cause) -> void
|
|
139
|
+
def send_failure: (Ractor::Port reply_port, Integer index, Exception error) -> void
|
|
140
|
+
def require_failure: (Exception? failure) -> Exception
|
|
141
|
+
def send_to_inbox: (untyped inbox, untyped message) -> void
|
|
142
|
+
def cool_down_if_needed: () -> void
|
|
143
|
+
def crash_loop?: () -> bool
|
|
144
|
+
def shutting_down?: () -> bool
|
|
145
|
+
def degraded_locked?: () -> bool
|
|
146
|
+
end
|
|
76
147
|
end
|
|
77
148
|
end
|
|
78
149
|
end
|
|
@@ -4,19 +4,25 @@ module CDC
|
|
|
4
4
|
class Runtime
|
|
5
5
|
type work_item = CDC::Core::ChangeEvent | CDC::Core::TransactionEnvelope
|
|
6
6
|
|
|
7
|
+
@processor: CDC::Core::Processor
|
|
7
8
|
@processor_pool: ProcessorPool
|
|
8
|
-
|
|
9
9
|
@transaction_pool: TransactionPool
|
|
10
|
-
|
|
11
10
|
@router: Router
|
|
12
|
-
|
|
13
11
|
@shutdown: bool
|
|
14
12
|
|
|
15
13
|
# @param processor [CDC::Core::Processor]
|
|
16
14
|
# @param size [Integer]
|
|
17
|
-
# @param timeout [
|
|
15
|
+
# @param timeout [Numeric, nil]
|
|
18
16
|
# @return [void]
|
|
19
|
-
def initialize: (
|
|
17
|
+
def initialize: (
|
|
18
|
+
processor: CDC::Core::Processor,
|
|
19
|
+
?size: Integer,
|
|
20
|
+
?timeout: Numeric?,
|
|
21
|
+
?supervision: bool,
|
|
22
|
+
?max_respawns: Integer,
|
|
23
|
+
?respawn_window: Numeric,
|
|
24
|
+
?respawn_cooldown: Numeric
|
|
25
|
+
) -> void
|
|
20
26
|
|
|
21
27
|
# Process a ChangeEvent or TransactionEnvelope.
|
|
22
28
|
#
|
|
@@ -31,10 +37,32 @@ module CDC
|
|
|
31
37
|
# @return [CDC::Core::ProcessorResult]
|
|
32
38
|
def process_transaction: (CDC::Core::TransactionEnvelope transaction) -> CDC::Core::ProcessorResult
|
|
33
39
|
|
|
34
|
-
# Shut down all runtime resources.
|
|
40
|
+
# Shut down all runtime resources and call processor lifecycle hooks.
|
|
35
41
|
#
|
|
36
42
|
# @return [void]
|
|
37
|
-
def shutdown: () ->
|
|
43
|
+
def shutdown: () -> void
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# Build keyword arguments shared by internal pools.
|
|
48
|
+
def build_pool_options: (
|
|
49
|
+
processor: CDC::Core::Processor,
|
|
50
|
+
size: Integer,
|
|
51
|
+
timeout: Numeric?,
|
|
52
|
+
supervision: bool,
|
|
53
|
+
max_respawns: Integer,
|
|
54
|
+
respawn_window: Numeric,
|
|
55
|
+
respawn_cooldown: Numeric
|
|
56
|
+
) -> {
|
|
57
|
+
processor: CDC::Core::Processor,
|
|
58
|
+
size: Integer,
|
|
59
|
+
timeout: Numeric?,
|
|
60
|
+
supervision: bool,
|
|
61
|
+
max_respawns: Integer,
|
|
62
|
+
respawn_window: Numeric,
|
|
63
|
+
respawn_cooldown: Numeric,
|
|
64
|
+
manage_lifecycle: false
|
|
65
|
+
}
|
|
38
66
|
end
|
|
39
67
|
end
|
|
40
68
|
end
|
|
@@ -6,8 +6,18 @@ module CDC
|
|
|
6
6
|
|
|
7
7
|
# @param processor [CDC::Core::Processor]
|
|
8
8
|
# @param size [Integer]
|
|
9
|
-
# @param timeout [
|
|
10
|
-
|
|
9
|
+
# @param timeout [Numeric, nil]
|
|
10
|
+
# @param manage_lifecycle [Boolean]
|
|
11
|
+
def initialize: (
|
|
12
|
+
processor: CDC::Core::Processor,
|
|
13
|
+
?size: Integer,
|
|
14
|
+
?timeout: Numeric?,
|
|
15
|
+
?supervision: bool,
|
|
16
|
+
?max_respawns: Integer,
|
|
17
|
+
?respawn_window: Numeric,
|
|
18
|
+
?respawn_cooldown: Numeric,
|
|
19
|
+
?manage_lifecycle: bool
|
|
20
|
+
) -> void
|
|
11
21
|
|
|
12
22
|
# Process all events inside a transaction envelope.
|
|
13
23
|
#
|
|
@@ -18,7 +28,7 @@ module CDC
|
|
|
18
28
|
# Shut down worker resources.
|
|
19
29
|
#
|
|
20
30
|
# @return [void]
|
|
21
|
-
def shutdown: () ->
|
|
31
|
+
def shutdown: () -> void
|
|
22
32
|
end
|
|
23
33
|
end
|
|
24
34
|
end
|