sidekiq-throttled 1.5.0 → 1.5.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d5819912372e26634558752740bc154c4d22899b42925740b86152433744740
4
- data.tar.gz: dadcea04012b95e0349728c573a3253d86bae7920ee5844b1df9fb80933c2267
3
+ metadata.gz: 07f60ab84ab53725f6189d8a142ccb7d5596ea408a5d52087e2665420c92a276
4
+ data.tar.gz: 489d3d9a5a72dc20af8295d6942d4abdc9a27dfffdf917b753a08af78184d59e
5
5
  SHA512:
6
- metadata.gz: cf81c1a319d6f4af5c5a06f5df95dd99ad44a405f152cfda5b4c6123d4692b93d23137ab12422d6b83c82090dc422b15b441f34ed43dc1868933d54354c1326d
7
- data.tar.gz: f72803ff9a84bb3cc729869dc1105dcc9f9581c7c4c59eb2bb8a28594a7fe0df49e9b064dbae2c1f46020790637769196f98b5b38a0f77f2cb858f1b6d928679
6
+ metadata.gz: bac5ad071e0de8913f80f8c78cec87ee98c61da77a48c46000ba3680f0eeb684b2970c69d256b61cf5d1b680121f2762a0ae3c9220729ee25140ba6cbf3f9598
7
+ data.tar.gz: 92d08e2c0f76716816385b24df2c06092fa416ceb22714657967e2e312fe04357000db45fc18ee4f99d5eab57b6c267d93556a8064cb704478590b457ec48f2f
data/README.adoc CHANGED
@@ -262,6 +262,33 @@ IMPORTANT: Don't forget to specify `:key_suffix` and make it return different
262
262
  values if you are using dynamic limit/period options. Otherwise, you risk
263
263
  getting into some trouble.
264
264
 
265
+ [source,ruby]
266
+ ----
267
+ class MyJob
268
+ include Sidekiq::Job
269
+ include Sidekiq::Throttled::Job
270
+
271
+ sidekiq_options queue: :my_queue
272
+
273
+ sidekiq_throttle(
274
+ concurrency: { limit: 10 },
275
+ # Allow 500 jobs per minute, 5,000 per hour, and 50,000 per day:
276
+ threshold: [
277
+ { limit: 500, period: 1.minute, key_suffix: "minutely" },
278
+ { limit: 5_000, period: 1.hour, key_suffix: "hourly" },
279
+ { limit: 50_000, period: 1.day, key_suffix: "daily" },
280
+ ]
281
+ )
282
+
283
+ def perform(project_id, user_id)
284
+ # ...
285
+ end
286
+ end
287
+ ----
288
+
289
+ NOTE: `key_suffix` does not have to be a proc/lambda, it can just be a
290
+ string value. This can come in handy to set throttle limits for different
291
+ ranges of time
265
292
 
266
293
  === Concurrency throttling fine-tuning
267
294
 
@@ -24,8 +24,6 @@ module Sidekiq
24
24
  #
25
25
  # @see ClassMethods
26
26
  module Job
27
- VALID_VALUES_FOR_REQUEUE_WITH = %i[enqueue schedule].freeze
28
-
29
27
  # Extends worker class with {ClassMethods}.
30
28
  #
31
29
  # @note Using `included` hook with extending worker with {ClassMethods}
@@ -91,13 +89,6 @@ module Sidekiq
91
89
  # @see Registry.add
92
90
  # @return [void]
93
91
  def sidekiq_throttle(**kwargs)
94
- requeue_options = Throttled.config.default_requeue_options.merge(kwargs.delete(:requeue) || {})
95
- unless VALID_VALUES_FOR_REQUEUE_WITH.include?(requeue_options[:with])
96
- raise ArgumentError, "requeue: #{requeue_options[:with]} is not a valid value for :with"
97
- end
98
-
99
- self.sidekiq_throttled_requeue_options = requeue_options
100
-
101
92
  Registry.add(self, **kwargs)
102
93
  end
103
94
 
@@ -11,7 +11,11 @@ module Sidekiq
11
11
  # Meta-strategy that couples {Concurrency} and {Threshold} strategies.
12
12
  #
13
13
  # @private
14
- class Strategy
14
+ class Strategy # rubocop:disable Metrics/ClassLength
15
+ # :enqueue means put the job back at the end of the queue immediately
16
+ # :schedule means schedule enqueueing the job for a later time when we expect to have capacity
17
+ VALID_VALUES_FOR_REQUEUE_WITH = %i[enqueue schedule].freeze
18
+
15
19
  # @!attribute [r] concurrency
16
20
  # @return [Strategy::Concurrency, nil]
17
21
  attr_reader :concurrency
@@ -24,6 +28,10 @@ module Sidekiq
24
28
  # @return [Proc, nil]
25
29
  attr_reader :observer
26
30
 
31
+ # @!attribute [r] requeue_options
32
+ # @return [Hash, nil]
33
+ attr_reader :requeue_options
34
+
27
35
  # @param [#to_s] name
28
36
  # @param [Hash] concurrency Concurrency options.
29
37
  # See keyword args of {Strategy::Concurrency#initialize} for details.
@@ -31,7 +39,8 @@ module Sidekiq
31
39
  # See keyword args of {Strategy::Threshold#initialize} for details.
32
40
  # @param [#call] key_suffix Dynamic key suffix generator.
33
41
  # @param [#call] observer Process called after throttled.
34
- def initialize(name, concurrency: nil, threshold: nil, key_suffix: nil, observer: nil)
42
+ # @param [#call] requeue What to do with jobs that are throttled.
43
+ def initialize(name, concurrency: nil, threshold: nil, key_suffix: nil, observer: nil, requeue: nil) # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
35
44
  @observer = observer
36
45
 
37
46
  @concurrency = StrategyCollection.new(concurrency,
@@ -44,7 +53,9 @@ module Sidekiq
44
53
  name: name,
45
54
  key_suffix: key_suffix)
46
55
 
47
- raise ArgumentError, "Neither :concurrency nor :threshold given" unless @concurrency.any? || @threshold.any?
56
+ @requeue_options = Throttled.config.default_requeue_options.merge(requeue || {})
57
+
58
+ validate!
48
59
  end
49
60
 
50
61
  # @return [Boolean] whenever strategy has dynamic config
@@ -72,26 +83,30 @@ module Sidekiq
72
83
  false
73
84
  end
74
85
 
75
- # Return throttled job to be executed later. Implementation depends on the value of `with`:
76
- # :enqueue means put the job back at the end of the queue immediately
77
- # :schedule means schedule enqueueing the job for a later time when we expect to have capacity
78
- #
79
- # @param [#to_s, #call] with How to handle the throttled job
80
- # @param [#to_s, #call] to Name of the queue to re-queue the job to.
81
- # If not specified, will use the job's original queue.
86
+ # @return [Proc, Symbol] How to requeue the throttled job
87
+ def requeue_with
88
+ requeue_options[:with]
89
+ end
90
+
91
+ # @return [String, nil] Name of the queue to re-queue the job to.
92
+ def requeue_to
93
+ requeue_options[:to]
94
+ end
95
+
96
+ # Return throttled job to be executed later. Implementation depends on the strategy's `requeue` options.
82
97
  # @return [void]
83
- def requeue_throttled(work, with:, to: nil) # rubocop:disable Metrics/MethodLength
84
- # Resolve :with and :to arguments, calling them if they are Procs
98
+ def requeue_throttled(work) # rubocop:disable Metrics/MethodLength
99
+ # Resolve :with and :to options, calling them if they are Procs
85
100
  job_args = JSON.parse(work.job)["args"]
86
- requeue_with = with.respond_to?(:call) ? with.call(*job_args) : with
87
- target_queue = calc_target_queue(work, to)
101
+ with = requeue_with.respond_to?(:call) ? requeue_with.call(*job_args) : requeue_with
102
+ target_queue = calc_target_queue(work)
88
103
 
89
- case requeue_with
104
+ case with
90
105
  when :enqueue
91
106
  re_enqueue_throttled(work, target_queue)
92
107
  when :schedule
93
108
  # Find out when we will next be able to execute this job, and reschedule for then.
94
- reschedule_throttled(work, requeue_to: target_queue)
109
+ reschedule_throttled(work, target_queue)
95
110
  else
96
111
  raise "unrecognized :with option #{with}"
97
112
  end
@@ -112,49 +127,65 @@ module Sidekiq
112
127
 
113
128
  private
114
129
 
115
- def calc_target_queue(work, to) # rubocop:disable Metrics/MethodLength
116
- target = case to
130
+ def validate!
131
+ unless VALID_VALUES_FOR_REQUEUE_WITH.include?(@requeue_options[:with]) ||
132
+ @requeue_options[:with].respond_to?(:call)
133
+ raise ArgumentError, "requeue: #{@requeue_options[:with]} is not a valid value for :with"
134
+ end
135
+
136
+ raise ArgumentError, "Neither :concurrency nor :threshold given" unless @concurrency.any? || @threshold.any?
137
+ end
138
+
139
+ def calc_target_queue(work) # rubocop:disable Metrics/MethodLength
140
+ target = case requeue_to
117
141
  when Proc, Method
118
- to.call(*JSON.parse(work.job)["args"])
142
+ requeue_to.call(*JSON.parse(work.job)["args"])
119
143
  when NilClass
120
144
  work.queue
121
145
  when String, Symbol
122
- to.to_s
146
+ requeue_to
123
147
  else
124
148
  raise ArgumentError, "Invalid argument for `to`"
125
149
  end
126
150
 
127
151
  target = work.queue if target.nil? || target.empty?
128
152
 
129
- target.start_with?("queue:") ? target : "queue:#{target}"
153
+ target.to_s
130
154
  end
131
155
 
132
156
  # Push the job back to the head of the queue.
133
- def re_enqueue_throttled(work, requeue_to)
157
+ # The queue name is expected to include the "queue:" prefix, so we add it if it's missing.
158
+ def re_enqueue_throttled(work, target_queue)
159
+ target_queue = "queue:#{target_queue}" unless target_queue.start_with?("queue:")
160
+
134
161
  case work.class.name
135
162
  when "Sidekiq::Pro::SuperFetch::UnitOfWork"
136
163
  # Calls SuperFetch UnitOfWork's requeue to remove the job from the
137
164
  # temporary queue and push job back to the head of the target queue, so that
138
165
  # the job won't be tried immediately after it was requeued (in most cases).
139
- work.queue = requeue_to if requeue_to
166
+ work.queue = target_queue
140
167
  work.requeue
141
168
  else
142
169
  # This is the same operation Sidekiq performs upon `Sidekiq::Worker.perform_async` call.
143
- Sidekiq.redis { |conn| conn.lpush(requeue_to, work.job) }
170
+ Sidekiq.redis { |conn| conn.lpush(target_queue, work.job) }
144
171
  end
145
172
  end
146
173
 
147
- def reschedule_throttled(work, requeue_to:)
148
- message = JSON.parse(work.job)
149
- job_class = message.fetch("wrapped") { message.fetch("class") { return false } }
150
- job_args = message["args"]
174
+ # Reschedule the job to be executed later in the target queue.
175
+ # The queue name should NOT include the "queue:" prefix, so we remove it if it's present.
176
+ def reschedule_throttled(work, target_queue)
177
+ target_queue = target_queue.delete_prefix("queue:")
178
+ message = JSON.parse(work.job)
179
+ job_class = message.fetch("wrapped") { message.fetch("class") { return false } }
180
+ job_args = message["args"]
151
181
 
152
182
  # Re-enqueue the job to the target queue at another time as a NEW unit of work
153
183
  # AND THEN mark this work as done, so SuperFetch doesn't think this instance is orphaned
154
184
  # Technically, the job could processed twice if the process dies between the two lines,
155
185
  # but your job should be idempotent anyway, right?
156
186
  # The job running twice was already a risk with SuperFetch anyway and this doesn't really increase that risk.
157
- Sidekiq::Client.enqueue_to_in(requeue_to, retry_in(work), Object.const_get(job_class), *job_args)
187
+ Sidekiq::Client.enqueue_to_in(target_queue, retry_in(work), Object.const_get(job_class), *job_args)
188
+
158
189
  work.acknowledge
159
190
  end
160
191
 
@@ -43,7 +43,7 @@ module Sidekiq
43
43
 
44
44
  # @return [Float] How long, in seconds, before we'll next be able to take on jobs
45
45
  def retry_in(*args)
46
- max { |s| s.retry_in(*args) }
46
+ map { |s| s.retry_in(*args) }.max
47
47
  end
48
48
 
49
49
  # Marks job as being processed.
@@ -3,6 +3,6 @@
3
3
  module Sidekiq
4
4
  module Throttled
5
5
  # Gem version
6
- VERSION = "1.5.0"
6
+ VERSION = "1.5.2"
7
7
  end
8
8
  end
@@ -102,7 +102,7 @@ module Sidekiq
102
102
  job_class = Object.const_get(message.fetch("wrapped") { message.fetch("class") { return false } })
103
103
 
104
104
  Registry.get job_class do |strategy|
105
- strategy.requeue_throttled(work, **job_class.sidekiq_throttled_requeue_options)
105
+ strategy.requeue_throttled(work)
106
106
  end
107
107
  end
108
108
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-throttled
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexey Zapparov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-17 00:00:00.000000000 Z
11
+ date: 2025-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -90,9 +90,9 @@ licenses:
90
90
  - MIT
91
91
  metadata:
92
92
  homepage_uri: https://github.com/ixti/sidekiq-throttled
93
- source_code_uri: https://github.com/ixti/sidekiq-throttled/tree/v1.5.0
93
+ source_code_uri: https://github.com/ixti/sidekiq-throttled/tree/v1.5.2
94
94
  bug_tracker_uri: https://github.com/ixti/sidekiq-throttled/issues
95
- changelog_uri: https://github.com/ixti/sidekiq-throttled/blob/v1.5.0/CHANGES.md
95
+ changelog_uri: https://github.com/ixti/sidekiq-throttled/blob/v1.5.2/CHANGES.md
96
96
  rubygems_mfa_required: 'true'
97
97
  post_install_message:
98
98
  rdoc_options: []
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  requirements: []
112
- rubygems_version: 3.4.22
112
+ rubygems_version: 3.5.22
113
113
  signing_key:
114
114
  specification_version: 4
115
115
  summary: Concurrency and rate-limit throttling for Sidekiq