cdc-parallel 0.2.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e16d6352e78132e2e0f488542b17117bda8733ca15b3117f8151b6acbfc3567
4
- data.tar.gz: 3e28c6c37d5078696ab334f5c8b15409172bebb8102747c2e7558f0089d4ad64
3
+ metadata.gz: 72b3b33568a37fa04b270edad8511a446703f115d78d86c2347c6af2e4be76a0
4
+ data.tar.gz: e044e42d90f11b6b75b946ef0f1b1725f2810056be353a56342a908c139eb91c
5
5
  SHA512:
6
- metadata.gz: 4e71ef2eeda63a9d6f6c59d49ba140b73a62d0e37894e168060a4c796f28a7d6790b37b891c7188fff78de1e843386c0f892f0720ba3de16ea9bfe198e719382
7
- data.tar.gz: de5eb4cb7861e263402305e361d4ac49d335d9b657562cca93544c64f34f76646289212647506c87f4d6ef7eb5ec586cbb58596ad9427f8becb99ca1af82127c
6
+ metadata.gz: 3780fa1a616b33e824cff4f6c7f0ec8402085e5a742abbaa45082d1dc8ce6c7be04bd462aa78e4afcb61f7f9a3b0adab54ed4e47f972633c12348d57e65a632f
7
+ data.tar.gz: 01ae9a18f4d8a6bab6c5f7e71333c38475a1930bdce6326e41bc9376100e74800cb850b22b8fda1de9638a2e78be3094a27bfd1b6592a6e868554ce933b471af
data/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ 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.1] - 2026-06-03
8
+
9
+ ### Added
10
+
11
+ v0.2.1 - Correctness and reliability patch
12
+
13
+ - Enforced processor timeout handling.
14
+ - Fixed transaction partial-failure behavior.
15
+ - Added regression coverage for hung processors and transaction failure cases.
16
+
7
17
  ## [0.2.0] - 2026-06-03
8
18
 
9
19
  ### 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]
@@ -80,7 +80,7 @@ module CDC
80
80
  "#{processor.class} must declare ractor_safe!"
81
81
  end
82
82
 
83
- def build_worker(processor)
83
+ def build_worker(processor) # rubocop:disable Metrics/MethodLength
84
84
  ::Ractor.new(processor) do |safe_processor|
85
85
  loop do
86
86
  message = ::Ractor.receive
@@ -96,7 +96,11 @@ module CDC
96
96
  CDC::Parallel::ResultCollector.worker_failure(e)
97
97
  end
98
98
 
99
- reply_port << [index, response]
99
+ begin
100
+ reply_port << [index, response]
101
+ rescue Ractor::ClosedError
102
+ # The caller may have timed out and closed the reply port.
103
+ end
100
104
  end
101
105
  end
102
106
  end
@@ -112,14 +116,50 @@ module CDC
112
116
 
113
117
  def collect_results(reply_port, count)
114
118
  results = Array.new(count)
119
+ return results.freeze if count.zero?
120
+
121
+ if @configuration.timeout
122
+ collect_results_with_timeout(reply_port, results)
123
+ else
124
+ collect_results_without_timeout(reply_port, results)
125
+ end
126
+ end
115
127
 
116
- count.times do
128
+ def collect_results_without_timeout(reply_port, results)
129
+ results.length.times do
117
130
  index, response = reply_port.receive
118
131
  results[index] = ResultCollector.normalize(response)
119
132
  end
120
133
 
121
134
  results.freeze
122
135
  end
136
+
137
+ def collect_results_with_timeout(reply_port, results)
138
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @configuration.timeout
139
+
140
+ results.length.times do
141
+ remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
142
+ return timeout_results(results) unless remaining.positive?
143
+
144
+ index, response = ::Timeout.timeout(remaining, TimeoutError) { reply_port.receive }
145
+ results[index] = ResultCollector.normalize(response)
146
+ rescue TimeoutError
147
+ return timeout_results(results)
148
+ end
149
+
150
+ results.freeze
151
+ end
152
+
153
+ def timeout_results(results)
154
+ missing = results.count(&:nil?)
155
+ timeout_error = TimeoutError.new(
156
+ "processor pool timed out after #{@configuration.timeout} seconds waiting for #{missing} result(s)"
157
+ )
158
+
159
+ results.map do |result|
160
+ result || CDC::Core::ProcessorResult.failure(timeout_error)
161
+ end.freeze
162
+ end
123
163
  end
124
164
  end
125
165
  end
@@ -3,6 +3,6 @@
3
3
  module CDC
4
4
  module Parallel
5
5
  # Current cdc-parallel version.
6
- VERSION = "0.2.0"
6
+ VERSION = "0.2.1"
7
7
  end
8
8
  end
data/lib/cdc/parallel.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "etc"
4
+ require "timeout"
4
5
 
5
6
  require_relative "parallel/version"
6
7
  require_relative "parallel/errors"
@@ -1,16 +1,15 @@
1
1
  module CDC
2
2
  module Parallel
3
- # Executes one Ractor-safe processor in isolated Ractor workers.
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 ChangeEvent.
21
+ # Process one work item synchronously.
23
22
  #
24
- # @param event [CDC::Core::ChangeEvent]
23
+ # @param item [Object]
25
24
  # @return [CDC::Core::ProcessorResult]
26
- def process: (untyped event) -> 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
  #
@@ -34,7 +39,17 @@ module CDC
34
39
 
35
40
  def validate_processor!: (untyped processor) -> (nil | untyped)
36
41
 
37
- def take: (untyped worker) -> untyped
42
+ def build_worker: (untyped processor) -> untyped
43
+
44
+ def next_worker: () -> untyped
45
+
46
+ def collect_results: (untyped reply_port, Integer count) -> untyped
47
+
48
+ def collect_results_without_timeout: (untyped reply_port, untyped results) -> untyped
49
+
50
+ def collect_results_with_timeout: (untyped reply_port, untyped results) -> untyped
51
+
52
+ def timeout_results: (untyped results) -> untyped
38
53
  end
39
54
  end
40
55
  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]
@@ -1,6 +1,6 @@
1
1
  module CDC
2
2
  module Parallel
3
3
  # Current cdc-parallel version.
4
- VERSION: "0.1.0"
4
+ VERSION: "0.2.0"
5
5
  end
6
6
  end
@@ -0,0 +1,3 @@
1
+ module Timeout
2
+ def self.timeout: (untyped sec, untyped klass) { () -> untyped } -> untyped
3
+ 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.0
4
+ version: 0.2.1
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