rcgt-sidekiq-limit_fetch 3.4.1
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 +3 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +161 -0
- data/Rakefile +12 -0
- data/bench/compare.rb +52 -0
- data/demo/Gemfile +7 -0
- data/demo/README.md +37 -0
- data/demo/Rakefile +99 -0
- data/demo/app/workers/a_worker.rb +8 -0
- data/demo/app/workers/b_worker.rb +8 -0
- data/demo/app/workers/c_worker.rb +9 -0
- data/demo/app/workers/fast_worker.rb +8 -0
- data/demo/app/workers/slow_worker.rb +8 -0
- data/demo/config/application.rb +11 -0
- data/demo/config/boot.rb +2 -0
- data/demo/config/environment.rb +2 -0
- data/demo/config/environments/development.rb +9 -0
- data/lib/sidekiq/extensions/manager.rb +16 -0
- data/lib/sidekiq/extensions/queue.rb +23 -0
- data/lib/sidekiq/limit_fetch/global/monitor.rb +72 -0
- data/lib/sidekiq/limit_fetch/global/selector.rb +125 -0
- data/lib/sidekiq/limit_fetch/global/semaphore.rb +175 -0
- data/lib/sidekiq/limit_fetch/instances.rb +19 -0
- data/lib/sidekiq/limit_fetch/queues.rb +124 -0
- data/lib/sidekiq/limit_fetch/unit_of_work.rb +24 -0
- data/lib/sidekiq/limit_fetch.rb +58 -0
- data/lib/sidekiq-limit_fetch.rb +1 -0
- data/rcgt-sidekiq-limit_fetch.gemspec +24 -0
- data/spec/sidekiq/extensions/queue_spec.rb +94 -0
- data/spec/sidekiq/limit_fetch/global/monitor_spec.rb +33 -0
- data/spec/sidekiq/limit_fetch/queues_spec.rb +100 -0
- data/spec/sidekiq/limit_fetch/semaphore_spec.rb +63 -0
- data/spec/sidekiq/limit_fetch_spec.rb +48 -0
- data/spec/spec_helper.rb +28 -0
- metadata +149 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
module Sidekiq::LimitFetch::Global
|
2
|
+
module Selector
|
3
|
+
extend self
|
4
|
+
|
5
|
+
MUTEX_FOR_UUID = Mutex.new
|
6
|
+
|
7
|
+
def acquire(queues, namespace)
|
8
|
+
redis_eval :acquire, [namespace, uuid, queues]
|
9
|
+
end
|
10
|
+
|
11
|
+
def release(queues, namespace)
|
12
|
+
redis_eval :release, [namespace, uuid, queues]
|
13
|
+
end
|
14
|
+
|
15
|
+
def uuid
|
16
|
+
# - if we'll remove "@uuid ||=" from inside of mutex
|
17
|
+
# then @uuid can be overwritten
|
18
|
+
# - if we'll remove "@uuid ||=" from outside of mutex
|
19
|
+
# then each read will lead to mutex
|
20
|
+
@uuid ||= MUTEX_FOR_UUID.synchronize { @uuid || SecureRandom.uuid }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def redis_eval(script_name, args)
|
26
|
+
Sidekiq.redis do |it|
|
27
|
+
begin
|
28
|
+
it.evalsha send("redis_#{script_name}_sha"), argv: args
|
29
|
+
rescue Redis::CommandError => error
|
30
|
+
raise unless error.message.include? 'NOSCRIPT'
|
31
|
+
it.eval send("redis_#{script_name}_script"), argv: args
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def redis_acquire_sha
|
37
|
+
@acquire_sha ||= Digest::SHA1.hexdigest redis_acquire_script
|
38
|
+
end
|
39
|
+
|
40
|
+
def redis_release_sha
|
41
|
+
@release_sha ||= Digest::SHA1.hexdigest redis_release_script
|
42
|
+
end
|
43
|
+
|
44
|
+
def redis_acquire_script
|
45
|
+
<<-LUA
|
46
|
+
local namespace = table.remove(ARGV, 1)..'limit_fetch:'
|
47
|
+
local worker_name = table.remove(ARGV, 1)
|
48
|
+
local queues = ARGV
|
49
|
+
local available = {}
|
50
|
+
local unblocked = {}
|
51
|
+
local locks
|
52
|
+
local process_locks
|
53
|
+
local blocking_mode
|
54
|
+
|
55
|
+
for _, queue in ipairs(queues) do
|
56
|
+
if not blocking_mode or unblocked[queue] then
|
57
|
+
local probed_key = namespace..'probed:'..queue
|
58
|
+
local pause_key = namespace..'pause:'..queue
|
59
|
+
local limit_key = namespace..'limit:'..queue
|
60
|
+
local process_limit_key = namespace..'process_limit:'..queue
|
61
|
+
local block_key = namespace..'block:'..queue
|
62
|
+
|
63
|
+
local paused, limit, process_limit, can_block =
|
64
|
+
unpack(redis.call('mget',
|
65
|
+
pause_key,
|
66
|
+
limit_key,
|
67
|
+
process_limit_key,
|
68
|
+
block_key
|
69
|
+
))
|
70
|
+
|
71
|
+
if not paused then
|
72
|
+
limit = tonumber(limit)
|
73
|
+
process_limit = tonumber(process_limit)
|
74
|
+
|
75
|
+
if can_block or limit then
|
76
|
+
locks = redis.call('llen', probed_key)
|
77
|
+
end
|
78
|
+
|
79
|
+
if process_limit then
|
80
|
+
local all_locks = redis.call('lrange', probed_key, 0, -1)
|
81
|
+
process_locks = 0
|
82
|
+
for _, process in ipairs(all_locks) do
|
83
|
+
if process == worker_name then
|
84
|
+
process_locks = process_locks + 1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if not blocking_mode then
|
90
|
+
blocking_mode = can_block and locks > 0
|
91
|
+
end
|
92
|
+
|
93
|
+
if blocking_mode and can_block ~= 'true' then
|
94
|
+
for unblocked_queue in string.gmatch(can_block, "[^,]+") do
|
95
|
+
unblocked[unblocked_queue] = true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
if (not limit or limit > locks) and
|
100
|
+
(not process_limit or process_limit > process_locks) then
|
101
|
+
redis.call('rpush', probed_key, worker_name)
|
102
|
+
table.insert(available, queue)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
return available
|
109
|
+
LUA
|
110
|
+
end
|
111
|
+
|
112
|
+
def redis_release_script
|
113
|
+
<<-LUA
|
114
|
+
local namespace = table.remove(ARGV, 1)..'limit_fetch:'
|
115
|
+
local worker_name = table.remove(ARGV, 1)
|
116
|
+
local queues = ARGV
|
117
|
+
|
118
|
+
for _, queue in ipairs(queues) do
|
119
|
+
local probed_key = namespace..'probed:'..queue
|
120
|
+
redis.call('lrem', probed_key, 1, worker_name)
|
121
|
+
end
|
122
|
+
LUA
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module Sidekiq::LimitFetch::Global
|
2
|
+
class Semaphore
|
3
|
+
PREFIX = 'limit_fetch'
|
4
|
+
|
5
|
+
attr_reader :local_busy
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
@lock = Mutex.new
|
10
|
+
@local_busy = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def limit
|
14
|
+
value = redis {|it| it.get "#{PREFIX}:limit:#@name" }
|
15
|
+
value.to_i if value
|
16
|
+
end
|
17
|
+
|
18
|
+
def limit=(value)
|
19
|
+
@limit_changed = true
|
20
|
+
|
21
|
+
if value
|
22
|
+
redis {|it| it.set "#{PREFIX}:limit:#@name", value }
|
23
|
+
else
|
24
|
+
redis {|it| it.del "#{PREFIX}:limit:#@name" }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def limit_changed?
|
29
|
+
@limit_changed
|
30
|
+
end
|
31
|
+
|
32
|
+
def process_limit
|
33
|
+
value = redis {|it| it.get "#{PREFIX}:process_limit:#@name" }
|
34
|
+
value.to_i if value
|
35
|
+
end
|
36
|
+
|
37
|
+
def process_limit=(value)
|
38
|
+
if value
|
39
|
+
redis {|it| it.set "#{PREFIX}:process_limit:#@name", value }
|
40
|
+
else
|
41
|
+
redis {|it| it.del "#{PREFIX}:process_limit:#@name" }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def acquire
|
46
|
+
Selector.acquire([@name], namespace).size > 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def release
|
50
|
+
redis {|it| it.lrem "#{PREFIX}:probed:#@name", 1, Selector.uuid }
|
51
|
+
end
|
52
|
+
|
53
|
+
def busy
|
54
|
+
redis {|it| it.llen "#{PREFIX}:busy:#@name" }
|
55
|
+
end
|
56
|
+
|
57
|
+
def busy_processes
|
58
|
+
redis {|it| it.lrange "#{PREFIX}:busy:#@name", 0, -1 }
|
59
|
+
end
|
60
|
+
|
61
|
+
def increase_busy
|
62
|
+
increase_local_busy
|
63
|
+
redis {|it| it.rpush "#{PREFIX}:busy:#@name", Selector.uuid }
|
64
|
+
end
|
65
|
+
|
66
|
+
def decrease_busy
|
67
|
+
decrease_local_busy
|
68
|
+
redis {|it| it.lrem "#{PREFIX}:busy:#@name", 1, Selector.uuid }
|
69
|
+
end
|
70
|
+
|
71
|
+
def probed
|
72
|
+
redis {|it| it.llen "#{PREFIX}:probed:#@name" }
|
73
|
+
end
|
74
|
+
|
75
|
+
def probed_processes
|
76
|
+
redis {|it| it.lrange "#{PREFIX}:probed:#@name", 0, -1 }
|
77
|
+
end
|
78
|
+
|
79
|
+
def pause
|
80
|
+
redis {|it| it.set "#{PREFIX}:pause:#@name", true }
|
81
|
+
end
|
82
|
+
|
83
|
+
def pause_for_ms ms
|
84
|
+
redis {|it| it.psetex "#{PREFIX}:pause:#@name", ms, true }
|
85
|
+
end
|
86
|
+
|
87
|
+
def unpause
|
88
|
+
redis {|it| it.del "#{PREFIX}:pause:#@name" }
|
89
|
+
end
|
90
|
+
|
91
|
+
def paused?
|
92
|
+
redis {|it| it.get "#{PREFIX}:pause:#@name" }
|
93
|
+
end
|
94
|
+
|
95
|
+
def block
|
96
|
+
redis {|it| it.set "#{PREFIX}:block:#@name", true }
|
97
|
+
end
|
98
|
+
|
99
|
+
def block_except(*queues)
|
100
|
+
raise ArgumentError if queues.empty?
|
101
|
+
redis {|it| it.set "#{PREFIX}:block:#@name", queues.join(',') }
|
102
|
+
end
|
103
|
+
|
104
|
+
def unblock
|
105
|
+
redis {|it| it.del "#{PREFIX}:block:#@name" }
|
106
|
+
end
|
107
|
+
|
108
|
+
def blocking?
|
109
|
+
redis {|it| it.get "#{PREFIX}:block:#@name" }
|
110
|
+
end
|
111
|
+
|
112
|
+
def increase_local_busy
|
113
|
+
@lock.synchronize { @local_busy += 1 }
|
114
|
+
end
|
115
|
+
|
116
|
+
def decrease_local_busy
|
117
|
+
@lock.synchronize { @local_busy -= 1 }
|
118
|
+
end
|
119
|
+
|
120
|
+
def local_busy?
|
121
|
+
@local_busy > 0
|
122
|
+
end
|
123
|
+
|
124
|
+
def explain
|
125
|
+
<<-END.gsub(/^ {8}/, '')
|
126
|
+
Current sidekiq process: #{Selector.uuid}
|
127
|
+
|
128
|
+
All processes:
|
129
|
+
#{Monitor.all_processes.join "\n"}
|
130
|
+
|
131
|
+
Stale processes:
|
132
|
+
#{Monitor.old_processes.join "\n"}
|
133
|
+
|
134
|
+
Locked queue processes:
|
135
|
+
#{probed_processes.sort.join "\n"}
|
136
|
+
|
137
|
+
Busy queue processes:
|
138
|
+
#{busy_processes.sort.join "\n"}
|
139
|
+
|
140
|
+
Limit:
|
141
|
+
#{limit.inspect}
|
142
|
+
|
143
|
+
Process limit:
|
144
|
+
#{process_limit.inspect}
|
145
|
+
|
146
|
+
Blocking:
|
147
|
+
#{blocking?}
|
148
|
+
END
|
149
|
+
end
|
150
|
+
|
151
|
+
def remove_locks_except!(processes)
|
152
|
+
locked_processes = probed_processes.uniq
|
153
|
+
(locked_processes - processes).each do |dead_process|
|
154
|
+
remove_lock! dead_process
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def remove_lock!(process)
|
159
|
+
redis do |it|
|
160
|
+
it.lrem "#{PREFIX}:probed:#@name", 0, process
|
161
|
+
it.lrem "#{PREFIX}:busy:#@name", 0, process
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def redis(&block)
|
168
|
+
Sidekiq.redis(&block)
|
169
|
+
end
|
170
|
+
|
171
|
+
def namespace
|
172
|
+
Sidekiq::LimitFetch::Queues.namespace
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Sidekiq::LimitFetch::Instances
|
2
|
+
def self.extended(klass)
|
3
|
+
klass.instance_variable_set :@instances, {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def new(*args)
|
7
|
+
@instances[args] ||= super
|
8
|
+
end
|
9
|
+
|
10
|
+
alias [] new
|
11
|
+
|
12
|
+
def instances
|
13
|
+
@instances.values
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset_instances!
|
17
|
+
@instances = {}
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Sidekiq::LimitFetch::Queues
|
2
|
+
extend self
|
3
|
+
|
4
|
+
THREAD_KEY = :acquired_queues
|
5
|
+
|
6
|
+
def start(options)
|
7
|
+
@queues = options[:queues]
|
8
|
+
@dynamic = options[:dynamic]
|
9
|
+
|
10
|
+
@limits = options[:limits] || {}
|
11
|
+
@process_limits = options[:process_limits] || {}
|
12
|
+
@blocks = options[:blocking] || []
|
13
|
+
|
14
|
+
options[:strict] ? strict_order! : weighted_order!
|
15
|
+
|
16
|
+
apply_process_limit_to_queues
|
17
|
+
apply_limit_to_queues
|
18
|
+
apply_blocks_to_queues
|
19
|
+
end
|
20
|
+
|
21
|
+
def acquire
|
22
|
+
selector.acquire(ordered_queues, namespace)
|
23
|
+
.tap {|it| save it }
|
24
|
+
.map {|it| "queue:#{it}" }
|
25
|
+
end
|
26
|
+
|
27
|
+
def release_except(full_name)
|
28
|
+
queues = restore
|
29
|
+
queues.delete full_name[/queue:(.*)/, 1] if full_name
|
30
|
+
|
31
|
+
Sidekiq::LimitFetch.redis_retryable do
|
32
|
+
selector.release queues, namespace
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def dynamic?
|
37
|
+
@dynamic
|
38
|
+
end
|
39
|
+
|
40
|
+
def add(queues)
|
41
|
+
queues.each do |queue|
|
42
|
+
unless @queues.include? queue
|
43
|
+
apply_process_limit_to_queue(queue)
|
44
|
+
apply_limit_to_queue(queue)
|
45
|
+
|
46
|
+
@queues.push queue
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def strict_order!
|
52
|
+
@queues.uniq!
|
53
|
+
def ordered_queues; @queues end
|
54
|
+
end
|
55
|
+
|
56
|
+
def weighted_order!
|
57
|
+
def ordered_queues; @queues.shuffle.uniq end
|
58
|
+
end
|
59
|
+
|
60
|
+
def namespace
|
61
|
+
@namespace ||= Sidekiq.redis do |it|
|
62
|
+
if it.respond_to?(:namespace) and it.namespace
|
63
|
+
"#{it.namespace}:"
|
64
|
+
else
|
65
|
+
''
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def apply_process_limit_to_queues
|
73
|
+
@queues.uniq.each do |queue_name|
|
74
|
+
apply_process_limit_to_queue(queue_name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def apply_process_limit_to_queue(queue_name)
|
79
|
+
queue = Sidekiq::Queue[queue_name]
|
80
|
+
queue.process_limit = @process_limits[queue_name.to_s] || @process_limits[queue_name.to_sym]
|
81
|
+
end
|
82
|
+
|
83
|
+
def apply_limit_to_queues
|
84
|
+
@queues.uniq.each do |queue_name|
|
85
|
+
apply_limit_to_queue(queue_name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def apply_limit_to_queue(queue_name)
|
90
|
+
queue = Sidekiq::Queue[queue_name]
|
91
|
+
|
92
|
+
unless queue.limit_changed?
|
93
|
+
queue.limit = @limits[queue_name.to_s] || @limits[queue_name.to_sym]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def apply_blocks_to_queues
|
98
|
+
@queues.uniq.each do |queue_name|
|
99
|
+
Sidekiq::Queue[queue_name].unblock
|
100
|
+
end
|
101
|
+
|
102
|
+
@blocks.to_a.each do |it|
|
103
|
+
if it.is_a? Array
|
104
|
+
it.each {|name| Sidekiq::Queue[name].block_except it }
|
105
|
+
else
|
106
|
+
Sidekiq::Queue[it].block
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def selector
|
112
|
+
Sidekiq::LimitFetch::Global::Selector
|
113
|
+
end
|
114
|
+
|
115
|
+
def save(queues)
|
116
|
+
Thread.current[THREAD_KEY] = queues
|
117
|
+
end
|
118
|
+
|
119
|
+
def restore
|
120
|
+
Thread.current[THREAD_KEY] || []
|
121
|
+
ensure
|
122
|
+
Thread.current[THREAD_KEY] = nil
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
class LimitFetch::UnitOfWork < BasicFetch::UnitOfWork
|
3
|
+
def initialize(queue, job)
|
4
|
+
super
|
5
|
+
redis_retryable { Queue[queue_name].increase_busy }
|
6
|
+
end
|
7
|
+
|
8
|
+
def acknowledge
|
9
|
+
redis_retryable { Queue[queue_name].decrease_busy }
|
10
|
+
redis_retryable { Queue[queue_name].release }
|
11
|
+
end
|
12
|
+
|
13
|
+
def requeue
|
14
|
+
super
|
15
|
+
acknowledge
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def redis_retryable(&block)
|
21
|
+
Sidekiq::LimitFetch.redis_retryable(&block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'sidekiq'
|
3
|
+
require 'sidekiq/manager'
|
4
|
+
require 'sidekiq/api'
|
5
|
+
|
6
|
+
module Sidekiq::LimitFetch
|
7
|
+
autoload :UnitOfWork, 'sidekiq/limit_fetch/unit_of_work'
|
8
|
+
|
9
|
+
require_relative 'limit_fetch/instances'
|
10
|
+
require_relative 'limit_fetch/queues'
|
11
|
+
require_relative 'limit_fetch/global/semaphore'
|
12
|
+
require_relative 'limit_fetch/global/selector'
|
13
|
+
require_relative 'limit_fetch/global/monitor'
|
14
|
+
require_relative 'extensions/queue'
|
15
|
+
require_relative 'extensions/manager'
|
16
|
+
|
17
|
+
extend self
|
18
|
+
|
19
|
+
def new(_)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def retrieve_work
|
24
|
+
queue, job = redis_brpop(Queues.acquire)
|
25
|
+
Queues.release_except(queue)
|
26
|
+
UnitOfWork.new(queue, job) if job
|
27
|
+
end
|
28
|
+
|
29
|
+
# Backwards compatibility for sidekiq v6.1.0
|
30
|
+
# @see https://github.com/mperham/sidekiq/pull/4602
|
31
|
+
def bulk_requeue(*args)
|
32
|
+
if Sidekiq::BasicFetch.respond_to?(:bulk_requeue) # < 6.1.0
|
33
|
+
Sidekiq::BasicFetch.bulk_requeue(*args)
|
34
|
+
else # 6.1.0+
|
35
|
+
Sidekiq::BasicFetch.new(Sidekiq.options).bulk_requeue(*args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def redis_retryable
|
40
|
+
yield
|
41
|
+
rescue Redis::BaseConnectionError
|
42
|
+
sleep 1
|
43
|
+
retry
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
TIMEOUT = Sidekiq::BasicFetch::TIMEOUT
|
49
|
+
|
50
|
+
def redis_brpop(queues)
|
51
|
+
if queues.empty?
|
52
|
+
sleep TIMEOUT # there are no queues to handle, so lets sleep
|
53
|
+
[] # and return nothing
|
54
|
+
else
|
55
|
+
redis_retryable { Sidekiq.redis { |it| it.brpop *queues, TIMEOUT } }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'sidekiq/limit_fetch'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = 'rcgt-sidekiq-limit_fetch'
|
3
|
+
gem.version = '3.4.1'
|
4
|
+
gem.license = 'MIT'
|
5
|
+
gem.authors = 'RCGT Consulting Inc.'
|
6
|
+
gem.email = 'kumanan.yogaratnam@rcgtconsulting.com'
|
7
|
+
gem.summary = 'Sidekiq strategy to support queue limits'
|
8
|
+
gem.homepage = 'https://github.com/RCGTConsulting/sidekiq-limit_fetch'
|
9
|
+
gem.description = <<-DESCRIPTION
|
10
|
+
A fork of https://github.com/brainopia/sidekiq-limit_fetch
|
11
|
+
|
12
|
+
Sidekiq strategy to restrict number of workers
|
13
|
+
which are able to run specified queues simultaneously.
|
14
|
+
DESCRIPTION
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.test_files = gem.files.grep %r{^spec/}
|
18
|
+
gem.require_paths = %w(lib)
|
19
|
+
|
20
|
+
gem.add_dependency 'sidekiq', '>= 4'
|
21
|
+
gem.add_development_dependency 'redis-namespace', '~> 1.5', '>= 1.5.2'
|
22
|
+
gem.add_development_dependency 'rspec'
|
23
|
+
gem.add_development_dependency 'rake'
|
24
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
RSpec.describe Sidekiq::Queue do
|
2
|
+
context 'singleton' do
|
3
|
+
shared_examples :constructor do
|
4
|
+
it 'with default name' do
|
5
|
+
new_object = -> { described_class.send constructor }
|
6
|
+
expect(new_object.call).to eq new_object.call
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'with given name' do
|
10
|
+
new_object = ->(name) { described_class.send constructor, name }
|
11
|
+
expect(new_object.call('name')).to eq new_object.call('name')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context '.new' do
|
16
|
+
let(:constructor) { :new }
|
17
|
+
it_behaves_like :constructor
|
18
|
+
end
|
19
|
+
|
20
|
+
context '.[]' do
|
21
|
+
let(:constructor) { :[] }
|
22
|
+
it_behaves_like :constructor
|
23
|
+
end
|
24
|
+
|
25
|
+
context '#lock' do
|
26
|
+
let(:name) { 'example' }
|
27
|
+
let(:queue) { Sidekiq::Queue[name] }
|
28
|
+
|
29
|
+
it 'should be available' do
|
30
|
+
expect(queue.acquire).to be
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should be pausable' do
|
34
|
+
queue.pause
|
35
|
+
expect(queue.acquire).not_to be
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should be continuable' do
|
39
|
+
queue.pause
|
40
|
+
queue.unpause
|
41
|
+
expect(queue.acquire).to be
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should be limitable' do
|
45
|
+
queue.limit = 1
|
46
|
+
expect(queue.acquire).to be
|
47
|
+
expect(queue.acquire).not_to be
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should be resizable' do
|
51
|
+
queue.limit = 0
|
52
|
+
expect(queue.acquire).not_to be
|
53
|
+
queue.limit = nil
|
54
|
+
expect(queue.acquire).to be
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should be countable' do
|
58
|
+
queue.limit = 3
|
59
|
+
5.times { queue.acquire }
|
60
|
+
expect(queue.probed).to eq 3
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should be releasable' do
|
64
|
+
queue.acquire
|
65
|
+
expect(queue.probed).to eq 1
|
66
|
+
queue.release
|
67
|
+
expect(queue.probed).to eq 0
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should tell if paused' do
|
71
|
+
expect(queue).not_to be_paused
|
72
|
+
queue.pause
|
73
|
+
expect(queue).to be_paused
|
74
|
+
queue.unpause
|
75
|
+
expect(queue).not_to be_paused
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should tell if blocking' do
|
79
|
+
expect(queue).not_to be_blocking
|
80
|
+
queue.block
|
81
|
+
expect(queue).to be_blocking
|
82
|
+
queue.unblock
|
83
|
+
expect(queue).not_to be_blocking
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should be marked as changed' do
|
87
|
+
queue = Sidekiq::Queue["uniq_#{name}"]
|
88
|
+
expect(queue).not_to be_limit_changed
|
89
|
+
queue.limit = 3
|
90
|
+
expect(queue).to be_limit_changed
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
RSpec.describe Sidekiq::LimitFetch::Global::Monitor do
|
2
|
+
let(:monitor) { described_class.start! ttl, timeout }
|
3
|
+
let(:ttl) { 1 }
|
4
|
+
let(:queue) { Sidekiq::Queue[name] }
|
5
|
+
let(:name) { 'default' }
|
6
|
+
|
7
|
+
before { monitor }
|
8
|
+
after { monitor.kill }
|
9
|
+
|
10
|
+
context 'old locks' do
|
11
|
+
let(:timeout) { 0.5 }
|
12
|
+
|
13
|
+
it 'should remove invalidated old locks' do
|
14
|
+
2.times { queue.acquire }
|
15
|
+
sleep 2*ttl
|
16
|
+
expect(queue.probed).to eq 2
|
17
|
+
|
18
|
+
allow(described_class).to receive(:update_heartbeat)
|
19
|
+
sleep 2*ttl
|
20
|
+
expect(queue.probed).to eq 0
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should remove invalid locks' do
|
24
|
+
2.times { queue.acquire }
|
25
|
+
allow(described_class).to receive(:update_heartbeat)
|
26
|
+
Sidekiq.redis do |it|
|
27
|
+
it.del Sidekiq::LimitFetch::Global::Monitor::PROCESS_SET
|
28
|
+
end
|
29
|
+
sleep 2*ttl
|
30
|
+
expect(queue.probed).to eq 0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|