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 +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +3 -3
- data/lib/sidekiq/ratomic/pool/version.rb +1 -1
- data/lib/sidekiq/ratomic/pool.rb +71 -11
- data/sig/sidekiq/ratomic/pool.rbs +5 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c1be6120972266a46da7e3b1646f695b015be76d366c231374d47d93aae1ac6
|
|
4
|
+
data.tar.gz: 85b6f68ea11bcc778afd3874551577b364bda3af6b5e8e9084c9e249ee18f85b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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.
|
|
44
|
+
RedisClient.config(url:).new_client
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
data/lib/sidekiq/ratomic/pool.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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: []
|