sidekiq-limit_fetch 4.4.0 → 4.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -0
- data/.rubocop.yml +29 -0
- data/Appraisals +2 -0
- data/Gemfile +2 -0
- data/Rakefile +2 -0
- data/bench/compare.rb +17 -13
- data/demo/Gemfile +3 -2
- data/demo/Rakefile +6 -5
- data/demo/app/workers/a_worker.rb +2 -0
- data/demo/app/workers/b_worker.rb +2 -0
- data/demo/app/workers/c_worker.rb +2 -1
- data/demo/app/workers/fast_worker.rb +2 -0
- data/demo/app/workers/slow_worker.rb +2 -0
- data/demo/config/application.rb +3 -1
- data/demo/config/boot.rb +4 -2
- data/demo/config/environment.rb +3 -1
- data/demo/config/environments/development.rb +2 -0
- data/docker-compose.dev.yml +2 -2
- data/lib/sidekiq/extensions/manager.rb +20 -17
- data/lib/sidekiq/extensions/queue.rb +16 -13
- data/lib/sidekiq/limit_fetch/global/monitor.rb +64 -58
- data/lib/sidekiq/limit_fetch/global/selector.rb +49 -44
- data/lib/sidekiq/limit_fetch/global/semaphore.rb +130 -123
- data/lib/sidekiq/limit_fetch/instances.rb +22 -16
- data/lib/sidekiq/limit_fetch/queues.rb +163 -137
- data/lib/sidekiq/limit_fetch/unit_of_work.rb +26 -22
- data/lib/sidekiq/limit_fetch.rb +68 -64
- data/lib/sidekiq-limit_fetch.rb +2 -0
- data/sidekiq-limit_fetch.gemspec +18 -10
- data/spec/sidekiq/extensions/manager_spec.rb +19 -0
- data/spec/sidekiq/extensions/queue_spec.rb +2 -0
- data/spec/sidekiq/limit_fetch/global/monitor_spec.rb +59 -32
- data/spec/sidekiq/limit_fetch/queues_spec.rb +34 -18
- data/spec/sidekiq/limit_fetch/semaphore_spec.rb +2 -0
- data/spec/sidekiq/limit_fetch_spec.rb +14 -4
- data/spec/spec_helper.rb +8 -3
- metadata +39 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8b031f9486e9a497f130f2b2ed58058980cdc14985895c4cfa9ab29dbb16509
|
4
|
+
data.tar.gz: 5c5c8c2d5b144405fc5a57bf5de67dda9709939c59bdcc7870f92ae43b95de55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a1cb3a5542dd9888fce9bddaabc7e8e862df7741750b9323ae637970c08be263bab2e662965b89fefb8a02f830e5f4493d46bad2e0dc6330d143cc801c2dc15
|
7
|
+
data.tar.gz: 760cc8e8bd550b7dbcd3caaa03340d9d5629f83746197254e486f07c01f52b05afb6c964afb5eb4fc4c3a95a64c1425fc358d5e806a54bf38ae4521a6ed48d06
|
data/.github/workflows/ci.yml
CHANGED
@@ -36,6 +36,8 @@ jobs:
|
|
36
36
|
bundler-cache: false
|
37
37
|
- name: Bundle for Appraisal
|
38
38
|
run: bundle
|
39
|
+
- name: Run Rubocop linting
|
40
|
+
run: bundle exec rubocop
|
39
41
|
- name: Install Appraisal dependencies
|
40
42
|
run: bundle exec appraisal sidekiq-${{ matrix.appraisal }} bundle
|
41
43
|
- name: Run tests
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
AllCops:
|
2
|
+
NewCops: enable
|
3
|
+
SuggestExtensions: false
|
4
|
+
TargetRubyVersion: 2.7
|
5
|
+
Exclude:
|
6
|
+
- 'gemfiles/*'
|
7
|
+
|
8
|
+
Style/Documentation:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
Metrics/BlockLength:
|
12
|
+
Exclude:
|
13
|
+
- 'spec/**/*.rb'
|
14
|
+
- 'demo/Rakefile'
|
15
|
+
|
16
|
+
Metrics/ModuleLength:
|
17
|
+
Exclude:
|
18
|
+
- 'lib/sidekiq/limit_fetch/queues.rb'
|
19
|
+
|
20
|
+
Metrics/ClassLength:
|
21
|
+
Exclude:
|
22
|
+
- 'lib/sidekiq/limit_fetch/global/semaphore.rb'
|
23
|
+
|
24
|
+
Naming/VariableNumber:
|
25
|
+
EnforcedStyle: snake_case
|
26
|
+
|
27
|
+
Naming/FileName:
|
28
|
+
Exclude:
|
29
|
+
- lib/sidekiq-limit_fetch.rb
|
data/Appraisals
CHANGED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/bench/compare.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'benchmark'
|
2
4
|
require 'sidekiq/cli'
|
3
5
|
require 'sidekiq/api'
|
@@ -9,11 +11,11 @@ limit = ARGV.shift
|
|
9
11
|
if limit
|
10
12
|
limit = nil if limit == 'nil'
|
11
13
|
|
12
|
-
|
14
|
+
$LOAD_PATH.unshift File.expand_path '../lib'
|
13
15
|
require 'sidekiq-limit_fetch'
|
14
16
|
Sidekiq::Queue['inline'].limit = limit
|
15
|
-
Sidekiq.redis {|it| it.del 'limit_fetch:probed:inline' }
|
16
|
-
Sidekiq::LimitFetch::Queues.send(:define_method, :set) {|*| }
|
17
|
+
Sidekiq.redis { |it| it.del 'limit_fetch:probed:inline' }
|
18
|
+
Sidekiq::LimitFetch::Queues.send(:define_method, :set) { |*| } # rubocop:disable Lint/EmptyBlock
|
17
19
|
end
|
18
20
|
|
19
21
|
Sidekiq::Queue.new('inline').clear
|
@@ -22,8 +24,8 @@ class FastJob
|
|
22
24
|
include Sidekiq::Worker
|
23
25
|
sidekiq_options queue: :inline
|
24
26
|
|
25
|
-
def perform(
|
26
|
-
puts "job N#{
|
27
|
+
def perform(index)
|
28
|
+
puts "job N#{index} is finished"
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
@@ -36,17 +38,19 @@ class FinishJob
|
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
total.times {|i| FastJob.perform_async i+1 }
|
41
|
+
total.times { |i| FastJob.perform_async i + 1 }
|
40
42
|
FinishJob.perform_async
|
41
43
|
|
42
44
|
Sidekiq::CLI.instance.tap do |cli|
|
43
|
-
%w
|
45
|
+
%w[validate! boot_system].each { |stub| cli.define_singleton_method(stub) {} } # rubocop:disable Lint/EmptyBlock
|
44
46
|
cli.parse ['-q inline', '-q other', "-c #{concurrency}"]
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
# rubocop:disable Lint/RescueException
|
49
|
+
# rubocop:disable Lint/SuppressedException
|
50
|
+
puts Benchmark.measure do
|
51
|
+
cli.run
|
52
|
+
rescue Exception
|
53
|
+
end
|
54
|
+
# rubocop:enable Lint/SuppressedException
|
55
|
+
# rubocop:enable Lint/RescueException
|
52
56
|
end
|
data/demo/Gemfile
CHANGED
data/demo/Rakefile
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('config/application', __dir__)
|
2
4
|
Demo::Application.load_tasks
|
3
5
|
|
4
6
|
namespace :demo do
|
@@ -63,7 +65,7 @@ namespace :demo do
|
|
63
65
|
end
|
64
66
|
def with_sidekiq_config(config)
|
65
67
|
whitespace_offset = config[/\A */].size
|
66
|
-
config.gsub!
|
68
|
+
config.gsub!(/^ {#{whitespace_offset}}/, '')
|
67
69
|
|
68
70
|
puts "=> Use sidekiq config:\n#{config}"
|
69
71
|
File.write 'config/sidekiq.yml', config
|
@@ -85,8 +87,8 @@ namespace :demo do
|
|
85
87
|
require 'sidekiq/cli'
|
86
88
|
cli = Sidekiq::CLI.instance
|
87
89
|
|
88
|
-
%w
|
89
|
-
cli.define_singleton_method(stub) {}
|
90
|
+
%w[validate! boot_system].each do |stub|
|
91
|
+
cli.define_singleton_method(stub) {} # rubocop:disable Lint/EmptyBlock
|
90
92
|
end
|
91
93
|
|
92
94
|
with_sidekiq_config options[:config] do
|
@@ -96,4 +98,3 @@ namespace :demo do
|
|
96
98
|
cli.run
|
97
99
|
end
|
98
100
|
end
|
99
|
-
|
data/demo/config/application.rb
CHANGED
data/demo/config/boot.rb
CHANGED
@@ -1,2 +1,4 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
4
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
data/demo/config/environment.rb
CHANGED
data/docker-compose.dev.yml
CHANGED
@@ -1,22 +1,25 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
class Manager
|
5
|
+
module InitLimitFetch
|
6
|
+
def initialize(capsule_or_options)
|
7
|
+
if Sidekiq::LimitFetch.post_7?
|
8
|
+
capsule_or_options.config[:fetch_class] = Sidekiq::LimitFetch
|
9
|
+
else
|
10
|
+
capsule_or_options[:fetch] = Sidekiq::LimitFetch
|
11
|
+
end
|
12
|
+
super
|
8
13
|
end
|
9
|
-
|
10
|
-
super
|
11
|
-
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def start
|
16
|
+
# In sidekiq 6.5.0 the variable @options has been renamed to @config
|
17
|
+
Sidekiq::LimitFetch::Queues.start @options || @config
|
18
|
+
Sidekiq::LimitFetch::Global::Monitor.start!
|
19
|
+
super
|
20
|
+
end
|
18
21
|
end
|
19
|
-
end
|
20
22
|
|
21
|
-
|
23
|
+
prepend InitLimitFetch
|
24
|
+
end
|
22
25
|
end
|
@@ -1,21 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Sidekiq
|
2
4
|
class Queue
|
3
|
-
extend
|
5
|
+
extend Forwardable
|
6
|
+
extend LimitFetch::Instances
|
4
7
|
attr_reader :rname
|
5
8
|
|
6
9
|
def_delegators :lock,
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
:limit, :limit=, :limit_changed?,
|
11
|
+
:process_limit, :process_limit=,
|
12
|
+
:acquire, :release,
|
13
|
+
:pause, :pause_for_ms, :unpause,
|
14
|
+
:block, :unblock,
|
15
|
+
:paused?, :blocking?,
|
16
|
+
:unblocked, :block_except,
|
17
|
+
:probed, :busy,
|
18
|
+
:increase_busy, :decrease_busy,
|
19
|
+
:local_busy?, :explain,
|
20
|
+
:remove_locks_except!,
|
21
|
+
:clear_limits
|
19
22
|
|
20
23
|
def lock
|
21
24
|
@lock ||= LimitFetch::Global::Semaphore.new name
|
@@ -1,77 +1,83 @@
|
|
1
|
-
|
2
|
-
module Monitor
|
3
|
-
extend self
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
module Sidekiq
|
4
|
+
module LimitFetch
|
5
|
+
module Global
|
6
|
+
module Monitor
|
7
|
+
extend self
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
18
22
|
|
19
|
-
|
23
|
+
sleep timeout
|
24
|
+
end
|
25
|
+
end
|
20
26
|
end
|
21
|
-
end
|
22
|
-
end
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
28
|
+
def all_processes
|
29
|
+
Sidekiq.redis { |it| it.smembers PROCESS_SET }
|
30
|
+
end
|
27
31
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
32
|
+
def old_processes
|
33
|
+
all_processes.reject do |process|
|
34
|
+
Sidekiq.redis { |it| it.get heartbeat_key process } == '1'
|
35
|
+
end
|
36
|
+
end
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
def remove_old_processes!
|
39
|
+
Sidekiq.redis do |it|
|
40
|
+
old_processes.each { |process| it.srem PROCESS_SET, [process] }
|
41
|
+
end
|
42
|
+
end
|
39
43
|
|
40
|
-
|
41
|
-
|
42
|
-
|
44
|
+
def handle_dynamic_queues
|
45
|
+
queues = Sidekiq::LimitFetch::Queues
|
46
|
+
return unless queues.dynamic?
|
43
47
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
49
53
|
|
50
|
-
|
54
|
+
private
|
51
55
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
58
64
|
end
|
59
|
-
end
|
60
|
-
end
|
61
65
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
+
def invalidate_old_processes
|
67
|
+
Sidekiq.redis do |_it|
|
68
|
+
remove_old_processes!
|
69
|
+
processes = all_processes
|
66
70
|
|
67
|
-
|
68
|
-
|
71
|
+
Sidekiq::Queue.instances.each do |queue|
|
72
|
+
queue.remove_locks_except! processes
|
73
|
+
end
|
74
|
+
end
|
69
75
|
end
|
70
|
-
end
|
71
|
-
end
|
72
76
|
|
73
|
-
|
74
|
-
|
77
|
+
def heartbeat_key(process = Selector.uuid)
|
78
|
+
HEARTBEAT_PREFIX + process
|
79
|
+
end
|
80
|
+
end
|
75
81
|
end
|
76
82
|
end
|
77
83
|
end
|
@@ -1,52 +1,55 @@
|
|
1
|
-
|
2
|
-
module Selector
|
3
|
-
extend self
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
3
|
+
module Sidekiq
|
4
|
+
module LimitFetch
|
5
|
+
module Global
|
6
|
+
module Selector
|
7
|
+
extend self
|
6
8
|
|
7
|
-
|
8
|
-
redis_eval :acquire, [namespace, uuid, queues]
|
9
|
-
end
|
9
|
+
MUTEX_FOR_UUID = Mutex.new
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
def acquire(queues, namespace)
|
12
|
+
redis_eval :acquire, [namespace, uuid, queues]
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
15
|
+
def release(queues, namespace)
|
16
|
+
redis_eval :release, [namespace, uuid, queues]
|
17
|
+
end
|
22
18
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
+
if Sidekiq::LimitFetch.post_7?
|
36
|
+
it.eval send("redis_#{script_name}_script"), 0, *args
|
37
|
+
else
|
38
|
+
it.eval send("redis_#{script_name}_script"), argv: args
|
39
|
+
end
|
35
40
|
end
|
36
41
|
end
|
37
|
-
end
|
38
|
-
end
|
39
42
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
+
def redis_acquire_sha
|
44
|
+
@redis_acquire_sha ||= OpenSSL::Digest::SHA1.hexdigest redis_acquire_script
|
45
|
+
end
|
43
46
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
def redis_release_sha
|
48
|
+
@redis_release_sha ||= OpenSSL::Digest::SHA1.hexdigest redis_release_script
|
49
|
+
end
|
47
50
|
|
48
|
-
|
49
|
-
|
51
|
+
def redis_acquire_script
|
52
|
+
<<-LUA
|
50
53
|
local namespace = table.remove(ARGV, 1)..'limit_fetch:'
|
51
54
|
local worker_name = table.remove(ARGV, 1)
|
52
55
|
local queues = ARGV
|
@@ -110,11 +113,11 @@ module Sidekiq::LimitFetch::Global
|
|
110
113
|
end
|
111
114
|
|
112
115
|
return available
|
113
|
-
|
114
|
-
|
116
|
+
LUA
|
117
|
+
end
|
115
118
|
|
116
|
-
|
117
|
-
|
119
|
+
def redis_release_script
|
120
|
+
<<-LUA
|
118
121
|
local namespace = table.remove(ARGV, 1)..'limit_fetch:'
|
119
122
|
local worker_name = table.remove(ARGV, 1)
|
120
123
|
local queues = ARGV
|
@@ -123,7 +126,9 @@ module Sidekiq::LimitFetch::Global
|
|
123
126
|
local probed_key = namespace..'probed:'..queue
|
124
127
|
redis.call('lrem', probed_key, 1, worker_name)
|
125
128
|
end
|
126
|
-
|
129
|
+
LUA
|
130
|
+
end
|
131
|
+
end
|
127
132
|
end
|
128
133
|
end
|
129
134
|
end
|