sidekiq-ratomic-pool 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73e5869196bd322f91cea20c1332e39b2c2e3c9d81e99717ac74210fb766be36
4
- data.tar.gz: 4b830bdc58727ba25cc653011bf52d1394d2344ad5de996a7f20965b9b404e57
3
+ metadata.gz: 3c1be6120972266a46da7e3b1646f695b015be76d366c231374d47d93aae1ac6
4
+ data.tar.gz: 85b6f68ea11bcc778afd3874551577b364bda3af6b5e8e9084c9e249ee18f85b
5
5
  SHA512:
6
- metadata.gz: 6272ccc17b365724a851f3d6b39cb82d4ce109068cc854a640ab0b649f2c062bf4ee6afdeccf0c6e442789dfe03be411eb3bf8abd8dab2da36d8f9b5f8d09e00
7
- data.tar.gz: 71f001b3aca75e43792bf760795d037a6f7a220eb15de33da7b1802eab89e976186ebce147f6eeb21f95e88c702ffd05c8309b06a410831609b58ff613133417
6
+ metadata.gz: 065052f3df563140c3928c37d4c4953ab4c1297760aa046ec6c91cd41e90b6debefa4aea28b13399975c4556b50a59e986127cd70bf4f060652a6579cdc13f5b
7
+ data.tar.gz: a045612004cdd32f615e4dfbb4eacab6240257d06ae68ecaa02d322a479933d24c1f1a202339effc8d61c07f9b9502ccf175adcc337d6e19918e3b1382a52605
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2026-08-25
4
+
5
+ - Added a real Redis-backed `smoke_test/` harness with standalone Sidekiq client
6
+ and server examples, Docker Compose Redis, and process/thread scheduling output.
7
+ - Made the middleware compatible with Sidekiq's per-job middleware instantiation
8
+ by sharing each configured pool runtime across middleware instances.
9
+ - Added support for Sidekiq-style positional middleware options and validation for
10
+ unknown options.
11
+ - Added smoke coverage for multi-threaded job processing across visible CPUs.
12
+
13
+ ## [0.1.0] - 2026-08-24
14
+
3
15
  - Added Sidekiq middleware with Ratomic `LocalPool` resource injection,
4
16
  health validation, exponential retries, and circuit-breaker fast failure.
5
17
  - Added thread-boundary pool behavior tests and synchronized RBS signatures for the
data/README.md CHANGED
@@ -35,13 +35,13 @@ and worker pool accessor pattern.
35
35
 
36
36
  ## Usage
37
37
 
38
- Factories must be Ractor-shareable because `Ratomic::LocalPool` creates resources lazily
39
- inside each Ractor. A small frozen factory object is suitable for production use:
38
+ Factories are made Ractor-shareable because `Ratomic::LocalPool` creates resources
39
+ lazily inside each Ractor. A small frozen factory object is suitable for production use:
40
40
 
41
41
  ```ruby
42
42
  RedisFactory = Data.define(:url) do
43
43
  def call
44
- RedisClient.new(url:)
44
+ RedisClient.config(url:).new_client
45
45
  end
46
46
  end
47
47
 
@@ -6,7 +6,7 @@ module Sidekiq
6
6
  module Ratomic
7
7
  class Pool
8
8
  # Current gem version.
9
- VERSION = '0.1.0'
9
+ VERSION = '0.2.0'
10
10
  end
11
11
  end
12
12
  end
@@ -13,14 +13,29 @@ module Sidekiq
13
13
  #
14
14
  # Resources are validated before checkout and transient failures are retried
15
15
  # with exponential backoff. Persistent failures open the circuit breaker.
16
+ # rubocop:disable Metrics/ClassLength
16
17
  class Pool
17
18
  attr_reader :pool_name, :size, :max_retries, :retry_delay, :validator, :cb_threshold, :cb_timeout,
18
19
  :retryable_errors
19
20
 
20
- def initialize(pool_name:, size: 10, max_retries: 3, retry_delay: 0.2,
21
+ # rubocop:disable Metrics/MethodLength
22
+ def initialize(options = nil, pool_name: nil, size: 10, max_retries: 3, retry_delay: 0.2,
21
23
  cb_threshold: 5, cb_timeout: 30, validator: nil,
22
24
  retryable_errors: [IOError, SystemCallError, Timeout::Error], factory: nil, &block)
25
+ normalize_options!(options) do |config|
26
+ pool_name = config.fetch(:pool_name, pool_name)
27
+ size = config.fetch(:size, size)
28
+ max_retries = config.fetch(:max_retries, max_retries)
29
+ retry_delay = config.fetch(:retry_delay, retry_delay)
30
+ cb_threshold = config.fetch(:cb_threshold, cb_threshold)
31
+ cb_timeout = config.fetch(:cb_timeout, cb_timeout)
32
+ validator = config.fetch(:validator, validator)
33
+ retryable_errors = config.fetch(:retryable_errors, retryable_errors)
34
+ factory = config.fetch(:factory, factory)
35
+ end
36
+
23
37
  factory ||= block
38
+ raise ArgumentError, 'A pool_name must be provided' unless pool_name
24
39
  raise ArgumentError, 'A resource factory must be provided' unless factory
25
40
 
26
41
  validate_options!(size, max_retries, retry_delay, cb_threshold, cb_timeout)
@@ -35,13 +50,37 @@ module Sidekiq
35
50
  @retryable_errors = retryable_errors.freeze
36
51
  @state_mutex = Mutex.new
37
52
  @failure_count = ::Ratomic::Counter.new
38
- @state = :closed
39
- @last_state_change = monotonic_time
53
+ @state_holder = { state: :closed, last_state_change: monotonic_time }
40
54
 
41
55
  shareable_factory = Ractor.make_shareable(factory)
42
56
  @local_pool = ::Ratomic::LocalPool.new(size: @size, factory: shareable_factory)
43
57
  end
44
58
 
59
+ # Share one pool runtime across Sidekiq's per-job middleware instances.
60
+ def config=(config)
61
+ mutex = config.instance_variable_get(:@sidekiq_ratomic_pool_mutex)
62
+ unless mutex
63
+ mutex = Mutex.new
64
+ config.instance_variable_set(:@sidekiq_ratomic_pool_mutex, mutex)
65
+ end
66
+
67
+ runtimes = config.instance_variable_get(:@sidekiq_ratomic_pool_runtimes)
68
+ unless runtimes
69
+ runtimes = {} # : Hash[Symbol, Pool]
70
+ config.instance_variable_set(:@sidekiq_ratomic_pool_runtimes, runtimes)
71
+ end
72
+
73
+ mutex.synchronize do
74
+ runtime = runtimes[@pool_name]
75
+ if runtime
76
+ adopt_runtime(runtime)
77
+ else
78
+ runtimes[@pool_name] = self
79
+ end
80
+ end
81
+ @config = config
82
+ end
83
+
45
84
  # Inject this pool into a worker's configured pool accessor.
46
85
  def call(job_instance, _job_payload, _queue)
47
86
  setter = "#{@pool_name}="
@@ -93,11 +132,11 @@ module Sidekiq
93
132
  # Return the current circuit-breaker state.
94
133
  def state
95
134
  @state_mutex.synchronize do
96
- if @state == :open && monotonic_time - @last_state_change > @cb_timeout
97
- @state = :half_open
98
- @last_state_change = monotonic_time
135
+ if @state_holder[:state] == :open && monotonic_time - @state_holder[:last_state_change] > @cb_timeout
136
+ @state_holder[:state] = :half_open
137
+ @state_holder[:last_state_change] = monotonic_time
99
138
  end
100
- @state
139
+ @state_holder[:state]
101
140
  end
102
141
  end
103
142
 
@@ -136,6 +175,25 @@ module Sidekiq
136
175
  end
137
176
  end
138
177
 
178
+ def normalize_options!(options)
179
+ return unless options
180
+ raise ArgumentError, 'middleware options must be a Hash' unless options.is_a?(Hash)
181
+
182
+ config = options.transform_keys(&:to_sym)
183
+ allowed = %i[pool_name size max_retries retry_delay cb_threshold cb_timeout validator retryable_errors factory]
184
+ unknown = config.keys - allowed
185
+ raise ArgumentError, "unknown middleware options: #{unknown.join(', ')}" unless unknown.empty?
186
+
187
+ yield config
188
+ end
189
+
190
+ def adopt_runtime(runtime)
191
+ @local_pool = runtime.instance_variable_get(:@local_pool)
192
+ @state_mutex = runtime.instance_variable_get(:@state_mutex)
193
+ @failure_count = runtime.instance_variable_get(:@failure_count)
194
+ @state_holder = runtime.instance_variable_get(:@state_holder)
195
+ end
196
+
139
197
  def default_validator(resource)
140
198
  return resource.ping if resource.respond_to?(:ping)
141
199
  return resource.active? if resource.respond_to?(:active?)
@@ -147,16 +205,16 @@ module Sidekiq
147
205
  @state_mutex.synchronize do
148
206
  failure_count = @failure_count.value
149
207
  @failure_count.decrement(failure_count) unless failure_count.zero?
150
- @state = :closed if @state == :half_open
208
+ @state_holder[:state] = :closed if @state_holder[:state] == :half_open
151
209
  end
152
210
  end
153
211
 
154
212
  def record_failure
155
213
  @state_mutex.synchronize do
156
214
  @failure_count.increment(1)
157
- if @failure_count.value >= @cb_threshold || @state == :half_open
158
- @state = :open
159
- @last_state_change = monotonic_time
215
+ if @failure_count.value >= @cb_threshold || @state_holder[:state] == :half_open
216
+ @state_holder[:state] = :open
217
+ @state_holder[:last_state_change] = monotonic_time
160
218
  end
161
219
  end
162
220
  end
@@ -165,5 +223,7 @@ module Sidekiq
165
223
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
166
224
  end
167
225
  end
226
+ # rubocop:enable Metrics/MethodLength
227
+ # rubocop:enable Metrics/ClassLength
168
228
  end
169
229
  end
@@ -22,7 +22,8 @@ module Sidekiq
22
22
  attr_reader retryable_errors: Array[Class]
23
23
 
24
24
  def initialize: (
25
- pool_name: Symbol | String,
25
+ ?untyped options,
26
+ ?pool_name: (Symbol | String),
26
27
  ?size: Integer,
27
28
  ?max_retries: Integer,
28
29
  ?retry_delay: Numeric,
@@ -34,12 +35,15 @@ module Sidekiq
34
35
  ) { () -> untyped } -> void
35
36
 
36
37
  def call: (untyped, untyped, untyped) { () -> untyped } -> untyped
38
+ def config=: (untyped) -> untyped
37
39
  def close: () -> nil
38
40
  def shutdown: () -> nil
39
41
  def with: () { (untyped) -> untyped } -> untyped
40
42
  def state: () -> Symbol
41
43
  private
42
44
  def validate_options!: (untyped, untyped, untyped, untyped, untyped) -> void
45
+ def normalize_options!: (untyped) { (Hash[Symbol, untyped]) -> void } -> void
46
+ def adopt_runtime: (instance) -> void
43
47
  def check_circuit_state!: () -> void
44
48
  def verify_health: (untyped) -> bool
45
49
  def retryable_error?: (StandardError) -> bool
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-ratomic-pool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken C. Demanawa
@@ -66,7 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0'
68
68
  requirements: []
69
- rubygems_version: 4.0.18
69
+ rubygems_version: 4.0.16
70
70
  specification_version: 4
71
71
  summary: Ractor-safe connection pooling for Sidekiq utilizing Ratomic's LocalPool.
72
72
  test_files: []