sidekiq-throttled 0.17.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +314 -0
  3. data/lib/sidekiq/throttled/config.rb +44 -0
  4. data/lib/sidekiq/throttled/cooldown.rb +55 -0
  5. data/lib/sidekiq/throttled/expirable_set.rb +70 -0
  6. data/lib/sidekiq/throttled/job.rb +4 -4
  7. data/lib/sidekiq/throttled/middlewares/server.rb +28 -0
  8. data/lib/sidekiq/throttled/patches/basic_fetch.rb +53 -0
  9. data/lib/sidekiq/throttled/registry.rb +4 -7
  10. data/lib/sidekiq/throttled/strategy/concurrency.rb +4 -6
  11. data/lib/sidekiq/throttled/strategy/threshold.rb +4 -6
  12. data/lib/sidekiq/throttled/strategy.rb +10 -10
  13. data/lib/sidekiq/throttled/strategy_collection.rb +2 -3
  14. data/lib/sidekiq/throttled/version.rb +1 -1
  15. data/lib/sidekiq/throttled/web.rb +2 -45
  16. data/lib/sidekiq/throttled/worker.rb +1 -1
  17. data/lib/sidekiq/throttled.rb +45 -57
  18. metadata +25 -70
  19. data/.coveralls.yml +0 -1
  20. data/.github/dependabot.yml +0 -12
  21. data/.github/workflows/ci.yml +0 -52
  22. data/.gitignore +0 -12
  23. data/.rspec +0 -5
  24. data/.rubocop.yml +0 -20
  25. data/.rubocop_todo.yml +0 -68
  26. data/.travis.yml +0 -37
  27. data/.yardopts +0 -1
  28. data/Appraisals +0 -9
  29. data/CHANGES.md +0 -300
  30. data/Gemfile +0 -34
  31. data/Guardfile +0 -25
  32. data/README.md +0 -297
  33. data/Rakefile +0 -27
  34. data/gemfiles/sidekiq_6.4.gemfile +0 -33
  35. data/gemfiles/sidekiq_6.5.gemfile +0 -33
  36. data/lib/sidekiq/throttled/communicator/callbacks.rb +0 -72
  37. data/lib/sidekiq/throttled/communicator/exception_handler.rb +0 -25
  38. data/lib/sidekiq/throttled/communicator/listener.rb +0 -109
  39. data/lib/sidekiq/throttled/communicator.rb +0 -116
  40. data/lib/sidekiq/throttled/configuration.rb +0 -50
  41. data/lib/sidekiq/throttled/expirable_list.rb +0 -70
  42. data/lib/sidekiq/throttled/fetch/unit_of_work.rb +0 -83
  43. data/lib/sidekiq/throttled/fetch.rb +0 -94
  44. data/lib/sidekiq/throttled/middleware.rb +0 -22
  45. data/lib/sidekiq/throttled/patches/queue.rb +0 -18
  46. data/lib/sidekiq/throttled/queue_name.rb +0 -46
  47. data/lib/sidekiq/throttled/queues_pauser.rb +0 -152
  48. data/lib/sidekiq/throttled/testing.rb +0 -12
  49. data/lib/sidekiq/throttled/utils.rb +0 -19
  50. data/lib/sidekiq/throttled/web/queues.html.erb +0 -49
  51. data/lib/sidekiq/throttled/web/summary_fix.js +0 -10
  52. data/lib/sidekiq/throttled/web/summary_fix.rb +0 -35
  53. data/rubocop/layout.yml +0 -24
  54. data/rubocop/lint.yml +0 -41
  55. data/rubocop/metrics.yml +0 -4
  56. data/rubocop/performance.yml +0 -25
  57. data/rubocop/rspec.yml +0 -3
  58. data/rubocop/style.yml +0 -84
  59. data/sidekiq-throttled.gemspec +0 -36
  60. /data/{LICENSE.md → LICENSE.txt} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a4884da942e03171d7265651258f4da2ca601aa36219192b325a2ce0ff113fd
4
- data.tar.gz: 7b1d20e64db0cf3720f4b404a95744b77de186da718feb7f6abd7cd23623e08d
3
+ metadata.gz: 31c4673002a4919f71a18009e82812f21a777d241072bc095994e4a67662a49e
4
+ data.tar.gz: fa2c768c9ea4b023e16864c77eabe4949e9e3301a20aab5cdb5acdbdd3de9702
5
5
  SHA512:
6
- metadata.gz: 93699777efb1fd7794a7e566d6b129b0898f8efb327627b2a17b5e3e595a100fedc8b5a064ab8331b61c553cdd4a3aa2f3ac57c76d9d677c16ef006df18c592d
7
- data.tar.gz: 9aaa27dbd6c116fbeac961cb8f8d1413bc1b9ff84241cfa81ce4b63b479a0b2386d6de8c94a6176a461d4f50867ce80957fd4b4569b5bd9db377d41fafbb247c
6
+ metadata.gz: 636e41cc593e6f164061ff901ab6dab4eba7c65acf1bb41febbca13be55e9336bd86413ac50db80469429ff3bd8d9eaccd62b4e9f397a7692c184c7c70f0b98f
7
+ data.tar.gz: 7055289ad5e059da619ede7c4d9ec73876894dc4a3913851e55e256e81be9bb08c3671199c81eafaf6865fe50b0f60ad670fa2e74460de45c89b20af9514a90e
data/README.adoc ADDED
@@ -0,0 +1,314 @@
1
+ = Sidekiq::Throttled
2
+ :ci-link: https://github.com/ixti/sidekiq-throttled/actions/workflows/ci.yml
3
+ :ci-badge: https://img.shields.io/github/actions/workflow/status/ixti/sidekiq-throttled/ci.yml?branch=main&style=for-the-badge
4
+ :gem-link: http://rubygems.org/gems/sidekiq-throttled
5
+ :gem-badge: https://img.shields.io/gem/v/sidekiq-throttled?style=for-the-badge
6
+ :doc-link: http://www.rubydoc.info/gems/sidekiq-throttled
7
+ :doc-badge: https://img.shields.io/badge/Documentation-API-blue?style=for-the-badge
8
+
9
+ ****
10
+ {ci-link}[image:{ci-badge}[CI Status]]
11
+ {gem-link}[image:{gem-badge}[Latest Version]]
12
+ {doc-link}[image:{doc-badge}[API Documentation]]
13
+ ****
14
+
15
+ NOTE: This is the 1.x *development* branch. For the 0.x *stable* branch, please
16
+ see: https://github.com/ixti/sidekiq-throttled/tree/0-x-stable[0-x-stable]
17
+
18
+ Concurrency and threshold throttling for https://github.com/mperham/sidekiq[Sidekiq].
19
+
20
+
21
+ == Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ [source,ruby]
26
+ ----
27
+ gem "sidekiq-throttled"
28
+ ----
29
+
30
+ And then execute:
31
+
32
+ $ bundle
33
+
34
+ Or install it yourself as:
35
+
36
+ $ gem install sidekiq-throttled
37
+
38
+
39
+ == Usage
40
+
41
+ Add somewhere in your app's bootstrap (e.g. `config/initializers/sidekiq.rb` if
42
+ you are using Rails):
43
+
44
+ [source,ruby]
45
+ ----
46
+ require "sidekiq/throttled"
47
+ ----
48
+
49
+ Once you've done that you can include `Sidekiq::Throttled::Job` to your
50
+ job classes and configure throttling:
51
+
52
+ [source,ruby]
53
+ ----
54
+ class MyJob
55
+ include Sidekiq::Job
56
+ include Sidekiq::Throttled::Job
57
+
58
+ sidekiq_options :queue => :my_queue
59
+
60
+ sidekiq_throttle(
61
+ # Allow maximum 10 concurrent jobs of this class at a time.
62
+ concurrency: { limit: 10 },
63
+ # Allow maximum 1K jobs being processed within one hour window.
64
+ threshold: { limit: 1_000, period: 1.hour }
65
+ )
66
+
67
+ def perform
68
+ # ...
69
+ end
70
+ end
71
+ ----
72
+
73
+ TIP: `Sidekiq::Throttled::Job` is aliased as `Sidekiq::Throttled::Worker`,
74
+ thus if you're using `Sidekiq::Worker` naming convention, you can use the
75
+ alias for consistency:
76
+
77
+ [source,ruby]
78
+ ----
79
+ class MyWorker
80
+ include Sidekiq::Worker
81
+ include Sidekiq::Throttled::Worker
82
+
83
+ # ...
84
+ end
85
+ ----
86
+
87
+
88
+ === Middleware(s)
89
+
90
+ `Sidekiq::Throttled` relies on following bundled middlewares:
91
+
92
+ * `Sidekiq::Throttled::Middlewares::Server`
93
+
94
+ The middleware is automatically injected when you require `sidekiq/throttled`.
95
+ In rare cases this might be an issue. You can change to order manually:
96
+
97
+ [source,ruby]
98
+ ----
99
+ Sidekiq.configure_server do |config|
100
+ # ...
101
+
102
+ config.server_middleware do |chain|
103
+ chain.remove(Sidekiq::Throttled::Middlewares::Server)
104
+ chain.add(Sidekiq::Throttled::Middlewares::Server)
105
+ end
106
+ end
107
+ ----
108
+
109
+ === Configuration
110
+
111
+ [source,ruby]
112
+ ----
113
+ Sidekiq::Throttled.configure do |config|
114
+ # Period in seconds to exclude queue from polling in case it returned
115
+ # {config.cooldown_threshold} amount of throttled jobs in a row. Set
116
+ # this value to `nil` to disable cooldown manager completely.
117
+ # Default: 2.0
118
+ config.cooldown_period = 2.0
119
+
120
+ # Exclude queue from polling after it returned given amount of throttled
121
+ # jobs in a row.
122
+ # Default: 1 (cooldown after first throttled job)
123
+ config.cooldown_threshold = 1
124
+ end
125
+ ----
126
+
127
+
128
+ === Observer
129
+
130
+ You can specify an observer that will be called on throttling. To do so pass an
131
+ `:observer` option with callable object:
132
+
133
+ [source,ruby]
134
+ ----
135
+ class MyJob
136
+ include Sidekiq::Job
137
+ include Sidekiq::Throttled::Job
138
+
139
+ MY_OBSERVER = lambda do |strategy, *args|
140
+ # do something
141
+ end
142
+
143
+ sidekiq_options queue: :my_queue
144
+
145
+ sidekiq_throttle(
146
+ concurrency: { limit: 10 },
147
+ threshold: { limit: 100, period: 1.hour },
148
+ observer: MY_OBSERVER
149
+ )
150
+
151
+ def perform(*args)
152
+ # ...
153
+ end
154
+ end
155
+ ----
156
+
157
+ Observer will receive `strategy, *args` arguments, where `strategy` is a Symbol
158
+ `:concurrency` or `:threshold`, and `*args` are the arguments that were passed
159
+ to the job.
160
+
161
+
162
+ === Dynamic throttling
163
+
164
+ You can throttle jobs dynamically with `:key_suffix` option:
165
+
166
+ [source,ruby]
167
+ ----
168
+ class MyJob
169
+ include Sidekiq::Job
170
+ include Sidekiq::Throttled::Job
171
+
172
+ sidekiq_options queue: :my_queue
173
+
174
+ sidekiq_throttle(
175
+ # Allow maximum 10 concurrent jobs per user at a time.
176
+ concurrency: { limit: 10, key_suffix: -> (user_id) { user_id } }
177
+ )
178
+
179
+ def perform(user_id)
180
+ # ...
181
+ end
182
+ end
183
+ ----
184
+
185
+ You can also supply dynamic values for limits and periods by supplying a proc
186
+ for these values. The proc will be evaluated at the time the job is fetched
187
+ and will receive the same arguments that are passed to the job.
188
+
189
+ [source,ruby]
190
+ ----
191
+ class MyJob
192
+ include Sidekiq::Job
193
+ include Sidekiq::Throttled::Job
194
+
195
+ sidekiq_options queue: :my_queue
196
+
197
+ sidekiq_throttle(
198
+ # Allow maximum 1000 concurrent jobs of this class at a time for VIPs and 10 for all other users.
199
+ concurrency: {
200
+ limit: ->(user_id) { User.vip?(user_id) ? 1_000 : 10 },
201
+ key_suffix: ->(user_id) { User.vip?(user_id) ? "vip" : "std" }
202
+ },
203
+ # Allow 1000 jobs/hour to be processed for VIPs and 10/day for all others
204
+ threshold: {
205
+ limit: ->(user_id) { User.vip?(user_id) ? 1_000 : 10 },
206
+ period: ->(user_id) { User.vip?(user_id) ? 1.hour : 1.day },
207
+ key_suffix: ->(user_id) { User.vip?(user_id) ? "vip" : "std" }
208
+ }
209
+ )
210
+
211
+ def perform(user_id)
212
+ # ...
213
+ end
214
+ end
215
+ ----
216
+
217
+ You also can use several different keys to throttle one worker.
218
+
219
+ [source,ruby]
220
+ ----
221
+ class MyJob
222
+ include Sidekiq::Job
223
+ include Sidekiq::Throttled::Job
224
+
225
+ sidekiq_options queue: :my_queue
226
+
227
+ sidekiq_throttle(
228
+ # Allow maximum 10 concurrent jobs per project at a time and maximum 2 jobs per user
229
+ concurrency: [
230
+ { limit: 10, key_suffix: -> (project_id, user_id) { project_id } },
231
+ { limit: 2, key_suffix: -> (project_id, user_id) { user_id } }
232
+ ]
233
+ # For :threshold it works the same
234
+ )
235
+
236
+ def perform(project_id, user_id)
237
+ # ...
238
+ end
239
+ end
240
+ ----
241
+
242
+ IMPORTANT: Don't forget to specify `:key_suffix` and make it return different
243
+ values if you are using dynamic limit/period options. Otherwise, you risk
244
+ getting into some trouble.
245
+
246
+
247
+ === Concurrency throttling fine-tuning
248
+
249
+ Concurrency throttling is based on distributed locks. Those locks have default
250
+ time to live (TTL) set to 15 minutes. If your job takes more than 15 minutes
251
+ to finish, lock will be released and you might end up with more jobs running
252
+ concurrently than you expect.
253
+
254
+ This is done to avoid deadlocks - when by any reason (e.g. Sidekiq process was
255
+ OOM-killed) cleanup middleware wasn't executed and locks were not released.
256
+
257
+ If your job takes more than 15 minutes to complete, you can tune concurrency
258
+ lock TTL to fit your needs:
259
+
260
+ [source,ruby]
261
+ ----
262
+ # Set concurrency strategy lock TTL to 1 hour.
263
+ sidekiq_throttle(concurrency: { limit: 20, ttl: 1.hour.to_i })
264
+ ----
265
+
266
+
267
+ == Supported Ruby Versions
268
+
269
+ This library aims to support and is tested against the following Ruby versions:
270
+
271
+ * Ruby 3.0.x
272
+ * Ruby 3.1.x
273
+ * Ruby 3.2.x
274
+
275
+ If something doesn't work on one of these versions, it's a bug.
276
+
277
+ This library may inadvertently work (or seem to work) on other Ruby versions,
278
+ however support will only be provided for the versions listed above.
279
+
280
+ If you would like this library to support another Ruby version or
281
+ implementation, you may volunteer to be a maintainer. Being a maintainer
282
+ entails making sure all tests run and pass on that implementation. When
283
+ something breaks on your implementation, you will be responsible for providing
284
+ patches in a timely fashion. If critical issues for a particular implementation
285
+ exist at the time of a major release, support for that Ruby version may be
286
+ dropped.
287
+
288
+
289
+ == Supported Sidekiq Versions
290
+
291
+ This library aims to support and work with following Sidekiq versions:
292
+
293
+ * Sidekiq 6.5.x
294
+ * Sidekiq 7.0.x
295
+ * Sidekiq 7.1.x
296
+ * Sidekiq 7.2.x
297
+
298
+
299
+ == Development
300
+
301
+ bundle install
302
+ bundle exec appraisal generate
303
+ bundle exec appraisal install
304
+ bundle exec rake
305
+
306
+
307
+ == Contributing
308
+
309
+ * Fork sidekiq-throttled on GitHub
310
+ * Make your changes
311
+ * Ensure all tests pass (`bundle exec rake`)
312
+ * Send a pull request
313
+ * If we like them we'll merge them
314
+ * If we've accepted a patch, feel free to ask for commit access!
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module Throttled
5
+ # Configuration object.
6
+ class Config
7
+ # Period in seconds to exclude queue from polling in case it returned
8
+ # {#cooldown_threshold} amount of throttled jobs in a row.
9
+ #
10
+ # Set this to `nil` to disable cooldown completely.
11
+ #
12
+ # @return [Float, nil]
13
+ attr_reader :cooldown_period
14
+
15
+ # Amount of throttled jobs returned from the queue subsequently after
16
+ # which queue will be excluded from polling for the durations of
17
+ # {#cooldown_period}.
18
+ #
19
+ # @return [Integer]
20
+ attr_reader :cooldown_threshold
21
+
22
+ def initialize
23
+ @cooldown_period = 2.0
24
+ @cooldown_threshold = 1
25
+ end
26
+
27
+ # @!attribute [w] cooldown_period
28
+ def cooldown_period=(value)
29
+ raise TypeError, "unexpected type #{value.class}" unless value.nil? || value.is_a?(Float)
30
+ raise ArgumentError, "period must be positive" unless value.nil? || value.positive?
31
+
32
+ @cooldown_period = value
33
+ end
34
+
35
+ # @!attribute [w] cooldown_threshold
36
+ def cooldown_threshold=(value)
37
+ raise TypeError, "unexpected type #{value.class}" unless value.is_a?(Integer)
38
+ raise ArgumentError, "threshold must be positive" unless value.positive?
39
+
40
+ @cooldown_threshold = value
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent"
4
+
5
+ require_relative "./expirable_set"
6
+
7
+ module Sidekiq
8
+ module Throttled
9
+ # @api internal
10
+ #
11
+ # Queues cooldown manager. Tracks list of queues that should be temporarily
12
+ # (for the duration of {Config#cooldown_period}) excluded from polling.
13
+ class Cooldown
14
+ class << self
15
+ # Returns new {Cooldown} instance if {Config#cooldown_period} is not `nil`.
16
+ #
17
+ # @param config [Config]
18
+ # @return [Cooldown, nil]
19
+ def [](config)
20
+ new(config) if config.cooldown_period
21
+ end
22
+ end
23
+
24
+ # @param config [Config]
25
+ def initialize(config)
26
+ @queues = ExpirableSet.new(config.cooldown_period)
27
+ @threshold = config.cooldown_threshold
28
+ @tracker = Concurrent::Map.new
29
+ end
30
+
31
+ # Notify that given queue returned job that was throttled.
32
+ #
33
+ # @param queue [String]
34
+ # @return [void]
35
+ def notify_throttled(queue)
36
+ @queues.add(queue) if @threshold <= @tracker.merge_pair(queue, 1, &:succ)
37
+ end
38
+
39
+ # Notify that given queue returned job that was not throttled.
40
+ #
41
+ # @param queue [String]
42
+ # @return [void]
43
+ def notify_admitted(queue)
44
+ @tracker.delete(queue)
45
+ end
46
+
47
+ # List of queues that should not be polled
48
+ #
49
+ # @return [Array<String>]
50
+ def queues
51
+ @queues.to_a
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent"
4
+
5
+ module Sidekiq
6
+ module Throttled
7
+ # @api internal
8
+ #
9
+ # Set of elements with expirations.
10
+ #
11
+ # @example
12
+ # set = ExpirableSet.new(10.0)
13
+ # set.add("a")
14
+ # sleep(5)
15
+ # set.add("b")
16
+ # set.to_a # => ["a", "b"]
17
+ # sleep(5)
18
+ # set.to_a # => ["b"]
19
+ class ExpirableSet
20
+ include Enumerable
21
+
22
+ # @param ttl [Float] expiration is seconds
23
+ # @raise [ArgumentError] if `ttl` is not positive Float
24
+ def initialize(ttl)
25
+ raise ArgumentError, "ttl must be positive Float" unless ttl.is_a?(Float) && ttl.positive?
26
+
27
+ @elements = Concurrent::Map.new
28
+ @ttl = ttl
29
+ end
30
+
31
+ # @param element [Object]
32
+ # @return [ExpirableSet] self
33
+ def add(element)
34
+ # cleanup expired elements to avoid mem-leak
35
+ horizon = now
36
+ expired = @elements.each_pair.select { |(_, sunset)| expired?(sunset, horizon) }
37
+ expired.each { |pair| @elements.delete_pair(*pair) }
38
+
39
+ # add new element
40
+ @elements[element] = now + @ttl
41
+
42
+ self
43
+ end
44
+
45
+ # @yield [Object] Gives each live (not expired) element to the block
46
+ def each
47
+ return to_enum __method__ unless block_given?
48
+
49
+ horizon = now
50
+
51
+ @elements.each_pair do |element, sunset|
52
+ yield element unless expired?(sunset, horizon)
53
+ end
54
+
55
+ self
56
+ end
57
+
58
+ private
59
+
60
+ # @return [Float]
61
+ def now
62
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
63
+ end
64
+
65
+ def expired?(sunset, horizon)
66
+ sunset <= horizon
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # internal
4
- require "sidekiq/throttled/registry"
4
+ require_relative "./registry"
5
5
 
6
6
  module Sidekiq
7
7
  module Throttled
@@ -13,7 +13,7 @@ module Sidekiq
13
13
  # include Sidekiq::Job
14
14
  # include Sidekiq::Throttled::Job
15
15
  #
16
- # sidkiq_options :queue => :my_queue
16
+ # sidekiq_options :queue => :my_queue
17
17
  # sidekiq_throttle :threshold => { :limit => 123, :period => 1.hour }
18
18
  #
19
19
  # def perform
@@ -29,8 +29,8 @@ module Sidekiq
29
29
  # in order to make API inline with `include Sidekiq::Job`.
30
30
  #
31
31
  # @private
32
- def self.included(worker)
33
- worker.send(:extend, ClassMethods)
32
+ def self.included(base)
33
+ base.extend(ClassMethods)
34
34
  end
35
35
 
36
36
  # Helper methods added to the singleton class of destination
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # internal
4
+ require_relative "../registry"
5
+
6
+ module Sidekiq
7
+ module Throttled
8
+ module Middlewares
9
+ # Server middleware required for Sidekiq::Throttled functioning.
10
+ class Server
11
+ include Sidekiq::ServerMiddleware
12
+
13
+ def call(_worker, msg, _queue)
14
+ yield
15
+ ensure
16
+ job = msg.fetch("wrapped") { msg["class"] }
17
+ jid = msg["jid"]
18
+
19
+ if job && jid
20
+ Registry.get job do |strategy|
21
+ strategy.finalize!(jid, *msg["args"])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq"
4
+ require "sidekiq/fetch"
5
+
6
+ module Sidekiq
7
+ module Throttled
8
+ module Patches
9
+ module BasicFetch
10
+ # Retrieves job from redis.
11
+ #
12
+ # @return [Sidekiq::Throttled::UnitOfWork, nil]
13
+ def retrieve_work
14
+ work = super
15
+
16
+ if work && Throttled.throttled?(work.job)
17
+ Throttled.cooldown&.notify_throttled(work.queue)
18
+ requeue_throttled(work)
19
+ return nil
20
+ end
21
+
22
+ Throttled.cooldown&.notify_admitted(work.queue) if work
23
+
24
+ work
25
+ end
26
+
27
+ private
28
+
29
+ # Pushes job back to the head of the queue, so that job won't be tried
30
+ # immediately after it was requeued (in most cases).
31
+ #
32
+ # @note This is triggered when job is throttled. So it is same operation
33
+ # Sidekiq performs upon `Sidekiq::Worker.perform_async` call.
34
+ #
35
+ # @return [void]
36
+ def requeue_throttled(work)
37
+ redis { |conn| conn.lpush(work.queue, work.job) }
38
+ end
39
+
40
+ # Returns list of queues to try to fetch jobs from.
41
+ #
42
+ # @note It may return an empty array.
43
+ # @param [Array<String>] queues
44
+ # @return [Array<String>]
45
+ def queues_cmd
46
+ super - (Throttled.cooldown&.queues || [])
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ Sidekiq::BasicFetch.prepend(Sidekiq::Throttled::Patches::BasicFetch)
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # internal
4
- require "sidekiq/throttled/strategy"
5
- require "sidekiq/throttled/utils"
4
+ require_relative "./strategy"
6
5
 
7
6
  module Sidekiq
8
7
  module Throttled
@@ -14,8 +13,6 @@ module Sidekiq
14
13
  @aliases = {}
15
14
 
16
15
  class << self
17
- include Utils
18
-
19
16
  # Adds strategy to the registry.
20
17
  #
21
18
  # @param (see Strategy#initialize)
@@ -105,9 +102,7 @@ module Sidekiq
105
102
  # @param name [Class, #to_s]
106
103
  # @return [Strategy, nil]
107
104
  def find_by_class(name)
108
- return unless Throttled.configuration.inherit_strategies?
109
-
110
- const = name.is_a?(Class) ? name : constantize(name)
105
+ const = name.is_a?(Class) ? name : Object.const_get(name)
111
106
  return unless const.is_a?(Class)
112
107
 
113
108
  const.ancestors.each do |m|
@@ -115,6 +110,8 @@ module Sidekiq
115
110
  return strategy if strategy
116
111
  end
117
112
 
113
+ nil
114
+ rescue NameError
118
115
  nil
119
116
  end
120
117
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redis/prescription"
3
+ require "redis_prescription"
4
4
 
5
- require "sidekiq/throttled/strategy/base"
5
+ require_relative "./base"
6
6
 
7
7
  module Sidekiq
8
8
  module Throttled
@@ -20,7 +20,7 @@ module Sidekiq
20
20
  # PUSH(@key, @jid)
21
21
  # return 0
22
22
  # end
23
- SCRIPT = Redis::Prescription.read "#{__dir__}/concurrency.lua"
23
+ SCRIPT = RedisPrescription.new(File.read("#{__dir__}/concurrency.lua"))
24
24
  private_constant :SCRIPT
25
25
 
26
26
  # @param [#to_s] strategy_key
@@ -49,9 +49,7 @@ module Sidekiq
49
49
  keys = [key(job_args)]
50
50
  argv = [jid.to_s, job_limit, @ttl, Time.now.to_f]
51
51
 
52
- Sidekiq.redis do |redis|
53
- 1 == SCRIPT.eval(redis, :keys => keys, :argv => argv)
54
- end
52
+ Sidekiq.redis { |redis| 1 == SCRIPT.call(redis, keys: keys, argv: argv) }
55
53
  end
56
54
 
57
55
  # @return [Integer] Current count of jobs