cdc-parallel 0.2.0 → 0.2.2
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 +25 -0
- data/lib/cdc/parallel/configuration.rb +2 -0
- data/lib/cdc/parallel/processor_pool.rb +71 -5
- data/lib/cdc/parallel/transaction_pool.rb +6 -2
- data/lib/cdc/parallel/version.rb +1 -1
- data/lib/cdc/parallel.rb +1 -0
- data/sig/cdc/parallel/processor_pool.rbs +31 -10
- data/sig/cdc/parallel/result_collector.rbs +6 -0
- data/sig/cdc/parallel/version.rbs +1 -1
- data/sig/shims/cdc_core.rbs +2 -2
- data/sig/shims/timeout.rbs +3 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a51d304d55079509a1c2056573a9f4764924573b9a13eb1dbeda89ac94d57836
|
|
4
|
+
data.tar.gz: 4e154536aaca3d801d4affe02e424f332c6a1a37b738ee70cc8fea5f335645fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3002bfcd3914510fc39e7621fba99a7868303d24f8c8e5b849d40c653f73355b9926636ed6bb31754f2f028d65af325207eb89466923bc413eb8e44538730e7
|
|
7
|
+
data.tar.gz: 686ef1d6c0759d8ebedbd18e7109a4ac22008e8a0501faab31db8440bef3cf1127f79b076ce46fd3d8eec46f6e524e0ac6a75b1d3e0cefbc2f5ab151e99110b8
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
|
|
6
6
|
|
|
7
|
+
## [0.2.2] - 2026-06-03
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Improved processor pool shutdown so workers are signaled and confirmed stopped where practical.
|
|
12
|
+
- Updated transaction processing so partial event failures fail the transaction result while preserving per-event results.
|
|
13
|
+
- Added CI validation for RBS signatures.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Added regression coverage for shutdown after processed and pending work.
|
|
18
|
+
- Added regression coverage for timeout-bounded shutdown behavior.
|
|
19
|
+
- Added regression coverage for `process_many([])` returning a clean empty result.
|
|
20
|
+
- Added transaction pool coverage for successful and partially failed transactions.
|
|
21
|
+
|
|
22
|
+
## [0.2.1] - 2026-06-03
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
v0.2.1 - Correctness and reliability patch
|
|
27
|
+
|
|
28
|
+
- Enforced processor timeout handling.
|
|
29
|
+
- Fixed transaction partial-failure behavior.
|
|
30
|
+
- Added regression coverage for hung processors and transaction failure cases.
|
|
31
|
+
|
|
7
32
|
## [0.2.0] - 2026-06-03
|
|
8
33
|
|
|
9
34
|
### Added
|
|
@@ -12,6 +12,8 @@ module CDC
|
|
|
12
12
|
def initialize(size: Etc.nprocessors, timeout: nil)
|
|
13
13
|
raise ArgumentError, "size must be an Integer" unless size.is_a?(Integer)
|
|
14
14
|
raise ArgumentError, "size must be greater than zero" unless size.positive?
|
|
15
|
+
raise ArgumentError, "timeout must be numeric" unless timeout.nil? || timeout.is_a?(Numeric)
|
|
16
|
+
raise ArgumentError, "timeout must be greater than zero" if timeout && !timeout.positive?
|
|
15
17
|
|
|
16
18
|
super
|
|
17
19
|
::Ractor.make_shareable(self)
|
|
@@ -8,7 +8,7 @@ module CDC
|
|
|
8
8
|
# This pays Ractor startup cost once, keeps workers alive after processor
|
|
9
9
|
# failures, and provides both synchronous single-item processing and batched
|
|
10
10
|
# dispatch for throughput-oriented benchmarks and runtimes.
|
|
11
|
-
class ProcessorPool
|
|
11
|
+
class ProcessorPool # rubocop:disable Metrics/ClassLength
|
|
12
12
|
# @param processor [CDC::Core::Processor]
|
|
13
13
|
# @param size [Integer]
|
|
14
14
|
# @param timeout [Float, nil]
|
|
@@ -63,6 +63,13 @@ module CDC
|
|
|
63
63
|
|
|
64
64
|
@shutdown = true
|
|
65
65
|
|
|
66
|
+
signal_workers
|
|
67
|
+
wait_for_workers
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def signal_workers
|
|
66
73
|
@workers.each do |worker|
|
|
67
74
|
worker.send(nil)
|
|
68
75
|
rescue Ractor::ClosedError
|
|
@@ -70,7 +77,26 @@ module CDC
|
|
|
70
77
|
end
|
|
71
78
|
end
|
|
72
79
|
|
|
73
|
-
|
|
80
|
+
def wait_for_workers
|
|
81
|
+
if @configuration.timeout
|
|
82
|
+
wait_for_workers_with_timeout
|
|
83
|
+
else
|
|
84
|
+
@workers.each(&:join)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def wait_for_workers_with_timeout
|
|
89
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @configuration.timeout
|
|
90
|
+
|
|
91
|
+
@workers.each do |worker|
|
|
92
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
93
|
+
break unless remaining.positive?
|
|
94
|
+
|
|
95
|
+
::Timeout.timeout(remaining, TimeoutError) { worker.join }
|
|
96
|
+
rescue TimeoutError
|
|
97
|
+
break
|
|
98
|
+
end
|
|
99
|
+
end
|
|
74
100
|
|
|
75
101
|
def validate_processor!(processor)
|
|
76
102
|
return if processor.class.respond_to?(:ractor_safe?) &&
|
|
@@ -80,7 +106,7 @@ module CDC
|
|
|
80
106
|
"#{processor.class} must declare ractor_safe!"
|
|
81
107
|
end
|
|
82
108
|
|
|
83
|
-
def build_worker(processor)
|
|
109
|
+
def build_worker(processor) # rubocop:disable Metrics/MethodLength
|
|
84
110
|
::Ractor.new(processor) do |safe_processor|
|
|
85
111
|
loop do
|
|
86
112
|
message = ::Ractor.receive
|
|
@@ -96,7 +122,11 @@ module CDC
|
|
|
96
122
|
CDC::Parallel::ResultCollector.worker_failure(e)
|
|
97
123
|
end
|
|
98
124
|
|
|
99
|
-
|
|
125
|
+
begin
|
|
126
|
+
reply_port << [index, response]
|
|
127
|
+
rescue Ractor::ClosedError
|
|
128
|
+
# The caller may have timed out and closed the reply port.
|
|
129
|
+
end
|
|
100
130
|
end
|
|
101
131
|
end
|
|
102
132
|
end
|
|
@@ -112,14 +142,50 @@ module CDC
|
|
|
112
142
|
|
|
113
143
|
def collect_results(reply_port, count)
|
|
114
144
|
results = Array.new(count)
|
|
145
|
+
return results.freeze if count.zero?
|
|
115
146
|
|
|
116
|
-
|
|
147
|
+
if @configuration.timeout
|
|
148
|
+
collect_results_with_timeout(reply_port, results)
|
|
149
|
+
else
|
|
150
|
+
collect_results_without_timeout(reply_port, results)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def collect_results_without_timeout(reply_port, results)
|
|
155
|
+
results.length.times do
|
|
117
156
|
index, response = reply_port.receive
|
|
118
157
|
results[index] = ResultCollector.normalize(response)
|
|
119
158
|
end
|
|
120
159
|
|
|
121
160
|
results.freeze
|
|
122
161
|
end
|
|
162
|
+
|
|
163
|
+
def collect_results_with_timeout(reply_port, results)
|
|
164
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @configuration.timeout
|
|
165
|
+
|
|
166
|
+
results.length.times do
|
|
167
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
168
|
+
return timeout_results(results) unless remaining.positive?
|
|
169
|
+
|
|
170
|
+
index, response = ::Timeout.timeout(remaining, TimeoutError) { reply_port.receive }
|
|
171
|
+
results[index] = ResultCollector.normalize(response)
|
|
172
|
+
rescue TimeoutError
|
|
173
|
+
return timeout_results(results)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
results.freeze
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def timeout_results(results)
|
|
180
|
+
missing = results.count(&:nil?)
|
|
181
|
+
timeout_error = TimeoutError.new(
|
|
182
|
+
"processor pool timed out after #{@configuration.timeout} seconds waiting for #{missing} result(s)"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
results.map do |result|
|
|
186
|
+
result || CDC::Core::ProcessorResult.failure(timeout_error)
|
|
187
|
+
end.freeze
|
|
188
|
+
end
|
|
123
189
|
end
|
|
124
190
|
end
|
|
125
191
|
end
|
|
@@ -16,8 +16,12 @@ module CDC
|
|
|
16
16
|
# @param transaction [CDC::Core::TransactionEnvelope]
|
|
17
17
|
# @return [CDC::Core::ProcessorResult]
|
|
18
18
|
def process(transaction)
|
|
19
|
-
results =
|
|
20
|
-
|
|
19
|
+
results = @processor_pool.process_many(transaction.events).freeze
|
|
20
|
+
failure = results.find(&:failure?)
|
|
21
|
+
|
|
22
|
+
return CDC::Core::ProcessorResult.failure(failure.error, event: results) if failure
|
|
23
|
+
|
|
24
|
+
CDC::Core::ProcessorResult.success(results)
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
# Shut down worker resources.
|
data/lib/cdc/parallel/version.rb
CHANGED
data/lib/cdc/parallel.rb
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
module CDC
|
|
2
2
|
module Parallel
|
|
3
|
-
# Executes one Ractor-safe processor in
|
|
4
|
-
#
|
|
5
|
-
# This v0.1 implementation intentionally uses one-shot worker Ractors for
|
|
6
|
-
# deterministic synchronous semantics while preserving the public pool API.
|
|
7
|
-
# The parallel-pool dependency is kept as the runtime foundation for later
|
|
8
|
-
# async/throughput-focused versions.
|
|
3
|
+
# Executes one Ractor-safe processor in pre-warmed persistent Ractor workers.
|
|
9
4
|
class ProcessorPool
|
|
10
5
|
@processor: untyped
|
|
11
6
|
|
|
12
7
|
@configuration: untyped
|
|
13
8
|
|
|
9
|
+
@workers: untyped
|
|
10
|
+
|
|
11
|
+
@next_worker: Integer
|
|
12
|
+
|
|
14
13
|
@shutdown: untyped
|
|
15
14
|
|
|
16
15
|
# @param processor [CDC::Core::Processor]
|
|
@@ -19,11 +18,17 @@ module CDC
|
|
|
19
18
|
# @return [void]
|
|
20
19
|
def initialize: (processor: untyped, ?size: untyped, ?timeout: untyped?) -> void
|
|
21
20
|
|
|
22
|
-
# Process one
|
|
21
|
+
# Process one work item synchronously.
|
|
23
22
|
#
|
|
24
|
-
# @param
|
|
23
|
+
# @param item [Object]
|
|
25
24
|
# @return [CDC::Core::ProcessorResult]
|
|
26
|
-
def process: (untyped
|
|
25
|
+
def process: (untyped item) -> untyped
|
|
26
|
+
|
|
27
|
+
# Process many work items using the pre-warmed worker pool.
|
|
28
|
+
#
|
|
29
|
+
# @param items [Array<Object>]
|
|
30
|
+
# @return [Array<CDC::Core::ProcessorResult>]
|
|
31
|
+
def process_many: (untyped items) -> untyped
|
|
27
32
|
|
|
28
33
|
# Shut down the pool.
|
|
29
34
|
#
|
|
@@ -32,9 +37,25 @@ module CDC
|
|
|
32
37
|
|
|
33
38
|
private
|
|
34
39
|
|
|
40
|
+
def signal_workers: () -> untyped
|
|
41
|
+
|
|
42
|
+
def wait_for_workers: () -> untyped
|
|
43
|
+
|
|
44
|
+
def wait_for_workers_with_timeout: () -> untyped
|
|
45
|
+
|
|
35
46
|
def validate_processor!: (untyped processor) -> (nil | untyped)
|
|
36
47
|
|
|
37
|
-
def
|
|
48
|
+
def build_worker: (untyped processor) -> untyped
|
|
49
|
+
|
|
50
|
+
def next_worker: () -> untyped
|
|
51
|
+
|
|
52
|
+
def collect_results: (untyped reply_port, Integer count) -> untyped
|
|
53
|
+
|
|
54
|
+
def collect_results_without_timeout: (untyped reply_port, untyped results) -> untyped
|
|
55
|
+
|
|
56
|
+
def collect_results_with_timeout: (untyped reply_port, untyped results) -> untyped
|
|
57
|
+
|
|
58
|
+
def timeout_results: (untyped results) -> untyped
|
|
38
59
|
end
|
|
39
60
|
end
|
|
40
61
|
end
|
|
@@ -4,6 +4,12 @@ module CDC
|
|
|
4
4
|
class ResultCollector
|
|
5
5
|
FAILURE_MARKER: :__cdc_parallel_failure__
|
|
6
6
|
|
|
7
|
+
# Build a shareable success payload that can safely cross a Ractor boundary.
|
|
8
|
+
#
|
|
9
|
+
# @param value [Object]
|
|
10
|
+
# @return [Object]
|
|
11
|
+
def self.worker_success: (untyped value) -> untyped
|
|
12
|
+
|
|
7
13
|
# Build a shareable failure payload that can safely cross a Ractor boundary.
|
|
8
14
|
#
|
|
9
15
|
# @param error [Exception]
|
data/sig/shims/cdc_core.rbs
CHANGED
|
@@ -2,7 +2,7 @@ module CDC
|
|
|
2
2
|
module Core
|
|
3
3
|
class ProcessorResult
|
|
4
4
|
def self.success: (untyped event) -> ProcessorResult
|
|
5
|
-
def self.failure: (untyped error) -> ProcessorResult
|
|
5
|
+
def self.failure: (untyped error, ?event: untyped) -> ProcessorResult
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
class ChangeEvent
|
|
@@ -11,4 +11,4 @@ module CDC
|
|
|
11
11
|
class TransactionEnvelope
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
|
-
end
|
|
14
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cdc-parallel
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ken C. Demanawa
|
|
@@ -73,6 +73,7 @@ files:
|
|
|
73
73
|
- sig/shims/cdc_core.rbs
|
|
74
74
|
- sig/shims/data_define.rbs
|
|
75
75
|
- sig/shims/etc.rbs
|
|
76
|
+
- sig/shims/timeout.rbs
|
|
76
77
|
homepage: https://kanutocd.github.io/cdc-parallel/
|
|
77
78
|
licenses:
|
|
78
79
|
- MIT
|