HornsAndHooves-sidekiq-limit_fetch 4.5.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/.gitignore +4 -0
- data/.rspec +5 -0
- data/.rubocop.yml +34 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +37 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +165 -0
- data/Rakefile +14 -0
- data/bench/compare.rb +56 -0
- data/demo/Gemfile +8 -0
- data/demo/README.md +37 -0
- data/demo/Rakefile +100 -0
- data/demo/app/workers/a_worker.rb +10 -0
- data/demo/app/workers/b_worker.rb +10 -0
- data/demo/app/workers/c_worker.rb +10 -0
- data/demo/app/workers/fast_worker.rb +10 -0
- data/demo/app/workers/slow_worker.rb +10 -0
- data/demo/config/application.rb +13 -0
- data/demo/config/boot.rb +4 -0
- data/demo/config/environment.rb +4 -0
- data/demo/config/environments/development.rb +11 -0
- data/lib/sidekiq/extensions/manager.rb +21 -0
- data/lib/sidekiq/extensions/queue.rb +27 -0
- data/lib/sidekiq/limit_fetch/global/monitor.rb +83 -0
- data/lib/sidekiq/limit_fetch/global/selector.rb +130 -0
- data/lib/sidekiq/limit_fetch/global/semaphore.rb +190 -0
- data/lib/sidekiq/limit_fetch/instances.rb +29 -0
- data/lib/sidekiq/limit_fetch/queues.rb +197 -0
- data/lib/sidekiq/limit_fetch/unit_of_work.rb +28 -0
- data/lib/sidekiq/limit_fetch.rb +76 -0
- data/lib/sidekiq-limit_fetch.rb +3 -0
- data/sidekiq-limit_fetch.gemspec +30 -0
- data/spec/sidekiq/extensions/manager_spec.rb +13 -0
- data/spec/sidekiq/extensions/queue_spec.rb +96 -0
- data/spec/sidekiq/limit_fetch/global/monitor_spec.rb +114 -0
- data/spec/sidekiq/limit_fetch/queues_spec.rb +127 -0
- data/spec/sidekiq/limit_fetch/semaphore_spec.rb +65 -0
- data/spec/sidekiq/limit_fetch_spec.rb +58 -0
- data/spec/spec_helper.rb +34 -0
- metadata +179 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidekiq
|
|
4
|
+
module LimitFetch
|
|
5
|
+
module Global
|
|
6
|
+
module Monitor
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
HEARTBEAT_PREFIX = 'limit:heartbeat:'
|
|
10
|
+
PROCESS_SET = 'limit:processes'
|
|
11
|
+
HEARTBEAT_TTL = 20
|
|
12
|
+
REFRESH_TIMEOUT = 5
|
|
13
|
+
|
|
14
|
+
def start!(ttl = HEARTBEAT_TTL, timeout = REFRESH_TIMEOUT)
|
|
15
|
+
Thread.new do
|
|
16
|
+
loop do
|
|
17
|
+
Sidekiq::LimitFetch.redis_retryable do
|
|
18
|
+
handle_dynamic_queues
|
|
19
|
+
update_heartbeat ttl
|
|
20
|
+
invalidate_old_processes
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
sleep timeout
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def all_processes
|
|
29
|
+
Sidekiq.redis { |it| it.smembers PROCESS_SET }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def old_processes
|
|
33
|
+
all_processes.reject do |process|
|
|
34
|
+
Sidekiq.redis { |it| it.get heartbeat_key process } == '1'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def remove_old_processes!
|
|
39
|
+
Sidekiq.redis do |it|
|
|
40
|
+
old_processes.each { |process| it.srem PROCESS_SET, [process] }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def handle_dynamic_queues
|
|
45
|
+
queues = Sidekiq::LimitFetch::Queues
|
|
46
|
+
return unless queues.dynamic?
|
|
47
|
+
|
|
48
|
+
available_queues = Sidekiq::Queue.all.map(&:name).reject do |it|
|
|
49
|
+
queues.dynamic_exclude.include? it
|
|
50
|
+
end
|
|
51
|
+
queues.handle available_queues
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def update_heartbeat(ttl)
|
|
57
|
+
Sidekiq.redis do |it|
|
|
58
|
+
it.multi do |pipeline|
|
|
59
|
+
pipeline.set heartbeat_key, '1'
|
|
60
|
+
pipeline.sadd PROCESS_SET, [Selector.uuid]
|
|
61
|
+
pipeline.expire heartbeat_key, ttl
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def invalidate_old_processes
|
|
67
|
+
Sidekiq.redis do |_it|
|
|
68
|
+
remove_old_processes!
|
|
69
|
+
processes = all_processes
|
|
70
|
+
|
|
71
|
+
Sidekiq::Queue.instances.each do |queue|
|
|
72
|
+
queue.remove_locks_except! processes
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def heartbeat_key(process = Selector.uuid)
|
|
78
|
+
HEARTBEAT_PREFIX + process
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidekiq
|
|
4
|
+
module LimitFetch
|
|
5
|
+
module Global
|
|
6
|
+
module Selector
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
MUTEX_FOR_UUID = Mutex.new
|
|
10
|
+
|
|
11
|
+
def acquire(queues, namespace)
|
|
12
|
+
redis_eval :acquire, [namespace, uuid, queues]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def release(queues, namespace)
|
|
16
|
+
redis_eval :release, [namespace, uuid, queues]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def uuid
|
|
20
|
+
# - if we'll remove "@uuid ||=" from inside of mutex
|
|
21
|
+
# then @uuid can be overwritten
|
|
22
|
+
# - if we'll remove "@uuid ||=" from outside of mutex
|
|
23
|
+
# then each read will lead to mutex
|
|
24
|
+
@uuid ||= MUTEX_FOR_UUID.synchronize { @uuid || SecureRandom.uuid }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def redis_eval(script_name, args)
|
|
30
|
+
Sidekiq.redis do |it|
|
|
31
|
+
it.evalsha send("redis_#{script_name}_sha"), [], args
|
|
32
|
+
rescue Sidekiq::LimitFetch::RedisCommandError => e
|
|
33
|
+
raise unless e.message.include? 'NOSCRIPT'
|
|
34
|
+
|
|
35
|
+
it.eval send("redis_#{script_name}_script"), 0, *args
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def redis_acquire_sha
|
|
40
|
+
@redis_acquire_sha ||= OpenSSL::Digest::SHA1.hexdigest redis_acquire_script
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def redis_release_sha
|
|
44
|
+
@redis_release_sha ||= OpenSSL::Digest::SHA1.hexdigest redis_release_script
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def redis_acquire_script
|
|
48
|
+
<<-LUA
|
|
49
|
+
local namespace = table.remove(ARGV, 1)..'limit_fetch:'
|
|
50
|
+
local worker_name = table.remove(ARGV, 1)
|
|
51
|
+
local queues = ARGV
|
|
52
|
+
local available = {}
|
|
53
|
+
local unblocked = {}
|
|
54
|
+
local locks
|
|
55
|
+
local process_locks
|
|
56
|
+
local blocking_mode
|
|
57
|
+
|
|
58
|
+
for _, queue in ipairs(queues) do
|
|
59
|
+
if not blocking_mode or unblocked[queue] then
|
|
60
|
+
local probed_key = namespace..'probed:'..queue
|
|
61
|
+
local pause_key = namespace..'pause:'..queue
|
|
62
|
+
local limit_key = namespace..'limit:'..queue
|
|
63
|
+
local process_limit_key = namespace..'process_limit:'..queue
|
|
64
|
+
local block_key = namespace..'block:'..queue
|
|
65
|
+
|
|
66
|
+
local paused, limit, process_limit, can_block =
|
|
67
|
+
unpack(redis.call('mget',
|
|
68
|
+
pause_key,
|
|
69
|
+
limit_key,
|
|
70
|
+
process_limit_key,
|
|
71
|
+
block_key
|
|
72
|
+
))
|
|
73
|
+
|
|
74
|
+
if not paused then
|
|
75
|
+
limit = tonumber(limit)
|
|
76
|
+
process_limit = tonumber(process_limit)
|
|
77
|
+
|
|
78
|
+
if can_block or limit then
|
|
79
|
+
locks = redis.call('llen', probed_key)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if process_limit then
|
|
83
|
+
local all_locks = redis.call('lrange', probed_key, 0, -1)
|
|
84
|
+
process_locks = 0
|
|
85
|
+
for _, process in ipairs(all_locks) do
|
|
86
|
+
if process == worker_name then
|
|
87
|
+
process_locks = process_locks + 1
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if not blocking_mode then
|
|
93
|
+
blocking_mode = can_block and locks > 0
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if blocking_mode and can_block ~= 'true' then
|
|
97
|
+
for unblocked_queue in string.gmatch(can_block, "[^,]+") do
|
|
98
|
+
unblocked[unblocked_queue] = true
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if (not limit or limit > locks) and
|
|
103
|
+
(not process_limit or process_limit > process_locks) then
|
|
104
|
+
redis.call('rpush', probed_key, worker_name)
|
|
105
|
+
table.insert(available, queue)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
return available
|
|
112
|
+
LUA
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def redis_release_script
|
|
116
|
+
<<-LUA
|
|
117
|
+
local namespace = table.remove(ARGV, 1)..'limit_fetch:'
|
|
118
|
+
local worker_name = table.remove(ARGV, 1)
|
|
119
|
+
local queues = ARGV
|
|
120
|
+
|
|
121
|
+
for _, queue in ipairs(queues) do
|
|
122
|
+
local probed_key = namespace..'probed:'..queue
|
|
123
|
+
redis.call('lrem', probed_key, 1, worker_name)
|
|
124
|
+
end
|
|
125
|
+
LUA
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidekiq
|
|
4
|
+
module LimitFetch
|
|
5
|
+
module Global
|
|
6
|
+
class Semaphore
|
|
7
|
+
PREFIX = 'limit_fetch'
|
|
8
|
+
|
|
9
|
+
attr_reader :local_busy
|
|
10
|
+
|
|
11
|
+
def initialize(name)
|
|
12
|
+
@name = name
|
|
13
|
+
@lock = Mutex.new
|
|
14
|
+
@local_busy = 0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def limit
|
|
18
|
+
value = redis { |it| it.get "#{PREFIX}:limit:#{@name}" }
|
|
19
|
+
value&.to_i
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def limit=(value)
|
|
23
|
+
@limit_changed = true
|
|
24
|
+
|
|
25
|
+
if value
|
|
26
|
+
redis { |it| it.set "#{PREFIX}:limit:#{@name}", value }
|
|
27
|
+
else
|
|
28
|
+
redis { |it| it.del "#{PREFIX}:limit:#{@name}" }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def limit_changed?
|
|
33
|
+
@limit_changed
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def process_limit
|
|
37
|
+
value = redis { |it| it.get "#{PREFIX}:process_limit:#{@name}" }
|
|
38
|
+
value&.to_i
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def process_limit=(value)
|
|
42
|
+
if value
|
|
43
|
+
redis { |it| it.set "#{PREFIX}:process_limit:#{@name}", value }
|
|
44
|
+
else
|
|
45
|
+
redis { |it| it.del "#{PREFIX}:process_limit:#{@name}" }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def acquire
|
|
50
|
+
Selector.acquire([@name], namespace).size.positive?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def release
|
|
54
|
+
redis { |it| it.lrem "#{PREFIX}:probed:#{@name}", 1, Selector.uuid }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def busy
|
|
58
|
+
redis { |it| it.llen "#{PREFIX}:busy:#{@name}" }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def busy_processes
|
|
62
|
+
redis { |it| it.lrange "#{PREFIX}:busy:#{@name}", 0, -1 }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def increase_busy
|
|
66
|
+
increase_local_busy
|
|
67
|
+
redis { |it| it.rpush "#{PREFIX}:busy:#{@name}", Selector.uuid }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def decrease_busy
|
|
71
|
+
decrease_local_busy
|
|
72
|
+
redis { |it| it.lrem "#{PREFIX}:busy:#{@name}", 1, Selector.uuid }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def probed
|
|
76
|
+
redis { |it| it.llen "#{PREFIX}:probed:#{@name}" }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def probed_processes
|
|
80
|
+
redis { |it| it.lrange "#{PREFIX}:probed:#{@name}", 0, -1 }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def pause
|
|
84
|
+
redis { |it| it.set "#{PREFIX}:pause:#{@name}", '1' }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def pause_for_ms(milliseconds)
|
|
88
|
+
redis { |it| it.psetex "#{PREFIX}:pause:#{@name}", milliseconds, 1 }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def unpause
|
|
92
|
+
redis { |it| it.del "#{PREFIX}:pause:#{@name}" }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def paused?
|
|
96
|
+
redis { |it| it.get "#{PREFIX}:pause:#{@name}" } == '1'
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def block
|
|
100
|
+
redis { |it| it.set "#{PREFIX}:block:#{@name}", '1' }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def block_except(*queues)
|
|
104
|
+
raise ArgumentError if queues.empty?
|
|
105
|
+
|
|
106
|
+
redis { |it| it.set "#{PREFIX}:block:#{@name}", queues.join(',') }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def unblock
|
|
110
|
+
redis { |it| it.del "#{PREFIX}:block:#{@name}" }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def blocking?
|
|
114
|
+
redis { |it| it.get "#{PREFIX}:block:#{@name}" } == '1'
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def clear_limits
|
|
118
|
+
redis do |it|
|
|
119
|
+
%w[block busy limit pause probed process_limit].each do |key|
|
|
120
|
+
it.del "#{PREFIX}:#{key}:#{@name}"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def increase_local_busy
|
|
126
|
+
@lock.synchronize { @local_busy += 1 }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def decrease_local_busy
|
|
130
|
+
@lock.synchronize { @local_busy -= 1 }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def local_busy?
|
|
134
|
+
@local_busy.positive?
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def explain
|
|
138
|
+
<<-INFO.gsub(/^ {8}/, '')
|
|
139
|
+
Current sidekiq process: #{Selector.uuid}
|
|
140
|
+
|
|
141
|
+
All processes:
|
|
142
|
+
#{Monitor.all_processes.join "\n"}
|
|
143
|
+
|
|
144
|
+
Stale processes:
|
|
145
|
+
#{Monitor.old_processes.join "\n"}
|
|
146
|
+
|
|
147
|
+
Locked queue processes:
|
|
148
|
+
#{probed_processes.sort.join "\n"}
|
|
149
|
+
|
|
150
|
+
Busy queue processes:
|
|
151
|
+
#{busy_processes.sort.join "\n"}
|
|
152
|
+
|
|
153
|
+
Limit:
|
|
154
|
+
#{limit.inspect}
|
|
155
|
+
|
|
156
|
+
Process limit:
|
|
157
|
+
#{process_limit.inspect}
|
|
158
|
+
|
|
159
|
+
Blocking:
|
|
160
|
+
#{blocking?}
|
|
161
|
+
INFO
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def remove_locks_except!(processes)
|
|
165
|
+
locked_processes = probed_processes.uniq
|
|
166
|
+
(locked_processes - processes).each do |dead_process|
|
|
167
|
+
remove_lock! dead_process
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def remove_lock!(process)
|
|
172
|
+
redis do |it|
|
|
173
|
+
it.lrem "#{PREFIX}:probed:#{@name}", 0, process
|
|
174
|
+
it.lrem "#{PREFIX}:busy:#{@name}", 0, process
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
def redis(&block)
|
|
181
|
+
Sidekiq.redis(&block)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def namespace
|
|
185
|
+
Sidekiq::LimitFetch::Queues.namespace
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidekiq
|
|
4
|
+
module LimitFetch
|
|
5
|
+
module Instances
|
|
6
|
+
def self.extended(klass)
|
|
7
|
+
klass.instance_variable_set :@instances, {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def new(*args)
|
|
11
|
+
@instances[args] ||= super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
alias [] new
|
|
15
|
+
|
|
16
|
+
def instances
|
|
17
|
+
@instances.values
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reset_instances!
|
|
21
|
+
@instances = {}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def delete_instance(name)
|
|
25
|
+
@instances.delete [name]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidekiq
|
|
4
|
+
module LimitFetch
|
|
5
|
+
module Queues
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
THREAD_KEY = :acquired_queues
|
|
9
|
+
|
|
10
|
+
# rubocop:disable Metrics/AbcSize
|
|
11
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
12
|
+
# rubocop:disable Metrics/MethodLength
|
|
13
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
|
14
|
+
def start(capsule)
|
|
15
|
+
config = capsule.config
|
|
16
|
+
@queues = capsule.queues.map { |queue| queue.is_a?(Array) ? queue.first : queue }.uniq
|
|
17
|
+
|
|
18
|
+
@startup_queues = @queues.dup
|
|
19
|
+
|
|
20
|
+
if config[:dynamic].is_a? Hash
|
|
21
|
+
@dynamic = true
|
|
22
|
+
@dynamic_exclude = config[:dynamic][:exclude] || []
|
|
23
|
+
else
|
|
24
|
+
@dynamic = config[:dynamic]
|
|
25
|
+
@dynamic_exclude = []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@limits = config[:limits] || {}
|
|
29
|
+
@process_limits = config[:process_limits] || {}
|
|
30
|
+
@blocks = config[:blocking] || []
|
|
31
|
+
|
|
32
|
+
config[:strict] ? strict_order! : weighted_order!
|
|
33
|
+
|
|
34
|
+
apply_process_limit_to_queues
|
|
35
|
+
apply_limit_to_queues
|
|
36
|
+
apply_blocks_to_queues
|
|
37
|
+
end
|
|
38
|
+
# rubocop:enable Metrics/AbcSize
|
|
39
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
40
|
+
# rubocop:enable Metrics/MethodLength
|
|
41
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
42
|
+
|
|
43
|
+
def acquire
|
|
44
|
+
queues = saved
|
|
45
|
+
queues ||= Sidekiq::LimitFetch.redis_retryable do
|
|
46
|
+
selector.acquire(ordered_queues, namespace)
|
|
47
|
+
end
|
|
48
|
+
save queues
|
|
49
|
+
queues.map { |it| "queue:#{it}" }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def release_except(full_name)
|
|
53
|
+
queues = restore
|
|
54
|
+
queues.delete full_name[/queue:(.*)/, 1] if full_name
|
|
55
|
+
Sidekiq::LimitFetch.redis_retryable do
|
|
56
|
+
selector.release queues, namespace
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def dynamic?
|
|
61
|
+
@dynamic
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def startup_queue?(queue)
|
|
65
|
+
@startup_queues.include?(queue)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def dynamic_exclude
|
|
69
|
+
@dynamic_exclude
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def add(queues)
|
|
73
|
+
return unless queues
|
|
74
|
+
|
|
75
|
+
queues.each do |queue|
|
|
76
|
+
next if @queues.include? queue
|
|
77
|
+
|
|
78
|
+
if startup_queue?(queue)
|
|
79
|
+
apply_process_limit_to_queue(queue)
|
|
80
|
+
apply_limit_to_queue(queue)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
@queues.push queue
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def remove(queues)
|
|
88
|
+
return unless queues
|
|
89
|
+
|
|
90
|
+
queues.each do |queue|
|
|
91
|
+
next unless @queues.include? queue
|
|
92
|
+
|
|
93
|
+
clear_limits_for_queue(queue)
|
|
94
|
+
@queues.delete queue
|
|
95
|
+
Sidekiq::Queue.delete_instance(queue)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def handle(queues)
|
|
100
|
+
add(queues - @queues)
|
|
101
|
+
remove(@queues - queues)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# rubocop:disable Lint/NestedMethodDefinition
|
|
105
|
+
def strict_order!
|
|
106
|
+
@queues.uniq!
|
|
107
|
+
def ordered_queues
|
|
108
|
+
@queues
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def weighted_order!
|
|
113
|
+
def ordered_queues
|
|
114
|
+
@queues.shuffle.uniq
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
# rubocop:enable Lint/NestedMethodDefinition
|
|
118
|
+
|
|
119
|
+
def namespace
|
|
120
|
+
@namespace ||= Sidekiq.redis do |it|
|
|
121
|
+
if it.respond_to?(:namespace) && it.namespace
|
|
122
|
+
"#{it.namespace}:"
|
|
123
|
+
else
|
|
124
|
+
''
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def apply_process_limit_to_queues
|
|
132
|
+
@queues.uniq.each do |queue_name|
|
|
133
|
+
apply_process_limit_to_queue(queue_name)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def apply_process_limit_to_queue(queue_name)
|
|
138
|
+
queue = Sidekiq::Queue[queue_name]
|
|
139
|
+
return unless queue.process_limit.nil? # honor existing deployed limits
|
|
140
|
+
|
|
141
|
+
queue.process_limit = @process_limits[queue_name.to_s] || @process_limits[queue_name.to_sym]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def apply_limit_to_queues
|
|
145
|
+
@queues.uniq.each do |queue_name|
|
|
146
|
+
apply_limit_to_queue(queue_name)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def apply_limit_to_queue(queue_name)
|
|
151
|
+
queue = Sidekiq::Queue[queue_name]
|
|
152
|
+
|
|
153
|
+
return if queue.limit_changed?
|
|
154
|
+
return unless queue.limit.nil? # honor existing deployed limits
|
|
155
|
+
|
|
156
|
+
queue.limit = @limits[queue_name.to_s] || @limits[queue_name.to_sym]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def apply_blocks_to_queues
|
|
160
|
+
@queues.uniq.each do |queue_name|
|
|
161
|
+
Sidekiq::Queue[queue_name].unblock
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
@blocks.to_a.each do |it|
|
|
165
|
+
if it.is_a? Array
|
|
166
|
+
it.each { |name| Sidekiq::Queue[name].block_except it }
|
|
167
|
+
else
|
|
168
|
+
Sidekiq::Queue[it].block
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def clear_limits_for_queue(queue_name)
|
|
174
|
+
queue = Sidekiq::Queue[queue_name]
|
|
175
|
+
queue.clear_limits
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def selector
|
|
179
|
+
Sidekiq::LimitFetch::Global::Selector
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def saved
|
|
183
|
+
Thread.current[THREAD_KEY]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def save(queues)
|
|
187
|
+
Thread.current[THREAD_KEY] = queues
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def restore
|
|
191
|
+
saved || []
|
|
192
|
+
ensure
|
|
193
|
+
save nil
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidekiq
|
|
4
|
+
module LimitFetch
|
|
5
|
+
class UnitOfWork < BasicFetch::UnitOfWork
|
|
6
|
+
def initialize(...)
|
|
7
|
+
super
|
|
8
|
+
redis_retryable { Queue[queue_name].increase_busy }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def acknowledge
|
|
12
|
+
redis_retryable { Queue[queue_name].decrease_busy }
|
|
13
|
+
redis_retryable { Queue[queue_name].release }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def requeue
|
|
17
|
+
super
|
|
18
|
+
acknowledge
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def redis_retryable(&block)
|
|
24
|
+
Sidekiq::LimitFetch.redis_retryable(&block)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|