sidekiq-throttled 0.10.0 → 0.14.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 +54 -0
- data/.rubocop/layout.yml +24 -0
- data/.rubocop/lint.yml +41 -0
- data/.rubocop/metrics.yml +4 -0
- data/.rubocop/performance.yml +25 -0
- data/.rubocop/rspec.yml +3 -0
- data/.rubocop/style.yml +84 -0
- data/.rubocop.yml +13 -68
- data/.rubocop_todo.yml +10 -35
- data/.travis.yml +2 -1
- data/Appraisals +8 -0
- data/CHANGES.md +28 -0
- data/Gemfile +6 -5
- data/LICENSE.md +1 -1
- data/README.md +75 -15
- data/gemfiles/sidekiq_5.0.gemfile +6 -5
- data/gemfiles/sidekiq_5.1.gemfile +6 -5
- data/gemfiles/sidekiq_5.2.gemfile +6 -5
- data/gemfiles/sidekiq_6.0.gemfile +31 -0
- data/gemfiles/sidekiq_6.1.gemfile +31 -0
- data/lib/sidekiq/throttled/communicator/callbacks.rb +7 -6
- data/lib/sidekiq/throttled/communicator/listener.rb +3 -3
- data/lib/sidekiq/throttled/expirable_list.rb +1 -1
- data/lib/sidekiq/throttled/fetch.rb +34 -20
- data/lib/sidekiq/throttled/queue_name.rb +1 -1
- data/lib/sidekiq/throttled/registry.rb +2 -5
- data/lib/sidekiq/throttled/strategy/concurrency.rb +5 -5
- data/lib/sidekiq/throttled/strategy/threshold.rb +6 -5
- data/lib/sidekiq/throttled/strategy.rb +23 -14
- data/lib/sidekiq/throttled/strategy_collection.rb +70 -0
- data/lib/sidekiq/throttled/utils.rb +1 -1
- data/lib/sidekiq/throttled/version.rb +1 -1
- data/lib/sidekiq/throttled/web/stats.rb +2 -6
- data/lib/sidekiq/throttled/web/summary_fix.rb +3 -2
- data/lib/sidekiq/throttled/web/throttled.html.erb +6 -2
- data/lib/sidekiq/throttled/web.rb +1 -1
- data/lib/sidekiq/throttled/worker.rb +2 -2
- data/lib/sidekiq/throttled.rb +14 -2
- data/sidekiq-throttled.gemspec +2 -0
- metadata +18 -8
@@ -5,8 +5,9 @@ source "https://rubygems.org"
|
|
5
5
|
gem "appraisal"
|
6
6
|
gem "rake"
|
7
7
|
gem "rspec"
|
8
|
-
gem "rubocop", "~> 0.
|
9
|
-
gem "rubocop-
|
8
|
+
gem "rubocop", "~> 0.82.0", require: false
|
9
|
+
gem "rubocop-performance", "~>1.5.2", require: false
|
10
|
+
gem "rubocop-rspec", "~> 1.39.0", require: false
|
10
11
|
gem "sidekiq", "~> 5.0.0"
|
11
12
|
|
12
13
|
group :development do
|
@@ -17,13 +18,13 @@ group :development do
|
|
17
18
|
end
|
18
19
|
|
19
20
|
group :test do
|
21
|
+
gem "apparition"
|
20
22
|
gem "capybara"
|
21
23
|
gem "coveralls", require: false
|
22
|
-
gem "poltergeist"
|
23
24
|
gem "puma"
|
24
25
|
gem "rack-test"
|
25
|
-
gem "simplecov"
|
26
|
-
gem "sinatra"
|
26
|
+
gem "simplecov"
|
27
|
+
gem "sinatra"
|
27
28
|
gem "timecop"
|
28
29
|
end
|
29
30
|
|
@@ -5,8 +5,9 @@ source "https://rubygems.org"
|
|
5
5
|
gem "appraisal"
|
6
6
|
gem "rake"
|
7
7
|
gem "rspec"
|
8
|
-
gem "rubocop", "~> 0.
|
9
|
-
gem "rubocop-
|
8
|
+
gem "rubocop", "~> 0.82.0", require: false
|
9
|
+
gem "rubocop-performance", "~>1.5.2", require: false
|
10
|
+
gem "rubocop-rspec", "~> 1.39.0", require: false
|
10
11
|
gem "sidekiq", "~> 5.1.0"
|
11
12
|
|
12
13
|
group :development do
|
@@ -17,13 +18,13 @@ group :development do
|
|
17
18
|
end
|
18
19
|
|
19
20
|
group :test do
|
21
|
+
gem "apparition"
|
20
22
|
gem "capybara"
|
21
23
|
gem "coveralls", require: false
|
22
|
-
gem "poltergeist"
|
23
24
|
gem "puma"
|
24
25
|
gem "rack-test"
|
25
|
-
gem "simplecov"
|
26
|
-
gem "sinatra"
|
26
|
+
gem "simplecov"
|
27
|
+
gem "sinatra"
|
27
28
|
gem "timecop"
|
28
29
|
end
|
29
30
|
|
@@ -5,8 +5,9 @@ source "https://rubygems.org"
|
|
5
5
|
gem "appraisal"
|
6
6
|
gem "rake"
|
7
7
|
gem "rspec"
|
8
|
-
gem "rubocop", "~> 0.
|
9
|
-
gem "rubocop-
|
8
|
+
gem "rubocop", "~> 0.82.0", require: false
|
9
|
+
gem "rubocop-performance", "~>1.5.2", require: false
|
10
|
+
gem "rubocop-rspec", "~> 1.39.0", require: false
|
10
11
|
gem "sidekiq", "~> 5.2.0"
|
11
12
|
|
12
13
|
group :development do
|
@@ -17,13 +18,13 @@ group :development do
|
|
17
18
|
end
|
18
19
|
|
19
20
|
group :test do
|
21
|
+
gem "apparition"
|
20
22
|
gem "capybara"
|
21
23
|
gem "coveralls", require: false
|
22
|
-
gem "poltergeist"
|
23
24
|
gem "puma"
|
24
25
|
gem "rack-test"
|
25
|
-
gem "simplecov"
|
26
|
-
gem "sinatra"
|
26
|
+
gem "simplecov"
|
27
|
+
gem "sinatra"
|
27
28
|
gem "timecop"
|
28
29
|
end
|
29
30
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "appraisal"
|
6
|
+
gem "rake"
|
7
|
+
gem "rspec"
|
8
|
+
gem "rubocop", "~> 0.82.0", require: false
|
9
|
+
gem "rubocop-performance", "~>1.5.2", require: false
|
10
|
+
gem "rubocop-rspec", "~> 1.39.0", require: false
|
11
|
+
gem "sidekiq", "~> 6.0.0"
|
12
|
+
|
13
|
+
group :development do
|
14
|
+
gem "byebug"
|
15
|
+
gem "guard", require: false
|
16
|
+
gem "guard-rspec", require: false
|
17
|
+
gem "guard-rubocop", require: false
|
18
|
+
end
|
19
|
+
|
20
|
+
group :test do
|
21
|
+
gem "apparition"
|
22
|
+
gem "capybara"
|
23
|
+
gem "coveralls", require: false
|
24
|
+
gem "puma"
|
25
|
+
gem "rack-test"
|
26
|
+
gem "simplecov"
|
27
|
+
gem "sinatra"
|
28
|
+
gem "timecop"
|
29
|
+
end
|
30
|
+
|
31
|
+
gemspec path: "../"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "appraisal"
|
6
|
+
gem "rake"
|
7
|
+
gem "rspec"
|
8
|
+
gem "rubocop", "~> 0.82.0", require: false
|
9
|
+
gem "rubocop-performance", "~>1.5.2", require: false
|
10
|
+
gem "rubocop-rspec", "~> 1.39.0", require: false
|
11
|
+
gem "sidekiq", "~> 6.1.0"
|
12
|
+
|
13
|
+
group :development do
|
14
|
+
gem "byebug"
|
15
|
+
gem "guard", require: false
|
16
|
+
gem "guard-rspec", require: false
|
17
|
+
gem "guard-rubocop", require: false
|
18
|
+
end
|
19
|
+
|
20
|
+
group :test do
|
21
|
+
gem "apparition"
|
22
|
+
gem "capybara"
|
23
|
+
gem "coveralls", require: false
|
24
|
+
gem "puma"
|
25
|
+
gem "rack-test"
|
26
|
+
gem "simplecov"
|
27
|
+
gem "sinatra"
|
28
|
+
gem "timecop"
|
29
|
+
end
|
30
|
+
|
31
|
+
gemspec path: "../"
|
@@ -43,6 +43,7 @@ module Sidekiq
|
|
43
43
|
# @return [self]
|
44
44
|
def on(event, &handler)
|
45
45
|
raise ArgumentError, "No block given" unless handler
|
46
|
+
|
46
47
|
@mutex.synchronize { @handlers[event.to_s] << handler }
|
47
48
|
self
|
48
49
|
end
|
@@ -52,19 +53,19 @@ module Sidekiq
|
|
52
53
|
# @param [#to_s] event
|
53
54
|
# @param [Object] payload
|
54
55
|
# @return [void]
|
55
|
-
def run(event, payload = nil)
|
56
|
+
def run(event, payload = nil) # rubocop:disable Metrics/MethodLength
|
56
57
|
@mutex.synchronize do
|
57
|
-
Fiber.new do
|
58
|
+
fiber = Fiber.new do
|
58
59
|
@handlers[event.to_s].each do |callback|
|
59
60
|
begin
|
60
61
|
callback.call(payload)
|
61
62
|
rescue => e
|
62
|
-
handle_exception(e,
|
63
|
-
:context => "sidekiq:throttled"
|
64
|
-
})
|
63
|
+
handle_exception(e, :context => "sidekiq:throttled")
|
65
64
|
end
|
66
65
|
end
|
67
|
-
end
|
66
|
+
end
|
67
|
+
|
68
|
+
fiber.resume
|
68
69
|
end
|
69
70
|
end
|
70
71
|
end
|
@@ -62,7 +62,7 @@ module Sidekiq
|
|
62
62
|
# - `Exception` is recorded to the log and re-raised.
|
63
63
|
#
|
64
64
|
# @return [void]
|
65
|
-
def listen
|
65
|
+
def listen # rubocop:disable Metrics/MethodLength
|
66
66
|
subscribe
|
67
67
|
rescue Sidekiq::Shutdown
|
68
68
|
@terminated = true
|
@@ -88,7 +88,7 @@ module Sidekiq
|
|
88
88
|
# @see http://redis.io/commands/subscribe
|
89
89
|
# @see Callbacks#run
|
90
90
|
# @return [void]
|
91
|
-
def subscribe
|
91
|
+
def subscribe # rubocop:disable Metrics/MethodLength
|
92
92
|
Sidekiq.redis do |conn|
|
93
93
|
conn.subscribe @channel do |on|
|
94
94
|
on.subscribe do
|
@@ -97,7 +97,7 @@ module Sidekiq
|
|
97
97
|
end
|
98
98
|
|
99
99
|
on.message do |_channel, data|
|
100
|
-
message, payload = Marshal.load(data)
|
100
|
+
message, payload = Marshal.load(data) # rubocop:disable Security/MarshalLoad:
|
101
101
|
@callbacks.run("message:#{message}", payload)
|
102
102
|
end
|
103
103
|
end
|
@@ -11,7 +11,7 @@ module Sidekiq
|
|
11
11
|
#
|
12
12
|
# ## Implementation
|
13
13
|
#
|
14
|
-
# Internally list holds an array of arrays. Thus
|
14
|
+
# Internally list holds an array of arrays. Thus each element is a tuple of
|
15
15
|
# monotonic timestamp (when element was added) and element itself:
|
16
16
|
#
|
17
17
|
# [
|
@@ -12,16 +12,47 @@ module Sidekiq
|
|
12
12
|
#
|
13
13
|
# @private
|
14
14
|
class Fetch
|
15
|
+
module BulkRequeue
|
16
|
+
# Requeues all given units as a single operation.
|
17
|
+
#
|
18
|
+
# @see http://www.rubydoc.info/github/redis/redis-rb/master/Redis#pipelined-instance_method
|
19
|
+
# @param [Array<Fetch::UnitOfWork>] units
|
20
|
+
# @return [void]
|
21
|
+
def bulk_requeue(units, _options)
|
22
|
+
return if units.empty?
|
23
|
+
|
24
|
+
Sidekiq.logger.debug { "Re-queueing terminated jobs" }
|
25
|
+
Sidekiq.redis { |conn| conn.pipelined { units.each(&:requeue) } }
|
26
|
+
Sidekiq.logger.info("Pushed #{units.size} jobs back to Redis")
|
27
|
+
rescue => e
|
28
|
+
Sidekiq.logger.warn("Failed to requeue #{units.size} jobs: #{e}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# https://github.com/mperham/sidekiq/commit/fce05c9d4b4c0411c982078a4cf3a63f20f739bc
|
33
|
+
if Gem::Version.new(Sidekiq::VERSION) < Gem::Version.new("6.1.0")
|
34
|
+
extend BulkRequeue
|
35
|
+
else
|
36
|
+
include BulkRequeue
|
37
|
+
end
|
15
38
|
# Timeout to sleep between fetch retries in case of no job received,
|
16
39
|
# as well as timeout to wait for redis to give us something to work.
|
17
40
|
TIMEOUT = 2
|
18
41
|
|
19
42
|
# Initializes fetcher instance.
|
43
|
+
# @param options [Hash]
|
44
|
+
# @option options [Integer] :throttled_queue_cooldown (TIMEOUT)
|
45
|
+
# Min delay in seconds before queue will be polled again after
|
46
|
+
# throttled job.
|
47
|
+
# @option options [Boolean] :strict (false)
|
48
|
+
# @option options [Array<#to_s>] :queue
|
20
49
|
def initialize(options)
|
21
|
-
@paused = ExpirableList.new(TIMEOUT)
|
50
|
+
@paused = ExpirableList.new(options.fetch(:throttled_queue_cooldown, TIMEOUT))
|
22
51
|
|
23
|
-
@strict = options
|
24
|
-
@queues = options
|
52
|
+
@strict = options.fetch(:strict, false)
|
53
|
+
@queues = options.fetch(:queues).map { |q| QueueName.expand q }
|
54
|
+
|
55
|
+
raise ArgumentError, "empty :queues" if @queues.empty?
|
25
56
|
|
26
57
|
@queues.uniq! if @strict
|
27
58
|
end
|
@@ -42,23 +73,6 @@ module Sidekiq
|
|
42
73
|
nil
|
43
74
|
end
|
44
75
|
|
45
|
-
class << self
|
46
|
-
# Requeues all given units as a single operation.
|
47
|
-
#
|
48
|
-
# @see http://www.rubydoc.info/github/redis/redis-rb/master/Redis#pipelined-instance_method
|
49
|
-
# @param [Array<Fetch::UnitOfWork>] units
|
50
|
-
# @return [void]
|
51
|
-
def bulk_requeue(units, _options)
|
52
|
-
return if units.empty?
|
53
|
-
|
54
|
-
Sidekiq.logger.debug { "Re-queueing terminated jobs" }
|
55
|
-
Sidekiq.redis { |conn| conn.pipelined { units.each(&:requeue) } }
|
56
|
-
Sidekiq.logger.info("Pushed #{units.size} jobs back to Redis")
|
57
|
-
rescue => e
|
58
|
-
Sidekiq.logger.warn("Failed to requeue #{units.size} jobs: #{e}")
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
76
|
private
|
63
77
|
|
64
78
|
# Tries to pop pair of `queue` and job `message` out of sidekiq queues.
|
@@ -18,20 +18,16 @@ module Sidekiq
|
|
18
18
|
|
19
19
|
# Adds strategy to the registry.
|
20
20
|
#
|
21
|
-
# @note prints a warning to STDERR upon duplicate strategy name
|
22
21
|
# @param (see Strategy#initialize)
|
23
22
|
# @return [Strategy]
|
24
23
|
def add(name, **kwargs)
|
25
24
|
name = name.to_s
|
26
25
|
|
27
|
-
warn "Duplicate strategy name: #{name}" if @strategies[name]
|
28
|
-
|
29
26
|
@strategies[name] = Strategy.new(name, **kwargs)
|
30
27
|
end
|
31
28
|
|
32
29
|
# Adds alias for existing strategy.
|
33
30
|
#
|
34
|
-
# @note prints a warning to STDERR upon duplicate strategy name
|
35
31
|
# @param (#to_s) new_name
|
36
32
|
# @param (#to_s) old_name
|
37
33
|
# @raise [RuntimeError] if no strategy found with `old_name`
|
@@ -40,7 +36,6 @@ module Sidekiq
|
|
40
36
|
new_name = new_name.to_s
|
41
37
|
old_name = old_name.to_s
|
42
38
|
|
43
|
-
warn "Duplicate strategy name: #{new_name}" if @strategies[new_name]
|
44
39
|
raise "Strategy not found: #{old_name}" unless @strategies[old_name]
|
45
40
|
|
46
41
|
@aliases[new_name] = @strategies[old_name]
|
@@ -74,6 +69,7 @@ module Sidekiq
|
|
74
69
|
# @return [Registry]
|
75
70
|
def each
|
76
71
|
return to_enum(__method__) unless block_given?
|
72
|
+
|
77
73
|
@strategies.each { |*args| yield(*args) }
|
78
74
|
self
|
79
75
|
end
|
@@ -88,6 +84,7 @@ module Sidekiq
|
|
88
84
|
# @return [Registry]
|
89
85
|
def each_with_static_keys
|
90
86
|
return to_enum(__method__) unless block_given?
|
87
|
+
|
91
88
|
@strategies.each do |name, strategy|
|
92
89
|
yield(name, strategy) unless strategy.dynamic?
|
93
90
|
end
|
@@ -46,12 +46,12 @@ module Sidekiq
|
|
46
46
|
return false unless job_limit
|
47
47
|
return true if job_limit <= 0
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
:argv => [jid.to_s, job_limit, @ttl, Time.now.to_f]
|
52
|
-
}
|
49
|
+
keys = [key(job_args)]
|
50
|
+
argv = [jid.to_s, job_limit, @ttl, Time.now.to_f]
|
53
51
|
|
54
|
-
Sidekiq.redis
|
52
|
+
Sidekiq.redis do |redis|
|
53
|
+
1 == SCRIPT.eval(redis, :keys => keys, :argv => argv)
|
54
|
+
end
|
55
55
|
end
|
56
56
|
|
57
57
|
# @return [Integer] Current count of jobs
|
@@ -48,6 +48,7 @@ module Sidekiq
|
|
48
48
|
# @return [Float] Period in seconds
|
49
49
|
def period(job_args = nil)
|
50
50
|
return @period.to_f unless @period.respond_to? :call
|
51
|
+
|
51
52
|
@period.call(*job_args).to_f
|
52
53
|
end
|
53
54
|
|
@@ -62,12 +63,12 @@ module Sidekiq
|
|
62
63
|
return false unless job_limit
|
63
64
|
return true if job_limit <= 0
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
:argv => [job_limit, period(job_args), Time.now.to_f]
|
68
|
-
}
|
66
|
+
keys = [key(job_args)]
|
67
|
+
argv = [job_limit, period(job_args), Time.now.to_f]
|
69
68
|
|
70
|
-
Sidekiq.redis
|
69
|
+
Sidekiq.redis do |redis|
|
70
|
+
1 == SCRIPT.eval(redis, :keys => keys, :argv => argv)
|
71
|
+
end
|
71
72
|
end
|
72
73
|
|
73
74
|
# @return [Integer] Current count of jobs
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# internal
|
4
4
|
require "sidekiq/throttled/errors"
|
5
|
+
require "sidekiq/throttled/strategy_collection"
|
5
6
|
require "sidekiq/throttled/strategy/concurrency"
|
6
7
|
require "sidekiq/throttled/strategy/threshold"
|
7
8
|
|
@@ -19,28 +20,31 @@ module Sidekiq
|
|
19
20
|
# @return [Strategy::Threshold, nil]
|
20
21
|
attr_reader :threshold
|
21
22
|
|
23
|
+
# @!attribute [r] observer
|
24
|
+
# @return [Proc, nil]
|
25
|
+
attr_reader :observer
|
26
|
+
|
22
27
|
# @param [#to_s] name
|
23
28
|
# @param [Hash] concurrency Concurrency options.
|
24
29
|
# See keyword args of {Strategy::Concurrency#initialize} for details.
|
25
30
|
# @param [Hash] threshold Threshold options.
|
26
31
|
# See keyword args of {Strategy::Threshold#initialize} for details.
|
27
32
|
# @param [#call] key_suffix Dynamic key suffix generator.
|
28
|
-
|
29
|
-
|
33
|
+
# @param [#call] observer Process called after throttled.
|
34
|
+
def initialize(name, concurrency: nil, threshold: nil, key_suffix: nil, observer: nil) # rubocop:disable Metrics/MethodLength
|
35
|
+
@observer = observer
|
30
36
|
|
31
|
-
@concurrency =
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
37
|
+
@concurrency = StrategyCollection.new(concurrency,
|
38
|
+
:strategy => Concurrency,
|
39
|
+
:name => name,
|
40
|
+
:key_suffix => key_suffix)
|
36
41
|
|
37
|
-
@threshold =
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
+
@threshold = StrategyCollection.new(threshold,
|
43
|
+
:strategy => Threshold,
|
44
|
+
:name => name,
|
45
|
+
:key_suffix => key_suffix)
|
42
46
|
|
43
|
-
return if @concurrency || @threshold
|
47
|
+
return if @concurrency.any? || @threshold.any?
|
44
48
|
|
45
49
|
raise ArgumentError, "Neither :concurrency nor :threshold given"
|
46
50
|
end
|
@@ -55,9 +59,14 @@ module Sidekiq
|
|
55
59
|
|
56
60
|
# @return [Boolean] whenever job is throttled or not.
|
57
61
|
def throttled?(jid, *job_args)
|
58
|
-
|
62
|
+
if @concurrency&.throttled?(jid, *job_args)
|
63
|
+
@observer&.call(:concurrency, *job_args)
|
64
|
+
return true
|
65
|
+
end
|
59
66
|
|
60
67
|
if @threshold&.throttled?(*job_args)
|
68
|
+
@observer&.call(:threshold, *job_args)
|
69
|
+
|
61
70
|
finalize!(jid, *job_args)
|
62
71
|
return true
|
63
72
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# internal
|
4
|
+
module Sidekiq
|
5
|
+
module Throttled
|
6
|
+
# Collection which transparently group several meta-strategies of one kind
|
7
|
+
#
|
8
|
+
# @private
|
9
|
+
class StrategyCollection
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
attr_reader :strategies
|
13
|
+
|
14
|
+
# @param [Hash, Array, nil] strategies Concurrency or Threshold options
|
15
|
+
# or array of options.
|
16
|
+
# See keyword args of {Strategy::Concurrency#initialize} for details.
|
17
|
+
# See keyword args of {Strategy::Threshold#initialize} for details.
|
18
|
+
# @param [Class] strategy class of strategy: Concurrency or Threshold
|
19
|
+
# @param [#to_s] name
|
20
|
+
# @param [#call] key_suffix Dynamic key suffix generator.
|
21
|
+
def initialize(strategies, strategy:, name:, key_suffix:)
|
22
|
+
strategies = (strategies.is_a?(Hash) ? [strategies] : Array(strategies))
|
23
|
+
@strategies = strategies.map do |options|
|
24
|
+
make_strategy(strategy, name, key_suffix, options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param [#call] block
|
29
|
+
# Iterates each strategy in collection
|
30
|
+
def each(&block)
|
31
|
+
@strategies.each(&block)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Boolean] whenever any strategy in collection has dynamic config
|
35
|
+
def dynamic?
|
36
|
+
any?(&:dynamic?)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Boolean] whenever job is throttled or not
|
40
|
+
# by any strategy in collection.
|
41
|
+
def throttled?(*args)
|
42
|
+
any? { |s| s.throttled?(*args) }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Marks job as being processed.
|
46
|
+
# @return [void]
|
47
|
+
def finalize!(*args)
|
48
|
+
each { |c| c.finalize!(*args) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Resets count of jobs of all avaliable strategies
|
52
|
+
# @return [void]
|
53
|
+
def reset!
|
54
|
+
each(&:reset!)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# @return [Base, nil]
|
60
|
+
def make_strategy(strategy, name, key_suffix, options)
|
61
|
+
return unless options
|
62
|
+
|
63
|
+
strategy.new("throttled:#{name}", {
|
64
|
+
:key_suffix => key_suffix,
|
65
|
+
**options
|
66
|
+
})
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -9,7 +9,7 @@ module Sidekiq
|
|
9
9
|
# @param name [#to_s] Constant name
|
10
10
|
# @return [Object, nil] Resolved constant or nil if failed.
|
11
11
|
def constantize(name)
|
12
|
-
name.to_s.sub(
|
12
|
+
name.to_s.sub(%r{^::}, "").split("::").inject(Object, &:const_get)
|
13
13
|
rescue NameError
|
14
14
|
Sidekiq.logger.warn { "Failed to constantize: #{name}" }
|
15
15
|
nil
|
@@ -14,9 +14,7 @@ module Sidekiq
|
|
14
14
|
|
15
15
|
# @param [Strategy::Concurrency, Strategy::Threshold] strategy
|
16
16
|
def initialize(strategy)
|
17
|
-
if strategy&.dynamic?
|
18
|
-
raise ArgumentError, "Can't handle dynamic strategies"
|
19
|
-
end
|
17
|
+
raise ArgumentError, "Can't handle dynamic strategies" if strategy&.dynamic?
|
20
18
|
|
21
19
|
@strategy = strategy
|
22
20
|
end
|
@@ -27,9 +25,7 @@ module Sidekiq
|
|
27
25
|
|
28
26
|
html = humanize_integer(@strategy.limit) << " jobs"
|
29
27
|
|
30
|
-
if @strategy.respond_to?
|
31
|
-
html << " per " << humanize_duration(@strategy.period)
|
32
|
-
end
|
28
|
+
html << " per " << humanize_duration(@strategy.period) if @strategy.respond_to?(:period)
|
33
29
|
|
34
30
|
html << "<br />" << colorize_count(@strategy.count, @strategy.limit)
|
35
31
|
end
|
@@ -4,14 +4,15 @@ module Sidekiq
|
|
4
4
|
module Throttled
|
5
5
|
module Web
|
6
6
|
module SummaryFix
|
7
|
-
JAVASCRIPT = [File.read(
|
7
|
+
JAVASCRIPT = [File.read(File.expand_path("summary_fix.js", __dir__)).freeze].freeze
|
8
8
|
HEADERS = { "Content-Type" => "application/javascript" }.freeze
|
9
9
|
|
10
10
|
class << self
|
11
11
|
attr_accessor :enabled
|
12
12
|
|
13
13
|
def apply!(app)
|
14
|
-
Sidekiq::WebAction.
|
14
|
+
Sidekiq::WebAction.prepend SummaryFix
|
15
|
+
|
15
16
|
app.get("/throttled/summary_fix") do
|
16
17
|
[200, HEADERS.dup, JAVASCRIPT.dup]
|
17
18
|
end
|
@@ -14,10 +14,14 @@
|
|
14
14
|
<tr>
|
15
15
|
<td style="vertical-align:middle;"><%= name %></td>
|
16
16
|
<td style="vertical-align:middle;text-align:center;">
|
17
|
-
|
17
|
+
<% strategy.concurrency.each do |concurrency| %>
|
18
|
+
<%= Sidekiq::Throttled::Web::Stats.new(concurrency).to_html %>
|
19
|
+
<% end %>
|
18
20
|
</td>
|
19
21
|
<td style="vertical-align:middle;text-align:center;">
|
20
|
-
|
22
|
+
<% strategy.threshold.each do |threshold| %>
|
23
|
+
<%= Sidekiq::Throttled::Web::Stats.new(threshold).to_html %>
|
24
|
+
<% end %>
|
21
25
|
</td>
|
22
26
|
<td style="vertical-align:middle;text-align:center;">
|
23
27
|
<form action="<%= root_path %>throttled/<%= CGI.escape name %>/reset" method="post">
|
@@ -77,7 +77,7 @@ module Sidekiq
|
|
77
77
|
Registry.add(self, **kwargs)
|
78
78
|
end
|
79
79
|
|
80
|
-
# Adds current worker to preconfigured
|
80
|
+
# Adds current worker to preconfigured throttling strategy. Allows
|
81
81
|
# sharing same pool for multiple workers.
|
82
82
|
#
|
83
83
|
# First of all we need to create shared throttling strategy:
|
@@ -85,7 +85,7 @@ module Sidekiq
|
|
85
85
|
# # Create google_api throttling strategy
|
86
86
|
# Sidekiq::Throttled::Registry.add(:google_api, {
|
87
87
|
# :threshold => { :limit => 123, :period => 1.hour },
|
88
|
-
# :concurrency => { :limit =>
|
88
|
+
# :concurrency => { :limit => 10 }
|
89
89
|
# })
|
90
90
|
#
|
91
91
|
# Now we can assign it to our workers:
|