sidekiq-limit_fetch 4.4.0 → 4.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 +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
|