sidekiq-queue-throttled 1.0.0 → 1.1.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 +10 -0
- data/README.md +40 -4
- data/lib/sidekiq/queue_throttled/job.rb +33 -32
- data/lib/sidekiq/queue_throttled/job_throttler.rb +40 -54
- data/lib/sidekiq/queue_throttled/middleware.rb +46 -40
- data/lib/sidekiq/queue_throttled/queue_limiter.rb +14 -28
- data/lib/sidekiq/queue_throttled/railtie.rb +13 -0
- data/lib/sidekiq/queue_throttled/version.rb +1 -1
- data/lib/sidekiq/queue_throttled.rb +8 -0
- data/spec/examples.txt +110 -105
- data/spec/sidekiq/queue_throttled/queue_limiter_spec.rb +31 -31
- data/spec/sidekiq/queue_throttled/railtie_spec.rb +46 -0
- data/spec/spec_helper.rb +4 -4
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b658041cc8f4b7cb1cd5055c8371e3c672c4f74f9151faf5a67dda68f014d6ac
|
4
|
+
data.tar.gz: 36afa33c4f2250d70bf55c52318fbedbe2a10e6a629c557d676b81bd1fadf485
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f6c4dc62bbd48be8646598d3e8aca5cb3baa2572bd80082c121fe140a7e13369694ef5420ecafd169fceb46344efec250d02a6398b8b7ac955b952faf33b710
|
7
|
+
data.tar.gz: deb6e902be8ea36f1b63a7bf630bcd8c28800360713349fa18a8f4ab07be142638d8437fb8e6e5ec5b7c0501aaab14be23f0a19b06b692fc53d87ba5e6fa995c
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [1.1.3] - 2024-12-19
|
9
|
+
|
10
|
+
### Changed
|
11
|
+
- Re-enabled MFA requirement for RubyGems deployment for enhanced security
|
12
|
+
|
13
|
+
## [1.1.2] - 2024-12-19
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
- Disabled MFA requirement for RubyGems deployment to simplify release process
|
17
|
+
|
8
18
|
## [Unreleased]
|
9
19
|
|
10
20
|
### Added
|
data/README.md
CHANGED
@@ -25,6 +25,18 @@ And then execute:
|
|
25
25
|
$ bundle install
|
26
26
|
```
|
27
27
|
|
28
|
+
### Rails Applications
|
29
|
+
|
30
|
+
For Rails applications, the gem will be automatically loaded when your application starts. No additional configuration is required.
|
31
|
+
|
32
|
+
### Non-Rails Applications
|
33
|
+
|
34
|
+
For non-Rails applications, you need to explicitly require the gem:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'sidekiq/queue_throttled'
|
38
|
+
```
|
39
|
+
|
28
40
|
## Configuration
|
29
41
|
|
30
42
|
### Queue Limits
|
@@ -203,7 +215,7 @@ Sidekiq::QueueThrottled.redis = Redis.new(url: ENV['REDIS_URL'])
|
|
203
215
|
limiter = Sidekiq::QueueThrottled::QueueLimiter.new(queue_name, limit)
|
204
216
|
|
205
217
|
# Acquire a lock (returns lock_id or false)
|
206
|
-
lock_id = limiter.acquire_lock
|
218
|
+
lock_id = limiter.acquire_lock?
|
207
219
|
|
208
220
|
# Release a lock
|
209
221
|
limiter.release_lock(lock_id)
|
@@ -272,9 +284,9 @@ RSpec.describe "Queue Limiting" do
|
|
272
284
|
it "respects queue limits" do
|
273
285
|
limiter = Sidekiq::QueueThrottled::QueueLimiter.new("test_queue", 2)
|
274
286
|
|
275
|
-
expect(limiter.acquire_lock).to be_truthy
|
276
|
-
expect(limiter.acquire_lock).to be_truthy
|
277
|
-
expect(limiter.acquire_lock).to be_falsey # Limit reached
|
287
|
+
expect(limiter.acquire_lock?).to be_truthy
|
288
|
+
expect(limiter.acquire_lock?).to be_truthy
|
289
|
+
expect(limiter.acquire_lock?).to be_falsey # Limit reached
|
278
290
|
end
|
279
291
|
end
|
280
292
|
```
|
@@ -286,6 +298,30 @@ end
|
|
286
298
|
- **Network Latency**: Consider Redis network latency when setting TTL values
|
287
299
|
- **Concurrent Access**: The gem uses thread-safe primitives for concurrent access
|
288
300
|
|
301
|
+
## Troubleshooting
|
302
|
+
|
303
|
+
### "uninitialized constant Sidekiq::QueueThrottled" Error
|
304
|
+
|
305
|
+
If you encounter this error when trying to use the gem:
|
306
|
+
|
307
|
+
```
|
308
|
+
uninitialized constant Sidekiq::QueueThrottled (NameError)
|
309
|
+
```
|
310
|
+
|
311
|
+
**For Rails applications:**
|
312
|
+
- Make sure the gem is properly added to your Gemfile
|
313
|
+
- Restart your Rails server after adding the gem
|
314
|
+
- The gem should auto-load when your Rails application starts
|
315
|
+
|
316
|
+
**For non-Rails applications:**
|
317
|
+
- Explicitly require the gem at the top of your file:
|
318
|
+
```ruby
|
319
|
+
require 'sidekiq/queue_throttled'
|
320
|
+
```
|
321
|
+
|
322
|
+
**For IRB/Console:**
|
323
|
+
- If you're testing in IRB or Rails console, make sure to restart the console after adding the gem to your Gemfile
|
324
|
+
|
289
325
|
## Contributing
|
290
326
|
|
291
327
|
1. Fork the repository
|
@@ -5,12 +5,24 @@ module Sidekiq
|
|
5
5
|
module Job
|
6
6
|
def self.included(base)
|
7
7
|
base.extend(ClassMethods)
|
8
|
+
setup_class_attributes(base)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.setup_class_attributes(base)
|
12
|
+
setup_attr_accessor(base)
|
13
|
+
setup_class_methods(base)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.setup_attr_accessor(base)
|
8
17
|
base.class_eval do
|
9
|
-
# Simple class attribute implementation
|
10
18
|
class << self
|
11
19
|
attr_accessor :sidekiq_throttle_config
|
12
20
|
end
|
21
|
+
end
|
22
|
+
end
|
13
23
|
|
24
|
+
def self.setup_class_methods(base)
|
25
|
+
base.class_eval do
|
14
26
|
def self.sidekiq_throttle_config
|
15
27
|
@sidekiq_throttle_config
|
16
28
|
end
|
@@ -31,50 +43,39 @@ module Sidekiq
|
|
31
43
|
|
32
44
|
def validate_throttle_options!(options)
|
33
45
|
return if options.empty?
|
46
|
+
raise ArgumentError, 'Cannot specify both concurrency and rate limits' if both_limits?(options)
|
34
47
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
if options[:concurrency]
|
40
|
-
validate_concurrency_options!(options[:concurrency])
|
41
|
-
end
|
48
|
+
validate_concurrency_options!(options[:concurrency]) if options[:concurrency]
|
49
|
+
validate_rate_options!(options[:rate]) if options[:rate]
|
50
|
+
end
|
42
51
|
|
43
|
-
|
44
|
-
|
45
|
-
end
|
52
|
+
def both_limits?(options)
|
53
|
+
options[:concurrency] && options[:rate]
|
46
54
|
end
|
47
55
|
|
48
56
|
def validate_concurrency_options!(concurrency)
|
49
|
-
unless concurrency.is_a?(Hash)
|
50
|
-
raise ArgumentError, 'Concurrency must be a hash'
|
51
|
-
end
|
57
|
+
raise ArgumentError, 'Concurrency must be a hash' unless concurrency.is_a?(Hash)
|
52
58
|
|
53
59
|
unless concurrency[:limit].is_a?(Integer) && concurrency[:limit].positive?
|
54
|
-
raise ArgumentError,
|
55
|
-
|
56
|
-
|
57
|
-
unless concurrency[:key_suffix]
|
58
|
-
raise ArgumentError, 'Concurrency key_suffix is required'
|
60
|
+
raise ArgumentError,
|
61
|
+
'Concurrency limit must be a positive integer'
|
59
62
|
end
|
63
|
+
raise ArgumentError, 'Concurrency key_suffix is required' unless concurrency[:key_suffix]
|
60
64
|
end
|
61
65
|
|
62
66
|
def validate_rate_options!(rate)
|
63
|
-
unless rate.is_a?(Hash)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
raise ArgumentError, 'Rate limit must be a positive integer'
|
69
|
-
end
|
67
|
+
raise ArgumentError, 'Rate must be a hash' unless rate.is_a?(Hash)
|
68
|
+
raise ArgumentError, 'Rate limit must be a positive integer' unless valid_rate_limit?(rate)
|
69
|
+
raise ArgumentError, 'Rate period must be a positive integer' unless valid_rate_period?(rate)
|
70
|
+
raise ArgumentError, 'Rate key_suffix is required' unless rate[:key_suffix]
|
71
|
+
end
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
-
|
73
|
+
def valid_rate_limit?(rate)
|
74
|
+
rate[:limit].is_a?(Integer) && rate[:limit].positive?
|
75
|
+
end
|
74
76
|
|
75
|
-
|
76
|
-
|
77
|
-
end
|
77
|
+
def valid_rate_period?(rate)
|
78
|
+
rate[:period].nil? || (rate[:period].is_a?(Integer) && rate[:period].positive?)
|
78
79
|
end
|
79
80
|
end
|
80
81
|
end
|
@@ -2,7 +2,33 @@
|
|
2
2
|
|
3
3
|
module Sidekiq
|
4
4
|
module QueueThrottled
|
5
|
+
module RedisKeyManager
|
6
|
+
def concurrency_key(key_suffix)
|
7
|
+
"#{Sidekiq::QueueThrottled.configuration.redis_key_prefix}:concurrency:#{@job_class}:#{key_suffix}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def rate_key(key_suffix, period)
|
11
|
+
window = Time.now.to_i / period
|
12
|
+
"#{Sidekiq::QueueThrottled.configuration.redis_key_prefix}:rate:#{@job_class}:#{key_suffix}:#{window}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def resolve_key_suffix(key_suffix, args)
|
16
|
+
case key_suffix
|
17
|
+
when Proc
|
18
|
+
key_suffix.call(*args)
|
19
|
+
when Symbol
|
20
|
+
args.first.send(key_suffix) if args.first.respond_to?(key_suffix)
|
21
|
+
when String
|
22
|
+
key_suffix
|
23
|
+
else
|
24
|
+
'default'
|
25
|
+
end.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
5
29
|
class JobThrottler
|
30
|
+
include RedisKeyManager
|
31
|
+
|
6
32
|
attr_reader :job_class, :throttle_config, :redis
|
7
33
|
|
8
34
|
def initialize(job_class, throttle_config, redis = nil)
|
@@ -15,9 +41,7 @@ module Sidekiq
|
|
15
41
|
def can_process?(args)
|
16
42
|
return true unless @throttle_config
|
17
43
|
|
18
|
-
@mutex.with_read_lock
|
19
|
-
check_concurrency_limit(args) && check_rate_limit(args)
|
20
|
-
end
|
44
|
+
@mutex.with_read_lock { concurrency_allowed?(args) && rate_allowed?(args) }
|
21
45
|
end
|
22
46
|
|
23
47
|
def acquire_slot(args)
|
@@ -25,14 +49,10 @@ module Sidekiq
|
|
25
49
|
|
26
50
|
@mutex.with_write_lock do
|
27
51
|
return false unless can_process?(args)
|
52
|
+
return acquire_concurrency_slot?(args) if @throttle_config[:concurrency]
|
53
|
+
return acquire_rate_slot?(args) if @throttle_config[:rate]
|
28
54
|
|
29
|
-
|
30
|
-
acquire_concurrency_slot(args)
|
31
|
-
elsif @throttle_config[:rate]
|
32
|
-
acquire_rate_slot(args)
|
33
|
-
else
|
34
|
-
true
|
35
|
-
end
|
55
|
+
true
|
36
56
|
end
|
37
57
|
end
|
38
58
|
|
@@ -40,12 +60,8 @@ module Sidekiq
|
|
40
60
|
return true unless @throttle_config
|
41
61
|
|
42
62
|
@mutex.with_write_lock do
|
43
|
-
if @throttle_config[:concurrency]
|
44
|
-
release_concurrency_slot(args)
|
45
|
-
end
|
46
|
-
# Rate limiting slots are not released - they expire naturally
|
63
|
+
release_concurrency_slot?(args) if @throttle_config[:concurrency]
|
47
64
|
end
|
48
|
-
|
49
65
|
true
|
50
66
|
rescue StandardError => e
|
51
67
|
Sidekiq::QueueThrottled.logger.error "Failed to release slot for job #{@job_class}: #{e.message}"
|
@@ -54,102 +70,72 @@ module Sidekiq
|
|
54
70
|
|
55
71
|
private
|
56
72
|
|
57
|
-
def
|
73
|
+
def concurrency_allowed?(args)
|
58
74
|
return true unless @throttle_config[:concurrency]
|
59
75
|
|
60
76
|
config = @throttle_config[:concurrency]
|
61
77
|
limit = config[:limit]
|
62
78
|
key_suffix = resolve_key_suffix(config[:key_suffix], args)
|
63
|
-
current_count =
|
64
|
-
|
79
|
+
current_count = concurrency_count(key_suffix)
|
65
80
|
current_count < limit
|
66
81
|
end
|
67
82
|
|
68
|
-
def
|
83
|
+
def rate_allowed?(args)
|
69
84
|
return true unless @throttle_config[:rate]
|
70
85
|
|
71
86
|
config = @throttle_config[:rate]
|
72
87
|
limit = config[:limit]
|
73
88
|
period = config[:period] || 60
|
74
89
|
key_suffix = resolve_key_suffix(config[:key_suffix], args)
|
75
|
-
|
76
|
-
current_count = get_rate_count(key_suffix, period)
|
90
|
+
current_count = rate_count(key_suffix, period)
|
77
91
|
current_count < limit
|
78
92
|
end
|
79
93
|
|
80
|
-
def acquire_concurrency_slot(args)
|
94
|
+
def acquire_concurrency_slot?(args)
|
81
95
|
config = @throttle_config[:concurrency]
|
82
96
|
key_suffix = resolve_key_suffix(config[:key_suffix], args)
|
83
97
|
key = concurrency_key(key_suffix)
|
84
|
-
|
85
98
|
@redis.multi do |multi|
|
86
99
|
multi.incr(key)
|
87
100
|
multi.expire(key, Sidekiq::QueueThrottled.configuration.throttle_ttl)
|
88
101
|
end
|
89
|
-
|
90
102
|
true
|
91
103
|
end
|
92
104
|
|
93
|
-
def release_concurrency_slot(args)
|
105
|
+
def release_concurrency_slot?(args)
|
94
106
|
config = @throttle_config[:concurrency]
|
95
107
|
key_suffix = resolve_key_suffix(config[:key_suffix], args)
|
96
108
|
key = concurrency_key(key_suffix)
|
97
|
-
|
98
109
|
@redis.multi do |multi|
|
99
110
|
multi.decr(key)
|
100
111
|
multi.expire(key, Sidekiq::QueueThrottled.configuration.throttle_ttl)
|
101
112
|
end
|
102
|
-
|
103
113
|
true
|
104
114
|
end
|
105
115
|
|
106
|
-
def acquire_rate_slot(args)
|
116
|
+
def acquire_rate_slot?(args)
|
107
117
|
config = @throttle_config[:rate]
|
108
118
|
period = config[:period] || 60
|
109
119
|
key_suffix = resolve_key_suffix(config[:key_suffix], args)
|
110
120
|
key = rate_key(key_suffix, period)
|
111
|
-
|
112
121
|
@redis.multi do |multi|
|
113
122
|
multi.incr(key)
|
114
123
|
multi.expire(key, period)
|
115
124
|
end
|
116
|
-
|
117
125
|
true
|
118
126
|
end
|
119
127
|
|
120
|
-
def
|
128
|
+
def concurrency_count(key_suffix)
|
121
129
|
key = concurrency_key(key_suffix)
|
122
130
|
count = @redis.get(key)
|
123
131
|
count ? count.to_i : 0
|
124
132
|
end
|
125
133
|
|
126
|
-
def
|
134
|
+
def rate_count(key_suffix, period)
|
127
135
|
key = rate_key(key_suffix, period)
|
128
136
|
count = @redis.get(key)
|
129
137
|
count ? count.to_i : 0
|
130
138
|
end
|
131
|
-
|
132
|
-
def concurrency_key(key_suffix)
|
133
|
-
"#{Sidekiq::QueueThrottled.configuration.redis_key_prefix}:concurrency:#{@job_class}:#{key_suffix}"
|
134
|
-
end
|
135
|
-
|
136
|
-
def rate_key(key_suffix, period)
|
137
|
-
window = Time.now.to_i / period
|
138
|
-
"#{Sidekiq::QueueThrottled.configuration.redis_key_prefix}:rate:#{@job_class}:#{key_suffix}:#{window}"
|
139
|
-
end
|
140
|
-
|
141
|
-
def resolve_key_suffix(key_suffix, args)
|
142
|
-
case key_suffix
|
143
|
-
when Proc
|
144
|
-
key_suffix.call(*args)
|
145
|
-
when Symbol
|
146
|
-
args.first.send(key_suffix) if args.first.respond_to?(key_suffix)
|
147
|
-
when String
|
148
|
-
key_suffix
|
149
|
-
else
|
150
|
-
'default'
|
151
|
-
end.to_s
|
152
|
-
end
|
153
139
|
end
|
154
140
|
end
|
155
141
|
end
|
@@ -8,84 +8,90 @@ module Sidekiq
|
|
8
8
|
@job_throttlers = Concurrent::Map.new
|
9
9
|
end
|
10
10
|
|
11
|
-
def call(worker, job, queue)
|
11
|
+
def call(worker, job, queue, &block)
|
12
12
|
queue_name = job['queue'] || queue
|
13
13
|
job_class = worker.class.name
|
14
14
|
|
15
|
-
|
15
|
+
return reschedule_and_return(job, queue_name, 'queue') unless queue_slot_available?(queue_name, job)
|
16
|
+
return reschedule_and_return(job, queue_name, 'job') unless job_slot_available?(job_class, job['args'], job)
|
17
|
+
|
18
|
+
process_job(job, queue_name, job_class, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def queue_slot_available?(queue_name, job)
|
16
24
|
queue_limiter = get_queue_limiter(queue_name)
|
17
|
-
|
18
|
-
lock_id = queue_limiter.acquire_lock
|
19
|
-
unless lock_id
|
20
|
-
Sidekiq::QueueThrottled.logger.info "Queue limit reached for #{queue_name}, rescheduling job"
|
21
|
-
reschedule_job(job, queue_name)
|
22
|
-
return nil
|
23
|
-
end
|
24
|
-
end
|
25
|
+
return true unless queue_limiter
|
25
26
|
|
26
|
-
|
27
|
+
lock_id = queue_limiter.acquire_lock?
|
28
|
+
job['lock_id'] = lock_id if lock_id
|
29
|
+
!!lock_id
|
30
|
+
end
|
31
|
+
|
32
|
+
def job_slot_available?(job_class, args, job)
|
27
33
|
job_throttler = get_job_throttler(job_class)
|
28
|
-
|
29
|
-
Sidekiq::QueueThrottled.logger.info "Job throttling limit reached for #{job_class}, rescheduling job"
|
30
|
-
reschedule_job(job, queue_name)
|
31
|
-
return nil
|
32
|
-
end
|
34
|
+
return true unless job_throttler
|
33
35
|
|
34
|
-
|
36
|
+
acquired = job_throttler.acquire_slot(args)
|
37
|
+
job['job_throttle_acquired'] = acquired
|
38
|
+
acquired
|
39
|
+
end
|
40
|
+
|
41
|
+
def process_job(job, queue_name, job_class, &_block)
|
42
|
+
queue_limiter = get_queue_limiter(queue_name)
|
43
|
+
job_throttler = get_job_throttler(job_class)
|
44
|
+
lock_id = job['lock_id']
|
35
45
|
begin
|
36
46
|
yield
|
37
47
|
ensure
|
38
|
-
# Release locks
|
39
48
|
queue_limiter&.release_lock(lock_id)
|
40
49
|
job_throttler&.release_slot(job['args'])
|
41
50
|
end
|
42
51
|
end
|
43
52
|
|
44
|
-
|
53
|
+
def reschedule_and_return(job, queue_name, type)
|
54
|
+
msg = type == 'queue' ? 'Queue limit reached' : 'Job throttling limit reached'
|
55
|
+
Sidekiq::QueueThrottled.logger.info "#{msg} for #{queue_name}, rescheduling job"
|
56
|
+
reschedule_job(job, queue_name)
|
57
|
+
nil
|
58
|
+
end
|
45
59
|
|
46
60
|
def get_queue_limiter(queue_name)
|
47
61
|
limit = Sidekiq::QueueThrottled.configuration.queue_limit(queue_name)
|
48
62
|
return nil unless limit
|
49
63
|
|
50
|
-
@queue_limiters.compute_if_absent(queue_name)
|
51
|
-
QueueLimiter.new(queue_name, limit)
|
52
|
-
end
|
64
|
+
@queue_limiters.compute_if_absent(queue_name) { QueueLimiter.new(queue_name, limit) }
|
53
65
|
end
|
54
66
|
|
55
67
|
def get_job_throttler(job_class)
|
56
|
-
throttle_config =
|
68
|
+
throttle_config = throttle_config_for(job_class)
|
57
69
|
return nil unless throttle_config
|
58
70
|
|
59
|
-
@job_throttlers.compute_if_absent(job_class)
|
60
|
-
JobThrottler.new(job_class, throttle_config)
|
61
|
-
end
|
71
|
+
@job_throttlers.compute_if_absent(job_class) { JobThrottler.new(job_class, throttle_config) }
|
62
72
|
end
|
63
73
|
|
64
|
-
def
|
65
|
-
# Handle string class names
|
74
|
+
def throttle_config_for(job_class)
|
66
75
|
if job_class.is_a?(String)
|
67
|
-
|
68
|
-
|
69
|
-
return klass.sidekiq_throttle_config if klass.respond_to?(:sidekiq_throttle_config)
|
70
|
-
rescue NameError
|
71
|
-
# For test classes that don't have proper constant names
|
72
|
-
return nil
|
73
|
-
end
|
76
|
+
klass = safe_const_get(job_class)
|
77
|
+
return klass.sidekiq_throttle_config if klass.respond_to?(:sidekiq_throttle_config)
|
74
78
|
elsif job_class.respond_to?(:sidekiq_throttle_config)
|
75
|
-
# Handle actual class objects
|
76
79
|
return job_class.sidekiq_throttle_config
|
77
80
|
end
|
78
81
|
nil
|
79
82
|
end
|
80
83
|
|
84
|
+
def safe_const_get(class_name)
|
85
|
+
Object.const_get(class_name)
|
86
|
+
rescue NameError
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
|
81
90
|
def reschedule_job(job, queue_name)
|
82
91
|
delay = Sidekiq::QueueThrottled.configuration.retry_delay
|
83
92
|
job['at'] = Time.now.to_f + delay
|
84
93
|
job['queue'] = queue_name
|
85
|
-
|
86
|
-
Sidekiq.redis do |conn|
|
87
|
-
conn.zadd('schedule', job['at'], job.to_json)
|
88
|
-
end
|
94
|
+
Sidekiq.redis { |conn| conn.zadd('schedule', job['at'], job.to_json) }
|
89
95
|
end
|
90
96
|
end
|
91
97
|
end
|
@@ -14,42 +14,28 @@ module Sidekiq
|
|
14
14
|
@mutex = Concurrent::ReentrantReadWriteLock.new
|
15
15
|
end
|
16
16
|
|
17
|
-
def acquire_lock(worker_id = nil)
|
17
|
+
def acquire_lock?(worker_id = nil)
|
18
18
|
worker_id ||= SecureRandom.uuid
|
19
19
|
lock_id = "#{worker_id}:#{Time.now.to_f}"
|
20
|
-
|
21
20
|
@mutex.with_write_lock do
|
22
|
-
|
23
|
-
puts "DEBUG: QueueLimiter - current_count: #{current_count}, limit: #{@limit}"
|
24
|
-
return false if current_count >= @limit
|
21
|
+
return false if limit_reached?
|
25
22
|
|
26
|
-
# Increment the counter first
|
27
23
|
increment_counter
|
28
|
-
|
29
|
-
return lock_id
|
24
|
+
lock_id
|
30
25
|
end
|
31
|
-
|
32
|
-
false
|
33
26
|
end
|
34
27
|
|
35
28
|
def release_lock(lock_id)
|
36
29
|
return false unless lock_id
|
37
30
|
|
38
|
-
@mutex.with_write_lock
|
39
|
-
# For time-based limiting, we don't immediately decrement the counter
|
40
|
-
# The counter will expire naturally after the TTL period
|
41
|
-
# This prevents immediate reuse of slots
|
42
|
-
true
|
43
|
-
end
|
31
|
+
@mutex.with_write_lock { true }
|
44
32
|
rescue StandardError => e
|
45
33
|
Sidekiq::QueueThrottled.logger.error "Failed to release lock #{lock_id} for queue #{@queue_name}: #{e.message}"
|
46
34
|
false
|
47
35
|
end
|
48
36
|
|
49
37
|
def current_count
|
50
|
-
@mutex.with_read_lock
|
51
|
-
get_current_count
|
52
|
-
end
|
38
|
+
@mutex.with_read_lock { fetch_current_count }
|
53
39
|
end
|
54
40
|
|
55
41
|
def available_slots
|
@@ -59,7 +45,6 @@ module Sidekiq
|
|
59
45
|
def reset!
|
60
46
|
@mutex.with_write_lock do
|
61
47
|
@redis.del(@counter_key)
|
62
|
-
# Clear all locks for this queue
|
63
48
|
pattern = "#{Sidekiq::QueueThrottled.configuration.redis_key_prefix}:queue:#{@queue_name}:lock:*"
|
64
49
|
keys = @redis.keys(pattern)
|
65
50
|
@redis.del(*keys) unless keys.empty?
|
@@ -68,7 +53,11 @@ module Sidekiq
|
|
68
53
|
|
69
54
|
private
|
70
55
|
|
71
|
-
def
|
56
|
+
def limit_reached?
|
57
|
+
fetch_current_count >= @limit
|
58
|
+
end
|
59
|
+
|
60
|
+
def fetch_current_count
|
72
61
|
count = @redis.get(@counter_key)
|
73
62
|
result = count ? count.to_i : 0
|
74
63
|
puts "DEBUG: get_current_count - key: #{@counter_key}, count: #{result}"
|
@@ -81,7 +70,7 @@ module Sidekiq
|
|
81
70
|
multi.incr(@counter_key)
|
82
71
|
multi.expire(@counter_key, Sidekiq::QueueThrottled.configuration.throttle_ttl)
|
83
72
|
end
|
84
|
-
puts "DEBUG: increment_counter - after increment, count: #{
|
73
|
+
puts "DEBUG: increment_counter - after increment, count: #{fetch_current_count}"
|
85
74
|
end
|
86
75
|
|
87
76
|
def decrement_counter
|
@@ -89,11 +78,8 @@ module Sidekiq
|
|
89
78
|
multi.decr(@counter_key)
|
90
79
|
multi.expire(@counter_key, Sidekiq::QueueThrottled.configuration.throttle_ttl)
|
91
80
|
end
|
92
|
-
|
93
|
-
|
94
|
-
if current.negative?
|
95
|
-
@redis.set(@counter_key, 0)
|
96
|
-
end
|
81
|
+
current = fetch_current_count
|
82
|
+
@redis.set(@counter_key, 0) if current.negative?
|
97
83
|
end
|
98
84
|
|
99
85
|
def acquire_redis_lock(lock_id)
|
@@ -101,7 +87,7 @@ module Sidekiq
|
|
101
87
|
@redis.set(lock_key, '1', nx: true, ex: Sidekiq::QueueThrottled.configuration.lock_ttl)
|
102
88
|
end
|
103
89
|
|
104
|
-
def release_redis_lock(lock_id)
|
90
|
+
def release_redis_lock?(lock_id)
|
105
91
|
lock_key = "#{@lock_key}:#{lock_id}"
|
106
92
|
@redis.del(lock_key).positive?
|
107
93
|
end
|
@@ -13,6 +13,14 @@ require_relative 'queue_throttled/job_throttler'
|
|
13
13
|
require_relative 'queue_throttled/middleware'
|
14
14
|
require_relative 'queue_throttled/job'
|
15
15
|
|
16
|
+
# Auto-load Rails integration if Rails is available
|
17
|
+
begin
|
18
|
+
require 'rails'
|
19
|
+
require_relative 'queue_throttled/railtie'
|
20
|
+
rescue LoadError
|
21
|
+
# Rails is not available, which is fine for non-Rails applications
|
22
|
+
end
|
23
|
+
|
16
24
|
module Sidekiq
|
17
25
|
module QueueThrottled
|
18
26
|
class << self
|
data/spec/examples.txt
CHANGED
@@ -1,110 +1,115 @@
|
|
1
1
|
example_id | status | run_time |
|
2
2
|
------------------------------------------------------------- | ------ | --------------- |
|
3
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:1:1] | passed | 0.
|
4
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:1:2] | passed | 0.
|
5
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:1:3] | passed | 0.
|
3
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:1:1] | passed | 0.0019 seconds |
|
4
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:1:2] | passed | 0.00016 seconds |
|
5
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:1:3] | passed | 0.00017 seconds |
|
6
6
|
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:2:1] | passed | 0.0001 seconds |
|
7
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:2:2] | passed | 0.
|
8
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:2:3] | passed | 0.
|
9
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:3:1] | passed | 0.
|
10
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:3:2] | passed | 0.
|
11
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:3:3] | passed | 0.
|
12
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:4:1] | passed | 0.
|
13
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:4:2] | passed | 0.
|
14
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:4:3] | passed | 0.
|
15
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:1] | passed | 0.
|
16
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:2] | passed | 0.
|
17
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:3] | passed | 0.
|
18
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:4] | passed | 0.
|
19
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:1] | passed | 0.
|
20
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:2] | passed | 0.
|
21
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:3] | passed | 0.
|
22
|
-
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:4] | passed | 0.
|
23
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:1:1] | passed | 0.
|
24
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:1:2] | passed | 0.
|
25
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:1] | passed | 0.
|
26
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:2] | passed | 0.
|
27
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:3] | passed | 0.
|
28
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:4] | passed | 0.
|
29
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:5] | passed | 0.
|
30
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:6] | passed | 0.
|
31
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:7] | passed | 0.
|
32
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:8] | passed | 0.
|
33
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:9] | passed | 0.
|
34
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:10] | passed | 0.
|
35
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:11] | passed | 0.
|
36
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:12] | passed | 0.
|
37
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:13] | passed | 0.
|
38
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:3:1] | passed | 0.
|
39
|
-
./spec/sidekiq/queue_throttled/job_spec.rb[1:3:2] | passed | 0.
|
40
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:1:1] | passed | 0.
|
41
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:1:2] | passed | 0.
|
42
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:1:1] | passed | 0.
|
43
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:2:1] | passed | 0.
|
44
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:2:2] | passed | 0.
|
45
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:2:3] | passed | 0.
|
46
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:3:1] | passed | 0.
|
47
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:3:2] | passed | 0.
|
48
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:3:3] | passed | 0.
|
49
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:4:1] | passed | 0.
|
50
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:1:1] | passed | 0.
|
51
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:2:1] | passed | 0.
|
52
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:2:2] | passed | 0.
|
53
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:2:3] | passed | 0.
|
54
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:3:1] | passed | 0.
|
55
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:3:2] | passed | 0.
|
56
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:1:1] | passed | 0.
|
57
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:2:1] | passed | 0.
|
58
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:2:2] | passed | 0.
|
59
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:3:1] | passed | 0.
|
60
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:1:1] | passed | 0.
|
61
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:2:1] | passed | 0.
|
7
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:2:2] | passed | 0.00017 seconds |
|
8
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:2:3] | passed | 0.00057 seconds |
|
9
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:3:1] | passed | 0.00016 seconds |
|
10
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:3:2] | passed | 0.0001 seconds |
|
11
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:3:3] | passed | 0.00098 seconds |
|
12
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:4:1] | passed | 0.00046 seconds |
|
13
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:4:2] | passed | 0.00045 seconds |
|
14
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:4:3] | passed | 0.0121 seconds |
|
15
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:1] | passed | 0.00082 seconds |
|
16
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:2] | passed | 0.00016 seconds |
|
17
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:3] | passed | 0.00021 seconds |
|
18
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:4] | passed | 0.00016 seconds |
|
19
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:1] | passed | 0.00014 seconds |
|
20
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:2] | passed | 0.00016 seconds |
|
21
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:3] | passed | 0.00013 seconds |
|
22
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:4] | passed | 0.0001 seconds |
|
23
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:1:1] | passed | 0.00056 seconds |
|
24
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:1:2] | passed | 0.0002 seconds |
|
25
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:1] | passed | 0.00014 seconds |
|
26
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:2] | passed | 0.00013 seconds |
|
27
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:3] | passed | 0.00013 seconds |
|
28
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:4] | passed | 0.00019 seconds |
|
29
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:5] | passed | 0.00012 seconds |
|
30
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:6] | passed | 0.00012 seconds |
|
31
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:7] | passed | 0.00011 seconds |
|
32
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:8] | passed | 0.00014 seconds |
|
33
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:9] | passed | 0.00011 seconds |
|
34
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:10] | passed | 0.00013 seconds |
|
35
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:11] | passed | 0.00011 seconds |
|
36
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:12] | passed | 0.00013 seconds |
|
37
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:13] | passed | 0.00013 seconds |
|
38
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:3:1] | passed | 0.00073 seconds |
|
39
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:3:2] | passed | 0.0015 seconds |
|
40
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:1:1] | passed | 0.00014 seconds |
|
41
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:1:2] | passed | 0.00016 seconds |
|
42
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:1:1] | passed | 0.00017 seconds |
|
43
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:2:1] | passed | 0.00024 seconds |
|
44
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:2:2] | passed | 0.0009 seconds |
|
45
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:2:3] | passed | 0.0007 seconds |
|
46
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:3:1] | passed | 0.00026 seconds |
|
47
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:3:2] | passed | 0.00076 seconds |
|
48
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:3:3] | passed | 0.00075 seconds |
|
49
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:4:1] | passed | 0.00061 seconds |
|
50
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:1:1] | passed | 0.00013 seconds |
|
51
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:2:1] | passed | 0.00046 seconds |
|
52
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:2:2] | passed | 0.00071 seconds |
|
53
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:2:3] | passed | 0.00078 seconds |
|
54
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:3:1] | passed | 0.00037 seconds |
|
55
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:3:2] | passed | 0.00068 seconds |
|
56
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:1:1] | passed | 0.00012 seconds |
|
57
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:2:1] | passed | 0.0006 seconds |
|
58
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:2:2] | passed | 0.0051 seconds |
|
59
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:3:1] | passed | 0.00011 seconds |
|
60
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:1:1] | passed | 0.0006 seconds |
|
61
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:2:1] | passed | 0.00056 seconds |
|
62
62
|
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:2:2] | passed | 0.00024 seconds |
|
63
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:3:1] | passed | 0.
|
64
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:4:1] | passed | 0.
|
65
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:6:1] | passed | 0.
|
66
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:6:2] | passed | 0.
|
67
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:6:3] | passed | 0.
|
68
|
-
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:7:1] | passed | 0.
|
69
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:1:1] | passed | 0.
|
70
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:2:1] | passed | 0.
|
71
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:2:2] | passed | 0.
|
72
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:3:1] | passed | 0.
|
73
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:3:2] | passed | 0.
|
74
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:3:3] | passed | 0.
|
75
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:4:1] | passed | 0.
|
76
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:5:1] | passed | 0.
|
77
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:5:2] | passed | 0.
|
78
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:2:1] | passed | 0.
|
79
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:2:2] | passed | 0.
|
80
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:2:3] | passed | 0.
|
81
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:3:1] | passed | 0.
|
82
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:3:2] | passed | 0.
|
83
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:3:3] | passed | 0.
|
84
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:4:1] | passed | 0.
|
85
|
-
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:4:2] | passed | 0.
|
86
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:1:1] | passed | 0.
|
87
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:1:2] | passed | 0.
|
88
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:1:3] | passed | 0.
|
89
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:1] | passed | 0.
|
90
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:2] | passed | 0.
|
91
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:3] | passed | 0.
|
92
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:4] | passed | 0.
|
93
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:5] | passed | 0.
|
94
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:3:1] | passed | 0.
|
95
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:3:2] | passed | 0.
|
63
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:3:1] | passed | 0.00048 seconds |
|
64
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:4:1] | passed | 0.00051 seconds |
|
65
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:6:1] | passed | 0.00092 seconds |
|
66
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:6:2] | passed | 0.00057 seconds |
|
67
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:6:3] | passed | 0.00045 seconds |
|
68
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:7:1] | passed | 0.00121 seconds |
|
69
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:1:1] | passed | 0.00052 seconds |
|
70
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:2:1] | passed | 0.00054 seconds |
|
71
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:2:2] | passed | 0.0011 seconds |
|
72
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:3:1] | passed | 0.00114 seconds |
|
73
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:3:2] | passed | 0.0028 seconds |
|
74
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:3:3] | passed | 0.0016 seconds |
|
75
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:4:1] | passed | 0.0012 seconds |
|
76
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:5:1] | passed | 0.00116 seconds |
|
77
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:5:2] | passed | 0.00146 seconds |
|
78
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:2:1] | passed | 0.00013 seconds |
|
79
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:2:2] | passed | 0.00014 seconds |
|
80
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:2:3] | passed | 0.00013 seconds |
|
81
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:3:1] | passed | 0.00013 seconds |
|
82
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:3:2] | passed | 0.00018 seconds |
|
83
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:3:3] | passed | 0.00018 seconds |
|
84
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:4:1] | passed | 0.00052 seconds |
|
85
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:4:2] | passed | 0.00094 seconds |
|
86
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:1:1] | passed | 0.00012 seconds |
|
87
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:1:2] | passed | 0.0001 seconds |
|
88
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:1:3] | passed | 0.0002 seconds |
|
89
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:1] | passed | 0.00067 seconds |
|
90
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:2] | passed | 0.001 seconds |
|
91
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:3] | passed | 0.0013 seconds |
|
92
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:4] | passed | 0.00281 seconds |
|
93
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:5] | passed | 0.00075 seconds |
|
94
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:3:1] | passed | 0.00038 seconds |
|
95
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:3:2] | passed | 0.00011 seconds |
|
96
96
|
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:3:3] | passed | 0.00016 seconds |
|
97
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:3:4] | passed | 0.
|
98
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:4:1] | passed | 0.
|
99
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:4:2] | passed | 0.
|
100
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:4:3] | passed | 0.
|
101
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:1] | passed | 0.
|
102
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:2] | passed | 0.
|
103
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:3] | passed | 0.
|
104
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:4] | passed | 0.
|
105
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:6:1] | passed | 0.
|
106
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:6:2] | passed | 0.
|
107
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:7:1] | passed | 0.
|
108
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:7:2] | passed | 0.
|
109
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:8:1] | passed | 0.
|
110
|
-
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:8:2] | passed | 0.
|
97
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:3:4] | passed | 0.00041 seconds |
|
98
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:4:1] | passed | 0.00016 seconds |
|
99
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:4:2] | passed | 0.00058 seconds |
|
100
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:4:3] | passed | 0.00063 seconds |
|
101
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:1] | passed | 0.00023 seconds |
|
102
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:2] | passed | 0.00067 seconds |
|
103
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:3] | passed | 0.0013 seconds |
|
104
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:4] | passed | 0.00024 seconds |
|
105
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:6:1] | passed | 0.00069 seconds |
|
106
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:6:2] | passed | 0.00158 seconds |
|
107
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:7:1] | passed | 0.00158 seconds |
|
108
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:7:2] | passed | 0.00131 seconds |
|
109
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:8:1] | passed | 0.00045 seconds |
|
110
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:8:2] | passed | 0.0008 seconds |
|
111
|
+
./spec/sidekiq/queue_throttled/railtie_spec.rb[1:1:1] | failed | 0.00014 seconds |
|
112
|
+
./spec/sidekiq/queue_throttled/railtie_spec.rb[1:1:2] | failed | 0.00011 seconds |
|
113
|
+
./spec/sidekiq/queue_throttled/railtie_spec.rb[1:1:3] | failed | 0.00011 seconds |
|
114
|
+
./spec/sidekiq/queue_throttled/railtie_spec.rb[1:2:1] | failed | 0.00128 seconds |
|
115
|
+
./spec/sidekiq/queue_throttled/railtie_spec.rb[1:2:2] | failed | 0.00021 seconds |
|
@@ -25,17 +25,17 @@ RSpec.describe Sidekiq::QueueThrottled::QueueLimiter do
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
describe '#acquire_lock' do
|
28
|
+
describe '#acquire_lock?' do
|
29
29
|
it 'acquires lock when under limit' do
|
30
|
-
lock_id = limiter.acquire_lock
|
30
|
+
lock_id = limiter.acquire_lock?
|
31
31
|
expect(lock_id).to be_truthy
|
32
32
|
expect(limiter.current_count).to eq(1)
|
33
33
|
end
|
34
34
|
|
35
35
|
it 'acquires multiple locks up to limit' do
|
36
|
-
lock1 = limiter.acquire_lock
|
37
|
-
lock2 = limiter.acquire_lock
|
38
|
-
lock3 = limiter.acquire_lock
|
36
|
+
lock1 = limiter.acquire_lock?
|
37
|
+
lock2 = limiter.acquire_lock?
|
38
|
+
lock3 = limiter.acquire_lock?
|
39
39
|
|
40
40
|
expect(lock1).to be_truthy
|
41
41
|
expect(lock2).to be_truthy
|
@@ -44,31 +44,31 @@ RSpec.describe Sidekiq::QueueThrottled::QueueLimiter do
|
|
44
44
|
end
|
45
45
|
|
46
46
|
it 'fails to acquire lock when at limit' do
|
47
|
-
limiter.acquire_lock
|
48
|
-
limiter.acquire_lock
|
49
|
-
limiter.acquire_lock
|
47
|
+
limiter.acquire_lock?
|
48
|
+
limiter.acquire_lock?
|
49
|
+
limiter.acquire_lock?
|
50
50
|
|
51
|
-
lock4 = limiter.acquire_lock
|
51
|
+
lock4 = limiter.acquire_lock?
|
52
52
|
expect(lock4).to be_falsey
|
53
53
|
expect(limiter.current_count).to eq(3)
|
54
54
|
end
|
55
55
|
|
56
56
|
it 'uses provided worker_id' do
|
57
57
|
worker_id = 'worker_123'
|
58
|
-
lock_id = limiter.acquire_lock(worker_id)
|
58
|
+
lock_id = limiter.acquire_lock?(worker_id)
|
59
59
|
expect(lock_id).to include(worker_id)
|
60
60
|
end
|
61
61
|
|
62
62
|
it 'generates unique lock_id for each call' do
|
63
|
-
lock1 = limiter.acquire_lock
|
64
|
-
lock2 = limiter.acquire_lock
|
63
|
+
lock1 = limiter.acquire_lock?
|
64
|
+
lock2 = limiter.acquire_lock?
|
65
65
|
expect(lock1).not_to eq(lock2)
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
69
|
describe '#release_lock' do
|
70
70
|
it 'releases lock and decrements counter' do
|
71
|
-
lock_id = limiter.acquire_lock
|
71
|
+
lock_id = limiter.acquire_lock?
|
72
72
|
expect(limiter.current_count).to eq(1)
|
73
73
|
|
74
74
|
result = limiter.release_lock(lock_id)
|
@@ -90,7 +90,7 @@ RSpec.describe Sidekiq::QueueThrottled::QueueLimiter do
|
|
90
90
|
it 'handles redis errors gracefully' do
|
91
91
|
allow(limiter.redis).to receive(:del).and_raise(Redis::BaseError.new('Connection error'))
|
92
92
|
|
93
|
-
lock_id = limiter.acquire_lock
|
93
|
+
lock_id = limiter.acquire_lock?
|
94
94
|
result = limiter.release_lock(lock_id)
|
95
95
|
expect(result).to be_truthy
|
96
96
|
end
|
@@ -102,14 +102,14 @@ RSpec.describe Sidekiq::QueueThrottled::QueueLimiter do
|
|
102
102
|
end
|
103
103
|
|
104
104
|
it 'returns correct count after acquiring locks' do
|
105
|
-
limiter.acquire_lock
|
106
|
-
limiter.acquire_lock
|
105
|
+
limiter.acquire_lock?
|
106
|
+
limiter.acquire_lock?
|
107
107
|
expect(limiter.current_count).to eq(2)
|
108
108
|
end
|
109
109
|
|
110
110
|
it 'returns correct count after releasing locks' do
|
111
|
-
lock1 = limiter.acquire_lock
|
112
|
-
limiter.acquire_lock
|
111
|
+
lock1 = limiter.acquire_lock?
|
112
|
+
limiter.acquire_lock?
|
113
113
|
limiter.release_lock(lock1)
|
114
114
|
expect(limiter.current_count).to eq(2)
|
115
115
|
end
|
@@ -121,14 +121,14 @@ RSpec.describe Sidekiq::QueueThrottled::QueueLimiter do
|
|
121
121
|
end
|
122
122
|
|
123
123
|
it 'returns correct available slots after acquiring locks' do
|
124
|
-
limiter.acquire_lock
|
124
|
+
limiter.acquire_lock?
|
125
125
|
expect(limiter.available_slots).to eq(2)
|
126
126
|
end
|
127
127
|
|
128
128
|
it 'returns 0 when at limit' do
|
129
|
-
limiter.acquire_lock
|
130
|
-
limiter.acquire_lock
|
131
|
-
limiter.acquire_lock
|
129
|
+
limiter.acquire_lock?
|
130
|
+
limiter.acquire_lock?
|
131
|
+
limiter.acquire_lock?
|
132
132
|
expect(limiter.available_slots).to eq(0)
|
133
133
|
end
|
134
134
|
|
@@ -141,7 +141,7 @@ RSpec.describe Sidekiq::QueueThrottled::QueueLimiter do
|
|
141
141
|
|
142
142
|
describe '#reset!' do
|
143
143
|
it 'resets counter and lock' do
|
144
|
-
limiter.acquire_lock
|
144
|
+
limiter.acquire_lock?
|
145
145
|
expect(limiter.current_count).to eq(1)
|
146
146
|
|
147
147
|
limiter.reset!
|
@@ -149,13 +149,13 @@ RSpec.describe Sidekiq::QueueThrottled::QueueLimiter do
|
|
149
149
|
end
|
150
150
|
|
151
151
|
it 'allows acquiring locks after reset' do
|
152
|
-
limiter.acquire_lock
|
153
|
-
limiter.acquire_lock
|
154
|
-
limiter.acquire_lock
|
152
|
+
limiter.acquire_lock?
|
153
|
+
limiter.acquire_lock?
|
154
|
+
limiter.acquire_lock?
|
155
155
|
expect(limiter.current_count).to eq(3)
|
156
156
|
|
157
157
|
limiter.reset!
|
158
|
-
lock_id = limiter.acquire_lock
|
158
|
+
lock_id = limiter.acquire_lock?
|
159
159
|
expect(lock_id).to be_truthy
|
160
160
|
expect(limiter.current_count).to eq(1)
|
161
161
|
end
|
@@ -168,7 +168,7 @@ RSpec.describe Sidekiq::QueueThrottled::QueueLimiter do
|
|
168
168
|
|
169
169
|
5.times do
|
170
170
|
threads << Thread.new do
|
171
|
-
lock_id = limiter.acquire_lock
|
171
|
+
lock_id = limiter.acquire_lock?
|
172
172
|
lock_ids << lock_id if lock_id
|
173
173
|
end
|
174
174
|
end
|
@@ -183,7 +183,7 @@ RSpec.describe Sidekiq::QueueThrottled::QueueLimiter do
|
|
183
183
|
|
184
184
|
it 'handles concurrent releases' do
|
185
185
|
lock_ids = []
|
186
|
-
3.times { lock_ids << limiter.acquire_lock }
|
186
|
+
3.times { lock_ids << limiter.acquire_lock? }
|
187
187
|
|
188
188
|
threads = lock_ids.map do |lock_id|
|
189
189
|
Thread.new do
|
@@ -199,7 +199,7 @@ RSpec.describe Sidekiq::QueueThrottled::QueueLimiter do
|
|
199
199
|
|
200
200
|
describe 'redis key management' do
|
201
201
|
it 'uses correct redis key prefix' do
|
202
|
-
limiter.acquire_lock
|
202
|
+
limiter.acquire_lock?
|
203
203
|
|
204
204
|
# Check that the counter key exists with correct prefix
|
205
205
|
keys = limiter.redis.keys("sidekiq:queue_throttled:queue:#{queue_name}:*")
|
@@ -207,7 +207,7 @@ RSpec.describe Sidekiq::QueueThrottled::QueueLimiter do
|
|
207
207
|
end
|
208
208
|
|
209
209
|
it 'sets TTL on counter keys' do
|
210
|
-
limiter.acquire_lock
|
210
|
+
limiter.acquire_lock?
|
211
211
|
|
212
212
|
counter_key = "#{Sidekiq::QueueThrottled.configuration.redis_key_prefix}:queue:#{queue_name}:counter"
|
213
213
|
ttl = limiter.redis.ttl(counter_key)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe 'Rails Integration' do
|
6
|
+
describe 'when Rails is available' do
|
7
|
+
before do
|
8
|
+
# Mock Rails to simulate Rails environment
|
9
|
+
stub_const('Rails', double('Rails'))
|
10
|
+
stub_const('Rails::Railtie', Class.new)
|
11
|
+
|
12
|
+
# Reload the main file to trigger Rails detection
|
13
|
+
load File.expand_path('../../lib/sidekiq/queue_throttled.rb', __dir__)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'defines the Sidekiq::QueueThrottled module' do
|
17
|
+
expect(defined?(Sidekiq::QueueThrottled)).to be_truthy
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'defines the Sidekiq::QueueThrottled::Job module' do
|
21
|
+
expect(defined?(Sidekiq::QueueThrottled::Job)).to be_truthy
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'defines the Railtie class' do
|
25
|
+
expect(defined?(Sidekiq::QueueThrottled::Railtie)).to be_truthy
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'when Rails is not available' do
|
30
|
+
before do
|
31
|
+
# Remove Rails constants to simulate non-Rails environment
|
32
|
+
Object.send(:remove_const, 'Rails') if defined?(Rails)
|
33
|
+
|
34
|
+
# Reload the main file to trigger Rails detection
|
35
|
+
load File.expand_path('../../lib/sidekiq/queue_throttled.rb', __dir__)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'still defines the Sidekiq::QueueThrottled module' do
|
39
|
+
expect(defined?(Sidekiq::QueueThrottled)).to be_truthy
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'still defines the Sidekiq::QueueThrottled::Job module' do
|
43
|
+
expect(defined?(Sidekiq::QueueThrottled::Job)).to be_truthy
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -6,10 +6,10 @@ require 'rspec'
|
|
6
6
|
require 'timecop'
|
7
7
|
require 'pry'
|
8
8
|
|
9
|
-
# Set up
|
10
|
-
redis_url = ENV['REDIS_URL'] || 'redis
|
9
|
+
# Set up Redis for tests - use passwordless Redis for CI, passworded for local dev
|
10
|
+
redis_url = ENV['REDIS_URL'] || 'redis://localhost:6379/0'
|
11
11
|
|
12
|
-
# Configure Sidekiq to use Redis
|
12
|
+
# Configure Sidekiq to use Redis
|
13
13
|
Sidekiq.configure_client { |c| c.redis = { url: redis_url } }
|
14
14
|
Sidekiq.configure_server { |c| c.redis = { url: redis_url } }
|
15
15
|
Sidekiq::QueueThrottled.redis = Redis.new(url: redis_url)
|
@@ -63,7 +63,7 @@ module TestHelpers
|
|
63
63
|
klass
|
64
64
|
end
|
65
65
|
|
66
|
-
def wait_for_condition(timeout = 5, &condition)
|
66
|
+
def wait_for_condition?(timeout = 5, &condition)
|
67
67
|
start_time = Time.now
|
68
68
|
while Time.now - start_time < timeout
|
69
69
|
return true if condition.call
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-queue-throttled
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Farid Mohammadi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- lib/sidekiq/queue_throttled/job_throttler.rb
|
71
71
|
- lib/sidekiq/queue_throttled/middleware.rb
|
72
72
|
- lib/sidekiq/queue_throttled/queue_limiter.rb
|
73
|
+
- lib/sidekiq/queue_throttled/railtie.rb
|
73
74
|
- lib/sidekiq/queue_throttled/version.rb
|
74
75
|
- spec/examples.txt
|
75
76
|
- spec/sidekiq/queue_throttled/configuration_spec.rb
|
@@ -77,14 +78,15 @@ files:
|
|
77
78
|
- spec/sidekiq/queue_throttled/job_throttler_spec.rb
|
78
79
|
- spec/sidekiq/queue_throttled/middleware_spec.rb
|
79
80
|
- spec/sidekiq/queue_throttled/queue_limiter_spec.rb
|
81
|
+
- spec/sidekiq/queue_throttled/railtie_spec.rb
|
80
82
|
- spec/spec_helper.rb
|
81
|
-
homepage: https://github.com/
|
83
|
+
homepage: https://github.com/zilnex/sidekiq-queue-throttled
|
82
84
|
licenses:
|
83
85
|
- MIT
|
84
86
|
metadata:
|
85
|
-
homepage_uri: https://github.com/
|
86
|
-
source_code_uri: https://github.com/
|
87
|
-
changelog_uri: https://github.com/
|
87
|
+
homepage_uri: https://github.com/zilnex/sidekiq-queue-throttled
|
88
|
+
source_code_uri: https://github.com/zilnex/sidekiq-queue-throttled
|
89
|
+
changelog_uri: https://github.com/zilnex/sidekiq-queue-throttled/blob/main/CHANGELOG.md
|
88
90
|
rubygems_mfa_required: 'true'
|
89
91
|
post_install_message:
|
90
92
|
rdoc_options: []
|