sidekiq-throttled 0.13.0 → 0.16.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 +4 -4
- data/.github/workflows/ci.yml +12 -16
- data/.rubocop.yml +14 -94
- data/.rubocop_todo.yml +42 -31
- data/Appraisals +14 -10
- data/CHANGES.md +65 -0
- data/Gemfile +7 -5
- data/LICENSE.md +2 -0
- data/README.md +42 -26
- data/Rakefile +1 -1
- data/gemfiles/sidekiq_6.0.gemfile +7 -5
- data/gemfiles/sidekiq_6.1.gemfile +7 -5
- data/gemfiles/{sidekiq_5.0.gemfile → sidekiq_6.2.gemfile} +8 -6
- data/gemfiles/{sidekiq_5.1.gemfile → sidekiq_6.3.gemfile} +8 -6
- data/gemfiles/{sidekiq_5.2.gemfile → sidekiq_6.4.gemfile} +8 -6
- data/gemfiles/sidekiq_6.5.gemfile +33 -0
- data/lib/sidekiq/throttled/communicator/callbacks.rb +8 -10
- data/lib/sidekiq/throttled/communicator/exception_handler.rb +25 -0
- data/lib/sidekiq/throttled/communicator/listener.rb +4 -4
- data/lib/sidekiq/throttled/communicator.rb +1 -1
- data/lib/sidekiq/throttled/expirable_list.rb +2 -5
- data/lib/sidekiq/throttled/fetch/unit_of_work.rb +7 -2
- data/lib/sidekiq/throttled/fetch.rb +5 -1
- data/lib/sidekiq/throttled/job.rb +128 -0
- data/lib/sidekiq/throttled/queue_name.rb +1 -1
- data/lib/sidekiq/throttled/registry.rb +0 -5
- data/lib/sidekiq/throttled/strategy.rb +15 -17
- data/lib/sidekiq/throttled/strategy_collection.rb +69 -0
- data/lib/sidekiq/throttled/utils.rb +1 -1
- data/lib/sidekiq/throttled/version.rb +1 -1
- data/lib/sidekiq/throttled/web/stats.rb +5 -4
- data/lib/sidekiq/throttled/web/summary_fix.rb +1 -1
- data/lib/sidekiq/throttled/web/throttled.html.erb +6 -2
- data/lib/sidekiq/throttled/web.rb +1 -1
- data/lib/sidekiq/throttled/worker.rb +6 -121
- data/lib/sidekiq/throttled.rb +5 -3
- 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/sidekiq-throttled.gemspec +8 -4
- metadata +27 -16
@@ -5,10 +5,7 @@ source "https://rubygems.org"
|
|
5
5
|
gem "appraisal"
|
6
6
|
gem "rake"
|
7
7
|
gem "rspec"
|
8
|
-
gem "
|
9
|
-
gem "rubocop-performance", "~>1.5.2", require: false
|
10
|
-
gem "rubocop-rspec", "~> 1.39.0", require: false
|
11
|
-
gem "sidekiq", "~> 5.1.0"
|
8
|
+
gem "sidekiq", "~> 6.3.0"
|
12
9
|
|
13
10
|
group :development do
|
14
11
|
gem "byebug"
|
@@ -20,12 +17,17 @@ end
|
|
20
17
|
group :test do
|
21
18
|
gem "apparition"
|
22
19
|
gem "capybara"
|
23
|
-
gem "coveralls", require: false
|
24
20
|
gem "puma"
|
25
21
|
gem "rack-test"
|
26
|
-
gem "simplecov"
|
27
22
|
gem "sinatra"
|
28
23
|
gem "timecop"
|
29
24
|
end
|
30
25
|
|
26
|
+
group :lint do
|
27
|
+
gem "rubocop", require: false
|
28
|
+
gem "rubocop-performance", require: false
|
29
|
+
gem "rubocop-rake", require: false
|
30
|
+
gem "rubocop-rspec", require: false
|
31
|
+
end
|
32
|
+
|
31
33
|
gemspec path: "../"
|
@@ -5,10 +5,7 @@ source "https://rubygems.org"
|
|
5
5
|
gem "appraisal"
|
6
6
|
gem "rake"
|
7
7
|
gem "rspec"
|
8
|
-
gem "
|
9
|
-
gem "rubocop-performance", "~>1.5.2", require: false
|
10
|
-
gem "rubocop-rspec", "~> 1.39.0", require: false
|
11
|
-
gem "sidekiq", "~> 5.2.0"
|
8
|
+
gem "sidekiq", "~> 6.4.0"
|
12
9
|
|
13
10
|
group :development do
|
14
11
|
gem "byebug"
|
@@ -20,12 +17,17 @@ end
|
|
20
17
|
group :test do
|
21
18
|
gem "apparition"
|
22
19
|
gem "capybara"
|
23
|
-
gem "coveralls", require: false
|
24
20
|
gem "puma"
|
25
21
|
gem "rack-test"
|
26
|
-
gem "simplecov"
|
27
22
|
gem "sinatra"
|
28
23
|
gem "timecop"
|
29
24
|
end
|
30
25
|
|
26
|
+
group :lint do
|
27
|
+
gem "rubocop", require: false
|
28
|
+
gem "rubocop-performance", require: false
|
29
|
+
gem "rubocop-rake", require: false
|
30
|
+
gem "rubocop-rspec", require: false
|
31
|
+
end
|
32
|
+
|
31
33
|
gemspec path: "../"
|
@@ -0,0 +1,33 @@
|
|
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 "sidekiq", "~> 6.5.0"
|
9
|
+
|
10
|
+
group :development do
|
11
|
+
gem "byebug"
|
12
|
+
gem "guard", require: false
|
13
|
+
gem "guard-rspec", require: false
|
14
|
+
gem "guard-rubocop", require: false
|
15
|
+
end
|
16
|
+
|
17
|
+
group :test do
|
18
|
+
gem "apparition"
|
19
|
+
gem "capybara"
|
20
|
+
gem "puma"
|
21
|
+
gem "rack-test"
|
22
|
+
gem "sinatra"
|
23
|
+
gem "timecop"
|
24
|
+
end
|
25
|
+
|
26
|
+
group :lint do
|
27
|
+
gem "rubocop", require: false
|
28
|
+
gem "rubocop-performance", require: false
|
29
|
+
gem "rubocop-rake", require: false
|
30
|
+
gem "rubocop-rspec", require: false
|
31
|
+
end
|
32
|
+
|
33
|
+
gemspec path: "../"
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "fiber"
|
4
4
|
|
5
|
-
require "sidekiq/exception_handler"
|
5
|
+
require "sidekiq/throttled/communicator/exception_handler"
|
6
6
|
|
7
7
|
module Sidekiq
|
8
8
|
module Throttled
|
@@ -55,17 +55,15 @@ module Sidekiq
|
|
55
55
|
# @return [void]
|
56
56
|
def run(event, payload = nil)
|
57
57
|
@mutex.synchronize do
|
58
|
-
Fiber.new do
|
58
|
+
fiber = Fiber.new do
|
59
59
|
@handlers[event.to_s].each do |callback|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
handle_exception(e, {
|
64
|
-
:context => "sidekiq:throttled"
|
65
|
-
})
|
66
|
-
end
|
60
|
+
callback.call(payload)
|
61
|
+
rescue => e
|
62
|
+
handle_exception(e, :context => "sidekiq:throttled")
|
67
63
|
end
|
68
|
-
end
|
64
|
+
end
|
65
|
+
|
66
|
+
fiber.resume
|
69
67
|
end
|
70
68
|
end
|
71
69
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sidekiq"
|
4
|
+
require "sidekiq/version"
|
5
|
+
|
6
|
+
module Sidekiq
|
7
|
+
module Throttled
|
8
|
+
class Communicator
|
9
|
+
if Sidekiq::VERSION >= "6.5.0"
|
10
|
+
module ExceptionHandler
|
11
|
+
def handle_exception(*args)
|
12
|
+
Sidekiq.handle_exception(*args)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# NOTE: `Sidekiq.default_error_handler` is private API
|
17
|
+
Sidekiq.error_handlers << Sidekiq.method(:default_error_handler)
|
18
|
+
else
|
19
|
+
require "sidekiq/exception_handler"
|
20
|
+
|
21
|
+
ExceptionHandler = ::Sidekiq::ExceptionHandler
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "sidekiq/exception_handler"
|
3
|
+
require "sidekiq/throttled/communicator/exception_handler"
|
4
4
|
|
5
5
|
module Sidekiq
|
6
6
|
module Throttled
|
@@ -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
|
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
require "monitor"
|
4
4
|
|
5
|
-
require "concurrent/utility/monotonic_time"
|
6
|
-
|
7
5
|
module Sidekiq
|
8
6
|
module Throttled
|
9
7
|
# List that tracks when elements were added and enumerates over those not
|
@@ -24,7 +22,6 @@ module Sidekiq
|
|
24
22
|
# It does not deduplicates elements. Eviction happens only upon elements
|
25
23
|
# retrieval (see {#each}).
|
26
24
|
#
|
27
|
-
# @see http://ruby-concurrency.github.io/concurrent-ruby/Concurrent.html#monotonic_time-class_method
|
28
25
|
# @see https://ruby-doc.org/core/Process.html#method-c-clock_gettime
|
29
26
|
# @see https://linux.die.net/man/3/clock_gettime
|
30
27
|
#
|
@@ -44,7 +41,7 @@ module Sidekiq
|
|
44
41
|
# @params element [Object]
|
45
42
|
# @return [ExpirableList] self
|
46
43
|
def <<(element)
|
47
|
-
@mon.synchronize { @arr << [
|
44
|
+
@mon.synchronize { @arr << [::Process.clock_gettime(::Process::CLOCK_MONOTONIC), element] }
|
48
45
|
self
|
49
46
|
end
|
50
47
|
|
@@ -58,7 +55,7 @@ module Sidekiq
|
|
58
55
|
return to_enum __method__ unless block_given?
|
59
56
|
|
60
57
|
@mon.synchronize do
|
61
|
-
horizon =
|
58
|
+
horizon = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @ttl
|
62
59
|
|
63
60
|
# drop all elements older than horizon
|
64
61
|
@arr.shift while @arr[0] && @arr[0][0] < horizon
|
@@ -49,9 +49,14 @@ module Sidekiq
|
|
49
49
|
# process was terminated. It is a reverse of whatever fetcher was
|
50
50
|
# doing to pull the job out of queue.
|
51
51
|
#
|
52
|
+
# @param [Redis] pipelined connection for requeing via Redis#pipelined
|
52
53
|
# @return [void]
|
53
|
-
def requeue
|
54
|
-
|
54
|
+
def requeue(pipeline = nil)
|
55
|
+
if pipeline
|
56
|
+
pipeline.rpush(QueueName.expand(queue_name), job)
|
57
|
+
else
|
58
|
+
Sidekiq.redis { |conn| conn.rpush(QueueName.expand(queue_name), job) }
|
59
|
+
end
|
55
60
|
end
|
56
61
|
|
57
62
|
# Pushes job back to the head of the queue, so that job won't be tried
|
@@ -22,7 +22,11 @@ module Sidekiq
|
|
22
22
|
return if units.empty?
|
23
23
|
|
24
24
|
Sidekiq.logger.debug { "Re-queueing terminated jobs" }
|
25
|
-
Sidekiq.redis
|
25
|
+
Sidekiq.redis do |conn|
|
26
|
+
conn.pipelined do |pipeline|
|
27
|
+
units.each { |unit| unit.requeue(pipeline) }
|
28
|
+
end
|
29
|
+
end
|
26
30
|
Sidekiq.logger.info("Pushed #{units.size} jobs back to Redis")
|
27
31
|
rescue => e
|
28
32
|
Sidekiq.logger.warn("Failed to requeue #{units.size} jobs: #{e}")
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# internal
|
4
|
+
require "sidekiq/throttled/registry"
|
5
|
+
|
6
|
+
module Sidekiq
|
7
|
+
module Throttled
|
8
|
+
# Adds helpers to your worker classes
|
9
|
+
#
|
10
|
+
# @example Usage
|
11
|
+
#
|
12
|
+
# class MyWorker
|
13
|
+
# include Sidekiq::Worker
|
14
|
+
# include Sidekiq::Throttled::Worker
|
15
|
+
#
|
16
|
+
# sidkiq_options :queue => :my_queue
|
17
|
+
# sidekiq_throttle :threshold => { :limit => 123, :period => 1.hour }
|
18
|
+
#
|
19
|
+
# def perform
|
20
|
+
# # ...
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @see ClassMethods
|
25
|
+
module Job
|
26
|
+
# Extends worker class with {ClassMethods}.
|
27
|
+
#
|
28
|
+
# @note Using `included` hook with extending worker with {ClassMethods}
|
29
|
+
# in order to make API inline with `include Sidekiq::Worker`.
|
30
|
+
#
|
31
|
+
# @private
|
32
|
+
def self.included(worker)
|
33
|
+
worker.send(:extend, ClassMethods)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Helper methods added to the singleton class of destination
|
37
|
+
module ClassMethods
|
38
|
+
# Registers some strategy for the worker.
|
39
|
+
#
|
40
|
+
# @example Allow max 123 MyWorker jobs per hour
|
41
|
+
#
|
42
|
+
# class MyWorker
|
43
|
+
# include Sidekiq::Worker
|
44
|
+
# include Sidekiq::Throttled::Worker
|
45
|
+
#
|
46
|
+
# sidekiq_throttle({
|
47
|
+
# :threshold => { :limit => 123, :period => 1.hour }
|
48
|
+
# })
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# @example Allow max 10 concurrently running MyWorker jobs
|
52
|
+
#
|
53
|
+
# class MyWorker
|
54
|
+
# include Sidekiq::Worker
|
55
|
+
# include Sidekiq::Throttled::Worker
|
56
|
+
#
|
57
|
+
# sidekiq_throttle({
|
58
|
+
# :concurrency => { :limit => 10 }
|
59
|
+
# })
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# @example Allow max 10 concurrent MyWorker jobs and max 123 per hour
|
63
|
+
#
|
64
|
+
# class MyWorker
|
65
|
+
# include Sidekiq::Worker
|
66
|
+
# include Sidekiq::Throttled::Worker
|
67
|
+
#
|
68
|
+
# sidekiq_throttle({
|
69
|
+
# :threshold => { :limit => 123, :period => 1.hour },
|
70
|
+
# :concurrency => { :limit => 10 }
|
71
|
+
# })
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# @see Registry.add
|
75
|
+
# @return [void]
|
76
|
+
def sidekiq_throttle(**kwargs)
|
77
|
+
Registry.add(self, **kwargs)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Adds current worker to preconfigured throttling strategy. Allows
|
81
|
+
# sharing same pool for multiple workers.
|
82
|
+
#
|
83
|
+
# First of all we need to create shared throttling strategy:
|
84
|
+
#
|
85
|
+
# # Create google_api throttling strategy
|
86
|
+
# Sidekiq::Throttled::Registry.add(:google_api, {
|
87
|
+
# :threshold => { :limit => 123, :period => 1.hour },
|
88
|
+
# :concurrency => { :limit => 10 }
|
89
|
+
# })
|
90
|
+
#
|
91
|
+
# Now we can assign it to our workers:
|
92
|
+
#
|
93
|
+
# class FetchProfileJob
|
94
|
+
# include Sidekiq::Worker
|
95
|
+
# include Sidekiq::Throttled::Worker
|
96
|
+
#
|
97
|
+
# sidekiq_throttle_as :google_api
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# class FetchCommentsJob
|
101
|
+
# include Sidekiq::Worker
|
102
|
+
# include Sidekiq::Throttled::Worker
|
103
|
+
#
|
104
|
+
# sidekiq_throttle_as :google_api
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# With the above configuration we ensure that there are maximum 10
|
108
|
+
# concurrently running jobs of FetchProfileJob or FetchCommentsJob
|
109
|
+
# allowed. And only 123 jobs of those are executed per hour.
|
110
|
+
#
|
111
|
+
# In other words, it will allow:
|
112
|
+
#
|
113
|
+
# - only `X` concurrent `FetchProfileJob`s
|
114
|
+
# - max `XX` `FetchProfileJob` per hour
|
115
|
+
# - only `Y` concurrent `FetchCommentsJob`s
|
116
|
+
# - max `YY` `FetchCommentsJob` per hour
|
117
|
+
#
|
118
|
+
# Where `(X + Y) == 10` and `(XX + YY) == 123`
|
119
|
+
#
|
120
|
+
# @see Registry.add_alias
|
121
|
+
# @return [void]
|
122
|
+
def sidekiq_throttle_as(name)
|
123
|
+
Registry.add_alias(self, name)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -7,7 +7,7 @@ module Sidekiq
|
|
7
7
|
# @private
|
8
8
|
module QueueName
|
9
9
|
# RegExp used to stip out any redisr-namespace prefixes with `queue:`.
|
10
|
-
QUEUE_NAME_PREFIX_RE =
|
10
|
+
QUEUE_NAME_PREFIX_RE = %r{.*queue:}.freeze
|
11
11
|
private_constant :QUEUE_NAME_PREFIX_RE
|
12
12
|
|
13
13
|
class << self
|
@@ -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]
|
@@ -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
|
|
@@ -30,15 +31,20 @@ module Sidekiq
|
|
30
31
|
# See keyword args of {Strategy::Threshold#initialize} for details.
|
31
32
|
# @param [#call] key_suffix Dynamic key suffix generator.
|
32
33
|
# @param [#call] observer Process called after throttled.
|
33
|
-
def initialize(
|
34
|
-
|
35
|
-
concurrency: nil, threshold: nil, key_suffix: nil, observer: nil
|
36
|
-
)
|
37
|
-
@observer = observer
|
38
|
-
@concurrency = make_strategy(Concurrency, name, key_suffix, concurrency)
|
39
|
-
@threshold = make_strategy(Threshold, name, key_suffix, threshold)
|
34
|
+
def initialize(name, concurrency: nil, threshold: nil, key_suffix: nil, observer: nil) # rubocop:disable Metrics/MethodLength
|
35
|
+
@observer = observer
|
40
36
|
|
41
|
-
|
37
|
+
@concurrency = StrategyCollection.new(concurrency,
|
38
|
+
:strategy => Concurrency,
|
39
|
+
:name => name,
|
40
|
+
:key_suffix => key_suffix)
|
41
|
+
|
42
|
+
@threshold = StrategyCollection.new(threshold,
|
43
|
+
:strategy => Threshold,
|
44
|
+
:name => name,
|
45
|
+
:key_suffix => key_suffix)
|
46
|
+
|
47
|
+
return if @concurrency.any? || @threshold.any?
|
42
48
|
|
43
49
|
raise ArgumentError, "Neither :concurrency nor :threshold given"
|
44
50
|
end
|
@@ -60,6 +66,7 @@ module Sidekiq
|
|
60
66
|
|
61
67
|
if @threshold&.throttled?(*job_args)
|
62
68
|
@observer&.call(:threshold, *job_args)
|
69
|
+
|
63
70
|
finalize!(jid, *job_args)
|
64
71
|
return true
|
65
72
|
end
|
@@ -79,15 +86,6 @@ module Sidekiq
|
|
79
86
|
@concurrency&.reset!
|
80
87
|
@threshold&.reset!
|
81
88
|
end
|
82
|
-
|
83
|
-
private
|
84
|
-
|
85
|
-
# @return [Base, nil]
|
86
|
-
def make_strategy(strategy, name, key_suffix, options)
|
87
|
-
return unless options
|
88
|
-
|
89
|
-
strategy.new("throttled:#{name}", :key_suffix => key_suffix, **options)
|
90
|
-
end
|
91
89
|
end
|
92
90
|
end
|
93
91
|
end
|
@@ -0,0 +1,69 @@
|
|
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
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
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
|
@@ -37,7 +37,8 @@ module Sidekiq
|
|
37
37
|
percentile = 100.00 * int / max
|
38
38
|
lvl = if 80 <= percentile then "danger"
|
39
39
|
elsif 60 <= percentile then "warning"
|
40
|
-
else
|
40
|
+
else
|
41
|
+
"success"
|
41
42
|
end
|
42
43
|
|
43
44
|
%(<span class="label label-#{lvl}">#{int}</span>)
|
@@ -61,10 +62,10 @@ module Sidekiq
|
|
61
62
|
|
62
63
|
# @return [String]
|
63
64
|
def humanize_integer(int)
|
64
|
-
digits = int.to_s.
|
65
|
-
str = digits.shift(digits.count % 3).join
|
65
|
+
digits = int.to_s.chars
|
66
|
+
str = digits.shift(digits.count % 3).join
|
66
67
|
|
67
|
-
str << " " << digits.shift(3).join
|
68
|
+
str << " " << digits.shift(3).join while digits.count.positive?
|
68
69
|
|
69
70
|
str.strip
|
70
71
|
end
|
@@ -4,7 +4,7 @@ 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
|
@@ -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">
|