sidekiq-queue-throttled 1.0.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 +7 -0
- data/CHANGELOG.md +53 -0
- data/LICENSE.txt +21 -0
- data/README.md +303 -0
- data/lib/sidekiq/queue_throttled/configuration.rb +53 -0
- data/lib/sidekiq/queue_throttled/job.rb +82 -0
- data/lib/sidekiq/queue_throttled/job_throttler.rb +155 -0
- data/lib/sidekiq/queue_throttled/middleware.rb +92 -0
- data/lib/sidekiq/queue_throttled/queue_limiter.rb +110 -0
- data/lib/sidekiq/queue_throttled/version.rb +7 -0
- data/lib/sidekiq/queue_throttled.rb +49 -0
- data/spec/examples.txt +110 -0
- data/spec/sidekiq/queue_throttled/configuration_spec.rb +145 -0
- data/spec/sidekiq/queue_throttled/job_spec.rb +181 -0
- data/spec/sidekiq/queue_throttled/job_throttler_spec.rb +365 -0
- data/spec/sidekiq/queue_throttled/middleware_spec.rb +280 -0
- data/spec/sidekiq/queue_throttled/queue_limiter_spec.rb +217 -0
- data/spec/spec_helper.rb +79 -0
- metadata +108 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module QueueThrottled
|
5
|
+
class Middleware
|
6
|
+
def initialize
|
7
|
+
@queue_limiters = Concurrent::Map.new
|
8
|
+
@job_throttlers = Concurrent::Map.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(worker, job, queue)
|
12
|
+
queue_name = job['queue'] || queue
|
13
|
+
job_class = worker.class.name
|
14
|
+
|
15
|
+
# Check queue-level limits
|
16
|
+
queue_limiter = get_queue_limiter(queue_name)
|
17
|
+
if queue_limiter
|
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
|
+
|
26
|
+
# Check job-level throttling
|
27
|
+
job_throttler = get_job_throttler(job_class)
|
28
|
+
if job_throttler && !job_throttler.acquire_slot(job['args'])
|
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
|
33
|
+
|
34
|
+
# Process the job
|
35
|
+
begin
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
# Release locks
|
39
|
+
queue_limiter&.release_lock(lock_id)
|
40
|
+
job_throttler&.release_slot(job['args'])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def get_queue_limiter(queue_name)
|
47
|
+
limit = Sidekiq::QueueThrottled.configuration.queue_limit(queue_name)
|
48
|
+
return nil unless limit
|
49
|
+
|
50
|
+
@queue_limiters.compute_if_absent(queue_name) do
|
51
|
+
QueueLimiter.new(queue_name, limit)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_job_throttler(job_class)
|
56
|
+
throttle_config = get_throttle_config(job_class)
|
57
|
+
return nil unless throttle_config
|
58
|
+
|
59
|
+
@job_throttlers.compute_if_absent(job_class) do
|
60
|
+
JobThrottler.new(job_class, throttle_config)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_throttle_config(job_class)
|
65
|
+
# Handle string class names
|
66
|
+
if job_class.is_a?(String)
|
67
|
+
begin
|
68
|
+
klass = Object.const_get(job_class)
|
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
|
74
|
+
elsif job_class.respond_to?(:sidekiq_throttle_config)
|
75
|
+
# Handle actual class objects
|
76
|
+
return job_class.sidekiq_throttle_config
|
77
|
+
end
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
|
81
|
+
def reschedule_job(job, queue_name)
|
82
|
+
delay = Sidekiq::QueueThrottled.configuration.retry_delay
|
83
|
+
job['at'] = Time.now.to_f + delay
|
84
|
+
job['queue'] = queue_name
|
85
|
+
|
86
|
+
Sidekiq.redis do |conn|
|
87
|
+
conn.zadd('schedule', job['at'], job.to_json)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module QueueThrottled
|
5
|
+
class QueueLimiter
|
6
|
+
attr_reader :queue_name, :limit, :redis
|
7
|
+
|
8
|
+
def initialize(queue_name, limit, redis = nil)
|
9
|
+
@queue_name = queue_name.to_s
|
10
|
+
@limit = limit.to_i
|
11
|
+
@redis = redis || Sidekiq::QueueThrottled.redis
|
12
|
+
@lock_key = "#{Sidekiq::QueueThrottled.configuration.redis_key_prefix}:queue:#{@queue_name}:lock"
|
13
|
+
@counter_key = "#{Sidekiq::QueueThrottled.configuration.redis_key_prefix}:queue:#{@queue_name}:counter"
|
14
|
+
@mutex = Concurrent::ReentrantReadWriteLock.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def acquire_lock(worker_id = nil)
|
18
|
+
worker_id ||= SecureRandom.uuid
|
19
|
+
lock_id = "#{worker_id}:#{Time.now.to_f}"
|
20
|
+
|
21
|
+
@mutex.with_write_lock do
|
22
|
+
current_count = get_current_count
|
23
|
+
puts "DEBUG: QueueLimiter - current_count: #{current_count}, limit: #{@limit}"
|
24
|
+
return false if current_count >= @limit
|
25
|
+
|
26
|
+
# Increment the counter first
|
27
|
+
increment_counter
|
28
|
+
puts "DEBUG: QueueLimiter - acquired lock: #{lock_id}"
|
29
|
+
return lock_id
|
30
|
+
end
|
31
|
+
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def release_lock(lock_id)
|
36
|
+
return false unless lock_id
|
37
|
+
|
38
|
+
@mutex.with_write_lock do
|
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
|
44
|
+
rescue StandardError => e
|
45
|
+
Sidekiq::QueueThrottled.logger.error "Failed to release lock #{lock_id} for queue #{@queue_name}: #{e.message}"
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
def current_count
|
50
|
+
@mutex.with_read_lock do
|
51
|
+
get_current_count
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def available_slots
|
56
|
+
[0, @limit - current_count].max
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset!
|
60
|
+
@mutex.with_write_lock do
|
61
|
+
@redis.del(@counter_key)
|
62
|
+
# Clear all locks for this queue
|
63
|
+
pattern = "#{Sidekiq::QueueThrottled.configuration.redis_key_prefix}:queue:#{@queue_name}:lock:*"
|
64
|
+
keys = @redis.keys(pattern)
|
65
|
+
@redis.del(*keys) unless keys.empty?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def get_current_count
|
72
|
+
count = @redis.get(@counter_key)
|
73
|
+
result = count ? count.to_i : 0
|
74
|
+
puts "DEBUG: get_current_count - key: #{@counter_key}, count: #{result}"
|
75
|
+
result
|
76
|
+
end
|
77
|
+
|
78
|
+
def increment_counter
|
79
|
+
puts "DEBUG: increment_counter - key: #{@counter_key}"
|
80
|
+
@redis.multi do |multi|
|
81
|
+
multi.incr(@counter_key)
|
82
|
+
multi.expire(@counter_key, Sidekiq::QueueThrottled.configuration.throttle_ttl)
|
83
|
+
end
|
84
|
+
puts "DEBUG: increment_counter - after increment, count: #{get_current_count}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def decrement_counter
|
88
|
+
@redis.multi do |multi|
|
89
|
+
multi.decr(@counter_key)
|
90
|
+
multi.expire(@counter_key, Sidekiq::QueueThrottled.configuration.throttle_ttl)
|
91
|
+
end
|
92
|
+
# Ensure counter doesn't go below 0
|
93
|
+
current = get_current_count
|
94
|
+
if current.negative?
|
95
|
+
@redis.set(@counter_key, 0)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def acquire_redis_lock(lock_id)
|
100
|
+
lock_key = "#{@lock_key}:#{lock_id}"
|
101
|
+
@redis.set(lock_key, '1', nx: true, ex: Sidekiq::QueueThrottled.configuration.lock_ttl)
|
102
|
+
end
|
103
|
+
|
104
|
+
def release_redis_lock(lock_id)
|
105
|
+
lock_key = "#{@lock_key}:#{lock_id}"
|
106
|
+
@redis.del(lock_key).positive?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sidekiq'
|
4
|
+
require 'redis'
|
5
|
+
require 'concurrent'
|
6
|
+
require 'json'
|
7
|
+
require 'logger'
|
8
|
+
|
9
|
+
require_relative 'queue_throttled/version'
|
10
|
+
require_relative 'queue_throttled/configuration'
|
11
|
+
require_relative 'queue_throttled/queue_limiter'
|
12
|
+
require_relative 'queue_throttled/job_throttler'
|
13
|
+
require_relative 'queue_throttled/middleware'
|
14
|
+
require_relative 'queue_throttled/job'
|
15
|
+
|
16
|
+
module Sidekiq
|
17
|
+
module QueueThrottled
|
18
|
+
class << self
|
19
|
+
def configure
|
20
|
+
yield configuration
|
21
|
+
end
|
22
|
+
|
23
|
+
def configuration
|
24
|
+
@configuration ||= Configuration.new
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_writer :configuration, :logger, :redis
|
28
|
+
|
29
|
+
def logger
|
30
|
+
@logger ||= begin
|
31
|
+
logger = Logger.new($stdout)
|
32
|
+
logger.level = Logger::INFO
|
33
|
+
logger
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def redis
|
38
|
+
@redis ||= Sidekiq.redis { |conn| conn }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Auto-load the middleware when the gem is required
|
45
|
+
Sidekiq.configure_server do |config|
|
46
|
+
config.server_middleware do |chain|
|
47
|
+
chain.add Sidekiq::QueueThrottled::Middleware
|
48
|
+
end
|
49
|
+
end
|
data/spec/examples.txt
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
example_id | status | run_time |
|
2
|
+
------------------------------------------------------------- | ------ | --------------- |
|
3
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:1:1] | passed | 0.0001 seconds |
|
4
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:1:2] | passed | 0.0001 seconds |
|
5
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:1:3] | passed | 0.00009 seconds |
|
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.0001 seconds |
|
8
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:2:3] | passed | 0.0001 seconds |
|
9
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:3:1] | passed | 0.00019 seconds |
|
10
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:3:2] | passed | 0.0002 seconds |
|
11
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:3:3] | passed | 0.00354 seconds |
|
12
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:4:1] | passed | 0.00031 seconds |
|
13
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:4:2] | passed | 0.00437 seconds |
|
14
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:4:3] | passed | 0.01693 seconds |
|
15
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:1] | passed | 0.0001 seconds |
|
16
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:2] | passed | 0.0001 seconds |
|
17
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:3] | passed | 0.0001 seconds |
|
18
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:5:4] | passed | 0.00011 seconds |
|
19
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:1] | passed | 0.0001 seconds |
|
20
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:2] | passed | 0.00009 seconds |
|
21
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:3] | passed | 0.0001 seconds |
|
22
|
+
./spec/sidekiq/queue_throttled/configuration_spec.rb[1:6:4] | passed | 0.00009 seconds |
|
23
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:1:1] | passed | 0.00014 seconds |
|
24
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:1:2] | passed | 0.00013 seconds |
|
25
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:1] | passed | 0.00015 seconds |
|
26
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:2] | passed | 0.00019 seconds |
|
27
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:3] | passed | 0.00012 seconds |
|
28
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:4] | passed | 0.00015 seconds |
|
29
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:5] | passed | 0.00017 seconds |
|
30
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:6] | passed | 0.00015 seconds |
|
31
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:7] | passed | 0.00015 seconds |
|
32
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:8] | passed | 0.00016 seconds |
|
33
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:9] | passed | 0.00016 seconds |
|
34
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:10] | passed | 0.00016 seconds |
|
35
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:11] | passed | 0.00015 seconds |
|
36
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:12] | passed | 0.00016 seconds |
|
37
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:2:13] | passed | 0.00016 seconds |
|
38
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:3:1] | passed | 0.00072 seconds |
|
39
|
+
./spec/sidekiq/queue_throttled/job_spec.rb[1:3:2] | passed | 0.00139 seconds |
|
40
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:1:1] | passed | 0.00024 seconds |
|
41
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:1:2] | passed | 0.00018 seconds |
|
42
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:1:1] | passed | 0.00011 seconds |
|
43
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:2:1] | passed | 0.00022 seconds |
|
44
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:2:2] | passed | 0.00065 seconds |
|
45
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:2:3] | passed | 0.00053 seconds |
|
46
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:3:1] | passed | 0.00015 seconds |
|
47
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:3:2] | passed | 0.00047 seconds |
|
48
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:3:3] | passed | 0.00072 seconds |
|
49
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:2:4:1] | passed | 0.0003 seconds |
|
50
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:1:1] | passed | 0.0001 seconds |
|
51
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:2:1] | passed | 0.00037 seconds |
|
52
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:2:2] | passed | 0.00066 seconds |
|
53
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:2:3] | passed | 0.00079 seconds |
|
54
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:3:1] | passed | 0.00022 seconds |
|
55
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:3:3:2] | passed | 0.00043 seconds |
|
56
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:1:1] | passed | 0.0045 seconds |
|
57
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:2:1] | passed | 0.00214 seconds |
|
58
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:2:2] | passed | 0.00612 seconds |
|
59
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:4:3:1] | passed | 0.00015 seconds |
|
60
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:1:1] | passed | 0.00066 seconds |
|
61
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:2:1] | passed | 0.00079 seconds |
|
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.00054 seconds |
|
64
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:5:4:1] | passed | 0.00068 seconds |
|
65
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:6:1] | passed | 0.00155 seconds |
|
66
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:6:2] | passed | 0.00058 seconds |
|
67
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:6:3] | passed | 0.0007 seconds |
|
68
|
+
./spec/sidekiq/queue_throttled/job_throttler_spec.rb[1:7:1] | passed | 0.0013 seconds |
|
69
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:1:1] | passed | 0.00033 seconds |
|
70
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:2:1] | passed | 0.00045 seconds |
|
71
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:2:2] | passed | 0.00296 seconds |
|
72
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:3:1] | passed | 0.00073 seconds |
|
73
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:3:2] | passed | 0.00098 seconds |
|
74
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:3:3] | passed | 0.00104 seconds |
|
75
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:4:1] | passed | 0.00105 seconds |
|
76
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:5:1] | passed | 0.00068 seconds |
|
77
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:1:5:2] | passed | 0.00223 seconds |
|
78
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:2:1] | passed | 0.00009 seconds |
|
79
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:2:2] | passed | 0.00013 seconds |
|
80
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:2:3] | passed | 0.0001 seconds |
|
81
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:3:1] | passed | 0.00015 seconds |
|
82
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:3:2] | passed | 0.00058 seconds |
|
83
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:3:3] | passed | 0.00023 seconds |
|
84
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:4:1] | passed | 0.0005 seconds |
|
85
|
+
./spec/sidekiq/queue_throttled/middleware_spec.rb[1:4:2] | passed | 0.00098 seconds |
|
86
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:1:1] | passed | 0.00013 seconds |
|
87
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:1:2] | passed | 0.00014 seconds |
|
88
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:1:3] | passed | 0.00016 seconds |
|
89
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:1] | passed | 0.0004 seconds |
|
90
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:2] | passed | 0.0011 seconds |
|
91
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:3] | passed | 0.00088 seconds |
|
92
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:4] | passed | 0.00046 seconds |
|
93
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:2:5] | passed | 0.00058 seconds |
|
94
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:3:1] | passed | 0.00058 seconds |
|
95
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:3:2] | passed | 0.00014 seconds |
|
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.00038 seconds |
|
98
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:4:1] | passed | 0.0002 seconds |
|
99
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:4:2] | passed | 0.00098 seconds |
|
100
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:4:3] | passed | 0.00095 seconds |
|
101
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:1] | passed | 0.00021 seconds |
|
102
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:2] | passed | 0.00045 seconds |
|
103
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:3] | passed | 0.00112 seconds |
|
104
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:5:4] | passed | 0.00023 seconds |
|
105
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:6:1] | passed | 0.00081 seconds |
|
106
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:6:2] | passed | 0.00152 seconds |
|
107
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:7:1] | passed | 0.00151 seconds |
|
108
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:7:2] | passed | 0.00136 seconds |
|
109
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:8:1] | passed | 0.00066 seconds |
|
110
|
+
./spec/sidekiq/queue_throttled/queue_limiter_spec.rb[1:8:2] | passed | 0.00093 seconds |
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Sidekiq::QueueThrottled::Configuration do
|
6
|
+
let(:config) { described_class.new }
|
7
|
+
|
8
|
+
describe '#set_queue_limit' do
|
9
|
+
it 'sets queue limit as integer' do
|
10
|
+
config.set_queue_limit(:test_queue, 100)
|
11
|
+
expect(config.queue_limit(:test_queue)).to eq(100)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'converts string limit to integer' do
|
15
|
+
config.set_queue_limit(:test_queue, '50')
|
16
|
+
expect(config.queue_limit(:test_queue)).to eq(50)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'handles string queue names' do
|
20
|
+
config.set_queue_limit('test_queue', 100)
|
21
|
+
expect(config.queue_limit('test_queue')).to eq(100)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#queue_limit' do
|
26
|
+
it 'returns limit for string queue name' do
|
27
|
+
config.set_queue_limit('test_queue', 100)
|
28
|
+
expect(config.queue_limit('test_queue')).to eq(100)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns limit for symbol queue name' do
|
32
|
+
config.set_queue_limit(:test_queue, 100)
|
33
|
+
expect(config.queue_limit(:test_queue)).to eq(100)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'returns nil for non-existent queue' do
|
37
|
+
expect(config.queue_limit(:non_existent)).to be_nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#load_from_sidekiq_config!' do
|
42
|
+
let(:sidekiq_config) do
|
43
|
+
{
|
44
|
+
limits: {
|
45
|
+
'queue1' => 100,
|
46
|
+
'queue2' => 50
|
47
|
+
}
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'loads limits from Sidekiq options' do
|
52
|
+
config.load_from_sidekiq_config!(sidekiq_config)
|
53
|
+
expect(config.queue_limit('queue1')).to eq(100)
|
54
|
+
expect(config.queue_limit('queue2')).to eq(50)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'handles string keys in limits' do
|
58
|
+
sidekiq_config_with_string_keys = {
|
59
|
+
'limits' => {
|
60
|
+
'queue1' => 100
|
61
|
+
}
|
62
|
+
}
|
63
|
+
config.load_from_sidekiq_config!(sidekiq_config_with_string_keys)
|
64
|
+
expect(config.queue_limit('queue1')).to eq(100)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'does nothing when limits are not defined' do
|
68
|
+
empty_config = {}
|
69
|
+
expect { config.load_from_sidekiq_config!(empty_config) }.not_to(change { config.queue_limits })
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#load_from_yaml!' do
|
74
|
+
let(:yaml_content) do
|
75
|
+
<<~YAML
|
76
|
+
limits:
|
77
|
+
queue1: 100
|
78
|
+
queue2: 50
|
79
|
+
YAML
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'loads limits from YAML content' do
|
83
|
+
config.load_from_yaml!(yaml_content)
|
84
|
+
expect(config.queue_limit('queue1')).to eq(100)
|
85
|
+
expect(config.queue_limit('queue2')).to eq(50)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'handles string keys in YAML' do
|
89
|
+
yaml_with_string_keys = <<~YAML
|
90
|
+
"limits":
|
91
|
+
"queue1": 100
|
92
|
+
YAML
|
93
|
+
config.load_from_yaml!(yaml_with_string_keys)
|
94
|
+
expect(config.queue_limit('queue1')).to eq(100)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'does nothing when limits are not defined' do
|
98
|
+
yaml_without_limits = <<~YAML
|
99
|
+
other_config: value
|
100
|
+
YAML
|
101
|
+
expect { config.load_from_yaml!(yaml_without_limits) }.not_to(change { config.queue_limits })
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '#validate!' do
|
106
|
+
it 'passes validation for valid limits' do
|
107
|
+
config.set_queue_limit(:queue1, 100)
|
108
|
+
config.set_queue_limit(:queue2, 50)
|
109
|
+
expect { config.validate! }.not_to raise_error
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'raises error for non-positive limits' do
|
113
|
+
config.set_queue_limit(:queue1, 0)
|
114
|
+
expect { config.validate! }.to raise_error(ArgumentError, /positive integer/)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'raises error for negative limits' do
|
118
|
+
config.set_queue_limit(:queue1, -1)
|
119
|
+
expect { config.validate! }.to raise_error(ArgumentError, /positive integer/)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'raises error for non-integer limits' do
|
123
|
+
config.queue_limits[:queue1] = 'invalid'
|
124
|
+
expect { config.validate! }.to raise_error(ArgumentError, /positive integer/)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe 'default values' do
|
129
|
+
it 'has correct default redis_key_prefix' do
|
130
|
+
expect(config.redis_key_prefix).to eq('sidekiq:queue_throttled')
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'has correct default throttle_ttl' do
|
134
|
+
expect(config.throttle_ttl).to eq(3600)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'has correct default lock_ttl' do
|
138
|
+
expect(config.lock_ttl).to eq(300)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'has correct default retry_delay' do
|
142
|
+
expect(config.retry_delay).to eq(5)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|