sidekiq 7.3.2 → 7.3.5

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: ce4653de61d5b923ce7556490e95cf6918dd1c94340417ec4093f11cc8ceda39
4
- data.tar.gz: e9faa23d4535a5b647c5dc86c28ce64317ea964f44faa9141b5a883f77b80dcd
3
+ metadata.gz: ada259b22664fbbb740c3d28fa06578cb6fc00a42590ea459a1f2395f8e38a92
4
+ data.tar.gz: 0636466a99e8a0df4d955b0928d76b504df74bba4e3ab13a2d08237ccd57b162
5
5
  SHA512:
6
- metadata.gz: 5fa259f415261689936b4f03f179198ea6849fbe63b932b5836fa800714055ce574a80a56462355a92d125440362957ebf02e5bafbde55fe8211805c80573ae5
7
- data.tar.gz: 0aaf341ae13c377e6f944344e0feef246e3450ae9eb0d74fcbf8e932e9c9fcf0d8f0dd56ce374064d4f51c4d99f4c306560c3283db27f70ae6aa5af346d83ec3
6
+ metadata.gz: 209f76b8ceebdb2cac83d22aab78873dad373a9ba488e7d320972e8ced4d3ac71454ef6669db321727ce3e4a85fdc5744005ff64d1a0f4cd48d9a4231bddf384
7
+ data.tar.gz: df094fd4baa169249e92309814d67d0fb5a53b5da4990c0510f5ae1d96e07541356a3014f75b8b79a97f0a91b03b92b576331422c54f36bb2c4943f2f0f87b08
data/Changes.md CHANGED
@@ -2,12 +2,50 @@
2
2
 
3
3
  [Sidekiq Changes](https://github.com/sidekiq/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/sidekiq/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/sidekiq/sidekiq/blob/main/Ent-Changes.md)
4
4
 
5
+ 7.3.5
6
+ ----------
7
+
8
+ - Reimplement `retry_all` and `kill_all` API methods to use ZPOPMIN,
9
+ approximately 30-60% faster. [#6481]
10
+ - Add preload testing binary at `examples/testing/sidekiq_boot` to verify your Rails app boots correctly with Sidekiq Enterprise's app preloading.
11
+ - Fix circular require with ActiveJob adapter [#6477]
12
+ - Fix potential race condition leading to incorrect serialized values for CurrentAttributes [#6475]
13
+ - Restore missing elapsed time when default job logging is disabled
14
+
15
+ 7.3.4
16
+ ----------
17
+
18
+ - Fix FrozenError when starting Sidekiq [#6470]
19
+
20
+ 7.3.3
21
+ ----------
22
+
23
+ - Freeze global configuration once boot is complete, to avoid configuration race conditions [#6466, #6465]
24
+ - Sidekiq now warns if a job iteration takes longer than the `-t` timeout setting (defaults to 25 seconds)
25
+ - Iteration callbacks now have easy access to job arguments via the `arguments` method:
26
+ ```ruby
27
+ def on_stop
28
+ p arguments # => `[123, "string", {"key" => "value"}]`
29
+ id, str, hash = arguments
30
+ end
31
+ ```
32
+ - Iterable jobs can be cancelled via `Sidekiq::Client#cancel!`:
33
+ ```ruby
34
+ c = Sidekiq::Client.new
35
+ jid = c.push("class" => SomeJob, "args" => [123])
36
+ c.cancel!(jid) # => true
37
+ ```
38
+ - Take over support for ActiveJob's `:sidekiq` adapter [#6430, fatkodima]
39
+ - Ensure CurrentAttributes are in scope when creating batch callbacks [#6455]
40
+ - Add `Sidekiq.gem_version` API.
41
+ - Update Ukranian translations
42
+
5
43
  7.3.2
6
44
  ----------
7
45
 
8
46
  - Adjust ActiveRecord batch iteration to restart an interrupted batch from the beginning.
9
47
  Each batch should be processed as a single transaction in order to be idempotent. [#6405]
10
- - Fix typo in S::DeadSet#kill [#6397]
48
+ - Fix typo in Sidekiq::DeadSet#kill [#6397]
11
49
  - Fix CSS issue with bottom bar in Web UI [#6414]
12
50
 
13
51
  7.3.1
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module ActiveJob
5
+ # @api private
6
+ class Wrapper
7
+ include Sidekiq::Job
8
+
9
+ def perform(job_data)
10
+ ::ActiveJob::Base.execute(job_data.merge("provider_job_id" => jid))
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ module ActiveJob
17
+ module QueueAdapters
18
+ # Explicitly remove the implementation existing in older rails'.
19
+ remove_const(:SidekiqAdapter) if defined?("::#{name}::SidekiqAdapter")
20
+
21
+ # Sidekiq adapter for Active Job
22
+ #
23
+ # To use Sidekiq set the queue_adapter config to +:sidekiq+.
24
+ #
25
+ # Rails.application.config.active_job.queue_adapter = :sidekiq
26
+ class SidekiqAdapter
27
+ # Defines whether enqueuing should happen implicitly to after commit when called
28
+ # from inside a transaction.
29
+ # @api private
30
+ def enqueue_after_transaction_commit?
31
+ true
32
+ end
33
+
34
+ # @api private
35
+ def enqueue(job)
36
+ job.provider_job_id = JobWrapper.set(
37
+ wrapped: job.class,
38
+ queue: job.queue_name
39
+ ).perform_async(job.serialize)
40
+ end
41
+
42
+ # @api private
43
+ def enqueue_at(job, timestamp)
44
+ job.provider_job_id = JobWrapper.set(
45
+ wrapped: job.class,
46
+ queue: job.queue_name
47
+ ).perform_at(timestamp, job.serialize)
48
+ end
49
+
50
+ # @api private
51
+ def enqueue_all(jobs)
52
+ enqueued_count = 0
53
+ jobs.group_by(&:class).each do |job_class, same_class_jobs|
54
+ same_class_jobs.group_by(&:queue_name).each do |queue, same_class_and_queue_jobs|
55
+ immediate_jobs, scheduled_jobs = same_class_and_queue_jobs.partition { |job| job.scheduled_at.nil? }
56
+
57
+ if immediate_jobs.any?
58
+ jids = Sidekiq::Client.push_bulk(
59
+ "class" => JobWrapper,
60
+ "wrapped" => job_class,
61
+ "queue" => queue,
62
+ "args" => immediate_jobs.map { |job| [job.serialize] }
63
+ )
64
+ enqueued_count += jids.compact.size
65
+ end
66
+
67
+ if scheduled_jobs.any?
68
+ jids = Sidekiq::Client.push_bulk(
69
+ "class" => JobWrapper,
70
+ "wrapped" => job_class,
71
+ "queue" => queue,
72
+ "args" => scheduled_jobs.map { |job| [job.serialize] },
73
+ "at" => scheduled_jobs.map { |job| job.scheduled_at&.to_f }
74
+ )
75
+ enqueued_count += jids.compact.size
76
+ end
77
+ end
78
+ end
79
+ enqueued_count
80
+ end
81
+
82
+ # Defines a class alias for backwards compatibility with enqueued Active Job jobs.
83
+ # @api private
84
+ class JobWrapper < Sidekiq::ActiveJob::Wrapper
85
+ end
86
+ end
87
+ end
88
+ end
data/lib/sidekiq/api.rb CHANGED
@@ -373,7 +373,7 @@ module Sidekiq
373
373
  def display_class
374
374
  # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
375
375
  @klass ||= self["display_class"] || begin
376
- if klass == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
376
+ if klass == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper" || klass == "Sidekiq::ActiveJob::Wrapper"
377
377
  job_class = @item["wrapped"] || args[0]
378
378
  if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob"
379
379
  # MailerClass#mailer_method
@@ -389,7 +389,7 @@ module Sidekiq
389
389
 
390
390
  def display_args
391
391
  # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
392
- @display_args ||= if klass == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
392
+ @display_args ||= if klass == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper" || klass == "Sidekiq::ActiveJob::Wrapper"
393
393
  job_args = self["wrapped"] ? deserialize_argument(args[0]["arguments"]) : []
394
394
  if (self["wrapped"] || args[0]) == "ActionMailer::DeliveryJob"
395
395
  # remove MailerClass, mailer_method and 'deliver_now'
@@ -668,6 +668,41 @@ module Sidekiq
668
668
  end
669
669
  end
670
670
 
671
+ def pop_each
672
+ Sidekiq.redis do |c|
673
+ size.times do
674
+ data, score = c.zpopmin(name, 1)&.first
675
+ break unless data
676
+ yield data, score
677
+ end
678
+ end
679
+ end
680
+
681
+ def retry_all
682
+ c = Sidekiq::Client.new
683
+ pop_each do |msg, _|
684
+ job = Sidekiq.load_json(msg)
685
+ # Manual retries should not count against the retry limit.
686
+ job["retry_count"] -= 1 if job["retry_count"]
687
+ c.push(job)
688
+ end
689
+ end
690
+
691
+ # Move all jobs from this Set to the Dead Set.
692
+ # See DeadSet#kill
693
+ def kill_all(notify_failure: false, ex: nil)
694
+ ds = DeadSet.new
695
+ opts = {notify_failure: notify_failure, ex: ex, trim: false}
696
+
697
+ begin
698
+ pop_each do |msg, _|
699
+ ds.kill(msg, opts)
700
+ end
701
+ ensure
702
+ ds.trim
703
+ end
704
+ end
705
+
671
706
  def each
672
707
  initial_size = @_size
673
708
  offset_size = 0
@@ -765,10 +800,6 @@ module Sidekiq
765
800
 
766
801
  ##
767
802
  # The set of scheduled jobs within Sidekiq.
768
- # Based on this, you can search/filter for jobs. Here's an
769
- # example where I'm selecting jobs based on some complex logic
770
- # and deleting them from the scheduled set.
771
- #
772
803
  # See the API wiki page for usage notes and examples.
773
804
  #
774
805
  class ScheduledSet < JobSet
@@ -779,26 +810,12 @@ module Sidekiq
779
810
 
780
811
  ##
781
812
  # The set of retries within Sidekiq.
782
- # Based on this, you can search/filter for jobs. Here's an
783
- # example where I'm selecting all jobs of a certain type
784
- # and deleting them from the retry queue.
785
- #
786
813
  # See the API wiki page for usage notes and examples.
787
814
  #
788
815
  class RetrySet < JobSet
789
816
  def initialize
790
817
  super("retry")
791
818
  end
792
-
793
- # Enqueues all jobs pending within the retry set.
794
- def retry_all
795
- each(&:retry) while size > 0
796
- end
797
-
798
- # Kills all jobs pending within the retry set.
799
- def kill_all
800
- each(&:kill) while size > 0
801
- end
802
819
  end
803
820
 
804
821
  ##
@@ -811,20 +828,31 @@ module Sidekiq
811
828
  super("dead")
812
829
  end
813
830
 
831
+ # Trim dead jobs which are over our storage limits
832
+ def trim
833
+ hash = Sidekiq.default_configuration
834
+ now = Time.now.to_f
835
+ Sidekiq.redis do |conn|
836
+ conn.multi do |transaction|
837
+ transaction.zremrangebyscore(name, "-inf", now - hash[:dead_timeout_in_seconds])
838
+ transaction.zremrangebyrank(name, 0, - hash[:dead_max_jobs])
839
+ end
840
+ end
841
+ end
842
+
814
843
  # Add the given job to the Dead set.
815
844
  # @param message [String] the job data as JSON
816
- # @option opts [Boolean] :notify_failure (true) Whether death handlers should be called
845
+ # @option opts [Boolean] :notify_failure (true) Whether death handlers should be called
846
+ # @option opts [Boolean] :trim (true) Whether Sidekiq should trim the structure to keep it within configuration
817
847
  # @option opts [Exception] :ex (RuntimeError) An exception to pass to the death handlers
818
848
  def kill(message, opts = {})
819
849
  now = Time.now.to_f
820
850
  Sidekiq.redis do |conn|
821
- conn.multi do |transaction|
822
- transaction.zadd(name, now.to_s, message)
823
- transaction.zremrangebyscore(name, "-inf", now - Sidekiq::Config::DEFAULTS[:dead_timeout_in_seconds])
824
- transaction.zremrangebyrank(name, 0, - Sidekiq::Config::DEFAULTS[:dead_max_jobs])
825
- end
851
+ conn.zadd(name, now.to_s, message)
826
852
  end
827
853
 
854
+ trim if opts[:trim] != false
855
+
828
856
  if opts[:notify_failure] != false
829
857
  job = Sidekiq.load_json(message)
830
858
  if opts[:ex]
@@ -839,11 +867,6 @@ module Sidekiq
839
867
  end
840
868
  true
841
869
  end
842
-
843
- # Enqueue all dead jobs
844
- def retry_all
845
- each(&:retry) while size > 0
846
- end
847
870
  end
848
871
 
849
872
  ##
@@ -40,9 +40,9 @@ module Sidekiq
40
40
 
41
41
  def fetcher
42
42
  @fetcher ||= begin
43
- inst = (config[:fetch_class] || Sidekiq::BasicFetch).new(self)
44
- inst.setup(config[:fetch_setup]) if inst.respond_to?(:setup)
45
- inst
43
+ instance = (config[:fetch_class] || Sidekiq::BasicFetch).new(self)
44
+ instance.setup(config[:fetch_setup]) if instance.respond_to?(:setup)
45
+ instance
46
46
  end
47
47
  end
48
48
 
@@ -58,6 +58,23 @@ module Sidekiq
58
58
  end
59
59
  end
60
60
 
61
+ # Cancel the IterableJob with the given JID.
62
+ # **NB: Cancellation is asynchronous.** Iteration checks every
63
+ # five seconds so this will not immediately stop the given job.
64
+ def cancel!(jid)
65
+ key = "it-#{jid}"
66
+ _, result, _ = Sidekiq.redis do |c|
67
+ c.pipelined do |p|
68
+ p.hsetnx(key, "cancelled", Time.now.to_i)
69
+ p.hget(key, "cancelled")
70
+ p.expire(key, Sidekiq::Job::Iterable::STATE_TTL)
71
+ # TODO When Redis 7.2 is required
72
+ # p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
73
+ end
74
+ end
75
+ result.to_i
76
+ end
77
+
61
78
  ##
62
79
  # The main method used to push a job to Redis. Accepts a number of options:
63
80
  #
@@ -185,7 +185,13 @@ module Sidekiq
185
185
 
186
186
  # register global singletons which can be accessed elsewhere
187
187
  def register(name, instance)
188
- @directory[name] = instance
188
+ # logger.debug("register[#{name}] = #{instance}")
189
+ # Sidekiq Enterprise lazy registers a few services so we
190
+ # can't lock down this hash completely.
191
+ hash = @directory.dup
192
+ hash[name] = instance
193
+ @directory = hash.freeze
194
+ instance
189
195
  end
190
196
 
191
197
  # find a singleton
@@ -193,10 +199,16 @@ module Sidekiq
193
199
  # JNDI is just a fancy name for a hash lookup
194
200
  @directory.fetch(name) do |key|
195
201
  return nil unless default_class
196
- @directory[key] = default_class.new(self)
202
+ register(key, default_class.new(self))
197
203
  end
198
204
  end
199
205
 
206
+ def freeze!
207
+ @directory.freeze
208
+ @options.freeze
209
+ true
210
+ end
211
+
200
212
  ##
201
213
  # Death handlers are called when all retries for a job have been exhausted and
202
214
  # the job dies. It's the notification to your application
@@ -30,6 +30,40 @@ module Sidekiq
30
30
  @_cursor = nil
31
31
  @_start_time = nil
32
32
  @_runtime = 0
33
+ @_args = nil
34
+ @_cancelled = nil
35
+ end
36
+
37
+ def arguments
38
+ @_args
39
+ end
40
+
41
+ # Three days is the longest period you generally need to wait for a retry to
42
+ # execute when using the default retry scheme. We don't want to "forget" the job
43
+ # is cancelled before it has a chance to execute and cancel itself.
44
+ CANCELLATION_PERIOD = (3 * 86_400).to_s
45
+
46
+ # Set a flag in Redis to mark this job as cancelled.
47
+ # Cancellation is asynchronous and is checked at the start of iteration
48
+ # and every 5 seconds thereafter as part of the recurring state flush.
49
+ def cancel!
50
+ return @_cancelled if cancelled?
51
+
52
+ key = "it-#{jid}"
53
+ _, result, _ = Sidekiq.redis do |c|
54
+ c.pipelined do |p|
55
+ p.hsetnx(key, "cancelled", Time.now.to_i)
56
+ p.hget(key, "cancelled")
57
+ # TODO When Redis 7.2 is required
58
+ # p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
59
+ p.expire(key, Sidekiq::Job::Iterable::STATE_TTL)
60
+ end
61
+ end
62
+ @_cancelled = result.to_i
63
+ end
64
+
65
+ def cancelled?
66
+ @_cancelled
33
67
  end
34
68
 
35
69
  # A hook to override that will be called when the job starts iterating.
@@ -91,13 +125,14 @@ module Sidekiq
91
125
  end
92
126
 
93
127
  # @api private
94
- def perform(*arguments)
128
+ def perform(*args)
129
+ @_args = args.dup.freeze
95
130
  fetch_previous_iteration_state
96
131
 
97
132
  @_executions += 1
98
133
  @_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
99
134
 
100
- enumerator = build_enumerator(*arguments, cursor: @_cursor)
135
+ enumerator = build_enumerator(*args, cursor: @_cursor)
101
136
  unless enumerator
102
137
  logger.info("'#build_enumerator' returned nil, skipping the job.")
103
138
  return
@@ -112,7 +147,7 @@ module Sidekiq
112
147
  end
113
148
 
114
149
  completed = catch(:abort) do
115
- iterate_with_enumerator(enumerator, arguments)
150
+ iterate_with_enumerator(enumerator, args)
116
151
  end
117
152
 
118
153
  on_stop
@@ -128,6 +163,10 @@ module Sidekiq
128
163
 
129
164
  private
130
165
 
166
+ def is_cancelled?
167
+ @_cancelled = Sidekiq.redis { |c| c.hget("it-#{jid}", "cancelled") }
168
+ end
169
+
131
170
  def fetch_previous_iteration_state
132
171
  state = Sidekiq.redis { |conn| conn.hgetall(iteration_key) }
133
172
 
@@ -144,6 +183,12 @@ module Sidekiq
144
183
  STATE_TTL = 30 * 24 * 60 * 60 # one month
145
184
 
146
185
  def iterate_with_enumerator(enumerator, arguments)
186
+ if is_cancelled?
187
+ logger.info { "Job cancelled" }
188
+ return true
189
+ end
190
+
191
+ time_limit = Sidekiq.default_configuration[:timeout]
147
192
  found_record = false
148
193
  state_flushed_at = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
149
194
 
@@ -153,14 +198,21 @@ module Sidekiq
153
198
 
154
199
  is_interrupted = interrupted?
155
200
  if ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - state_flushed_at >= STATE_FLUSH_INTERVAL || is_interrupted
156
- flush_state
201
+ _, _, cancelled = flush_state
157
202
  state_flushed_at = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
203
+ if cancelled == 1
204
+ @_cancelled = true
205
+ logger.info { "Job cancelled" }
206
+ return true
207
+ end
158
208
  end
159
209
 
160
210
  return false if is_interrupted
161
211
 
162
- around_iteration do
163
- each_iteration(object, *arguments)
212
+ verify_iteration_time(time_limit, object) do
213
+ around_iteration do
214
+ each_iteration(object, *arguments)
215
+ end
164
216
  end
165
217
  end
166
218
 
@@ -170,6 +222,16 @@ module Sidekiq
170
222
  @_runtime += (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @_start_time)
171
223
  end
172
224
 
225
+ def verify_iteration_time(time_limit, object)
226
+ start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
227
+ yield
228
+ finish = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
229
+ total = finish - start
230
+ if total > time_limit
231
+ logger.warn { "Iteration took longer (%.2f) than Sidekiq's shutdown timeout (%d) when processing `%s`. This can lead to job processing problems during deploys" % [total, time_limit, object] }
232
+ end
233
+ end
234
+
173
235
  def reenqueue_iteration_job
174
236
  flush_state
175
237
  logger.debug { "Interrupting job (cursor=#{@_cursor.inspect})" }
@@ -204,6 +266,7 @@ module Sidekiq
204
266
  conn.multi do |pipe|
205
267
  pipe.hset(key, state)
206
268
  pipe.expire(key, STATE_TTL)
269
+ pipe.hget(key, "cancelled")
207
270
  end
208
271
  end
209
272
  end
@@ -5,31 +5,21 @@ module Sidekiq
5
5
  def initialize(config)
6
6
  @config = config
7
7
  @logger = @config.logger
8
- end
9
-
10
- # If true we won't do any job logging out of the box.
11
- # The user is responsible for any logging.
12
- def skip_default_logging?
13
- @config[:skip_default_job_logging]
8
+ @skip = !!@config[:skip_default_job_logging]
14
9
  end
15
10
 
16
11
  def call(item, queue)
17
- return yield if skip_default_logging?
18
-
19
- begin
20
- start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
21
- @logger.info("start")
12
+ start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
13
+ @logger.info { "start" } unless @skip
22
14
 
23
- yield
24
-
25
- Sidekiq::Context.add(:elapsed, elapsed(start))
26
- @logger.info("done")
27
- rescue Exception
28
- Sidekiq::Context.add(:elapsed, elapsed(start))
29
- @logger.info("fail")
15
+ yield
30
16
 
31
- raise
32
- end
17
+ Sidekiq::Context.add(:elapsed, elapsed(start))
18
+ @logger.info { "done" } unless @skip
19
+ rescue Exception
20
+ Sidekiq::Context.add(:elapsed, elapsed(start))
21
+ @logger.info { "fail" } unless @skip
22
+ raise
33
23
  end
34
24
 
35
25
  def prepare(job_hash, &block)
@@ -36,8 +36,8 @@ module Sidekiq
36
36
  # has a heartbeat thread, caller can use `async_beat: false`
37
37
  # and instead have thread call Launcher#heartbeat every N seconds.
38
38
  def run(async_beat: true)
39
- Sidekiq.freeze!
40
39
  logger.debug { @config.merge!({}) }
40
+ Sidekiq.freeze!
41
41
  @thread = safe_thread("heartbeat", &method(:start_heartbeat)) if async_beat
42
42
  @poller.start
43
43
  @managers.each(&:start)
@@ -1,12 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "concurrent"
4
-
5
3
  module Sidekiq
6
4
  module Metrics
7
- # This is the only dependency on concurrent-ruby in Sidekiq but it's
8
- # mandatory for thread-safety until MRI supports atomic operations on values.
9
- Counter = ::Concurrent::AtomicFixnum
5
+ class Counter
6
+ def initialize
7
+ @value = 0
8
+ @lock = Mutex.new
9
+ end
10
+
11
+ def increment
12
+ @lock.synchronize { @value += 1 }
13
+ end
14
+
15
+ def value
16
+ @lock.synchronize { @value }
17
+ end
18
+ end
10
19
 
11
20
  # Implements space-efficient but statistically useful histogram storage.
12
21
  # A precise time histogram stores every time. Instead we break times into a set of
@@ -33,11 +33,26 @@ module Sidekiq
33
33
  attrs = strklass.constantize.attributes
34
34
  # Retries can push the job N times, we don't
35
35
  # want retries to reset cattr. #5692, #5090
36
- job[key] = attrs if attrs.any?
36
+ if attrs.any?
37
+ # Older rails has a bug that `CurrentAttributes#attributes` always returns
38
+ # the same hash instance. We need to dup it to avoid being accidentally mutated.
39
+ job[key] = if returns_same_object?
40
+ attrs.dup
41
+ else
42
+ attrs
43
+ end
44
+ end
37
45
  end
38
46
  end
39
47
  yield
40
48
  end
49
+
50
+ private
51
+
52
+ def returns_same_object?
53
+ ActiveSupport::VERSION::MAJOR < 8 ||
54
+ (ActiveSupport::VERSION::MAJOR == 8 && ActiveSupport::VERSION::MINOR == 0)
55
+ end
41
56
  end
42
57
 
43
58
  class Load
@@ -88,7 +103,7 @@ module Sidekiq
88
103
  cattrs = build_cattrs_hash(klass_or_array)
89
104
 
90
105
  config.client_middleware.add Save, cattrs
91
- config.server_middleware.add Load, cattrs
106
+ config.server_middleware.prepend Load, cattrs
92
107
  end
93
108
 
94
109
  private
@@ -138,11 +138,11 @@ module Sidekiq
138
138
  # Effectively this block denotes a "unit of work" to Rails.
139
139
  @reloader.call do
140
140
  klass = Object.const_get(job_hash["class"])
141
- inst = klass.new
142
- inst.jid = job_hash["jid"]
143
- inst._context = self
144
- @retrier.local(inst, jobstr, queue) do
145
- yield inst
141
+ instance = klass.new
142
+ instance.jid = job_hash["jid"]
143
+ instance._context = self
144
+ @retrier.local(instance, jobstr, queue) do
145
+ yield instance
146
146
  end
147
147
  end
148
148
  end
@@ -180,9 +180,9 @@ module Sidekiq
180
180
  ack = false
181
181
  Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
182
182
  Thread.handle_interrupt(ALLOW_SHUTDOWN_INTERRUPTS) do
183
- dispatch(job_hash, queue, jobstr) do |inst|
184
- config.server_middleware.invoke(inst, job_hash, queue) do
185
- execute_job(inst, job_hash["args"])
183
+ dispatch(job_hash, queue, jobstr) do |instance|
184
+ config.server_middleware.invoke(instance, job_hash, queue) do
185
+ execute_job(instance, job_hash["args"])
186
186
  end
187
187
  end
188
188
  ack = true
@@ -216,8 +216,8 @@ module Sidekiq
216
216
  end
217
217
  end
218
218
 
219
- def execute_job(inst, cloned_args)
220
- inst.perform(*cloned_args)
219
+ def execute_job(instance, cloned_args)
220
+ instance.perform(*cloned_args)
221
221
  end
222
222
 
223
223
  # Ruby doesn't provide atomic counters out of the box so we'll
data/lib/sidekiq/rails.rb CHANGED
@@ -39,6 +39,8 @@ module Sidekiq
39
39
  # end
40
40
  initializer "sidekiq.active_job_integration" do
41
41
  ActiveSupport.on_load(:active_job) do
42
+ require "active_job/queue_adapters/sidekiq_adapter"
43
+
42
44
  include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
43
45
  end
44
46
  end
@@ -283,11 +283,11 @@ module Sidekiq
283
283
  end
284
284
 
285
285
  def process_job(job)
286
- inst = new
287
- inst.jid = job["jid"]
288
- inst.bid = job["bid"] if inst.respond_to?(:bid=)
289
- Sidekiq::Testing.server_middleware.invoke(inst, job, job["queue"]) do
290
- execute_job(inst, job["args"])
286
+ instance = new
287
+ instance.jid = job["jid"]
288
+ instance.bid = job["bid"] if instance.respond_to?(:bid=)
289
+ Sidekiq::Testing.server_middleware.invoke(instance, job, job["queue"]) do
290
+ execute_job(instance, job["args"])
291
291
  end
292
292
  end
293
293
 
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "7.3.2"
4
+ VERSION = "7.3.5"
5
5
  MAJOR = 7
6
+
7
+ def self.gem_version
8
+ Gem::Version.new(VERSION)
9
+ end
6
10
  end
@@ -48,8 +48,12 @@ module Sidekiq
48
48
  if content.is_a? Symbol
49
49
  unless respond_to?(:"_erb_#{content}")
50
50
  views = options[:views] || Web.settings.views
51
- src = ERB.new(File.read("#{views}/#{content}.erb")).src
52
- WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1
51
+ filename = "#{views}/#{content}.erb"
52
+ src = ERB.new(File.read(filename)).src
53
+
54
+ # Need to use lineno less by 1 because erb generates a
55
+ # comment before the source code.
56
+ WebAction.class_eval <<-RUBY, filename, -1 # standard:disable Style/EvalWithLocation
53
57
  def _erb_#{content}
54
58
  #{src}
55
59
  end
@@ -331,12 +331,10 @@ module Sidekiq
331
331
  ########
332
332
  # Filtering
333
333
 
334
- get "/filter/metrics" do
335
- redirect "#{root_path}metrics"
336
- end
337
-
338
- post "/filter/metrics" do
334
+ route :get, :post, "/filter/metrics" do
339
335
  x = params[:substr]
336
+ return redirect "#{root_path}metrics" unless x && x != ""
337
+
340
338
  q = Sidekiq::Metrics::Query.new
341
339
  @period = h((params[:period] || "")[0..1])
342
340
  @periods = METRICS_PERIODS
@@ -346,15 +344,7 @@ module Sidekiq
346
344
  erb :metrics
347
345
  end
348
346
 
349
- get "/filter/retries" do
350
- x = params[:substr]
351
- return redirect "#{root_path}retries" unless x && x != ""
352
-
353
- @retries = search(Sidekiq::RetrySet.new, params[:substr])
354
- erb :retries
355
- end
356
-
357
- post "/filter/retries" do
347
+ route :get, :post, "/filter/retries" do
358
348
  x = params[:substr]
359
349
  return redirect "#{root_path}retries" unless x && x != ""
360
350
 
@@ -362,15 +352,7 @@ module Sidekiq
362
352
  erb :retries
363
353
  end
364
354
 
365
- get "/filter/scheduled" do
366
- x = params[:substr]
367
- return redirect "#{root_path}scheduled" unless x && x != ""
368
-
369
- @scheduled = search(Sidekiq::ScheduledSet.new, params[:substr])
370
- erb :scheduled
371
- end
372
-
373
- post "/filter/scheduled" do
355
+ route :get, :post, "/filter/scheduled" do
374
356
  x = params[:substr]
375
357
  return redirect "#{root_path}scheduled" unless x && x != ""
376
358
 
@@ -378,15 +360,7 @@ module Sidekiq
378
360
  erb :scheduled
379
361
  end
380
362
 
381
- get "/filter/dead" do
382
- x = params[:substr]
383
- return redirect "#{root_path}morgue" unless x && x != ""
384
-
385
- @dead = search(Sidekiq::DeadSet.new, params[:substr])
386
- erb :morgue
387
- end
388
-
389
- post "/filter/dead" do
363
+ route :get, :post, "/filter/dead" do
390
364
  x = params[:substr]
391
365
  return redirect "#{root_path}morgue" unless x && x != ""
392
366
 
@@ -39,10 +39,13 @@ module Sidekiq
39
39
  route(DELETE, path, &block)
40
40
  end
41
41
 
42
- def route(method, path, &block)
42
+ def route(*methods, path, &block)
43
43
  @routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []}
44
44
 
45
- @routes[method] << WebRoute.new(method, path, block)
45
+ methods.each do |method|
46
+ method = method.to_s.upcase
47
+ @routes[method] << WebRoute.new(method, path, block)
48
+ end
46
49
  end
47
50
 
48
51
  def match(env)
data/lib/sidekiq/web.rb CHANGED
@@ -213,7 +213,7 @@ module Sidekiq
213
213
  Sidekiq::WebApplication.helpers WebHelpers
214
214
  Sidekiq::WebApplication.helpers Sidekiq::Paginator
215
215
 
216
- Sidekiq::WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1
216
+ Sidekiq::WebAction.class_eval <<-RUBY, Web::LAYOUT, -1 # standard:disable Style/EvalWithLocation
217
217
  def _render
218
218
  #{ERB.new(File.read(Web::LAYOUT)).src}
219
219
  end
data/lib/sidekiq.rb CHANGED
@@ -102,18 +102,19 @@ module Sidekiq
102
102
  def self.freeze!
103
103
  @frozen = true
104
104
  @config_blocks = nil
105
+ default_configuration.freeze!
105
106
  end
106
107
 
107
108
  # Creates a Sidekiq::Config instance that is more tuned for embedding
108
109
  # within an arbitrary Ruby process. Notably it reduces concurrency by
109
110
  # default so there is less contention for CPU time with other threads.
110
111
  #
111
- # inst = Sidekiq.configure_embed do |config|
112
+ # instance = Sidekiq.configure_embed do |config|
112
113
  # config.queues = %w[critical default low]
113
114
  # end
114
- # inst.run
115
+ # instance.run
115
116
  # sleep 10
116
- # inst.stop
117
+ # instance.stop
117
118
  #
118
119
  # NB: it is really easy to overload a Ruby process with threads due to the GIL.
119
120
  # I do not recommend setting concurrency higher than 2-3.
data/sidekiq.gemspec CHANGED
@@ -26,6 +26,5 @@ Gem::Specification.new do |gem|
26
26
  gem.add_dependency "redis-client", ">= 0.22.2"
27
27
  gem.add_dependency "connection_pool", ">= 2.3.0"
28
28
  gem.add_dependency "rack", ">= 2.2.4"
29
- gem.add_dependency "concurrent-ruby", "< 2"
30
29
  gem.add_dependency "logger"
31
30
  end
@@ -650,6 +650,11 @@ div.interval-slider input {
650
650
  .redis-url {
651
651
  max-width: 160px;
652
652
  }
653
+
654
+ .navbar-fixed-bottom .nav {
655
+ margin-left: -15px;
656
+ margin-right: -15px;
657
+ }
653
658
  }
654
659
 
655
660
  @media (min-width: 992px) {
data/web/locales/en.yml CHANGED
@@ -95,7 +95,6 @@ en:
95
95
  TotalExecutionTime: Total Execution Time
96
96
  AvgExecutionTime: Average Execution Time
97
97
  Context: Context
98
- Bucket: Bucket
99
98
  NoJobMetricsFound: No recent job metrics were found
100
99
  Filter: Filter
101
100
  AnyJobContent: Any job content
data/web/locales/fr.yml CHANGED
@@ -95,5 +95,4 @@ fr:
95
95
  TotalExecutionTime: Temps d'exécution total
96
96
  AvgExecutionTime: Temps d'exécution moyen
97
97
  Context: Contexte
98
- Bucket: Bucket
99
98
  NoJobMetricsFound: Aucune statistique de tâche récente n'a été trouvée
data/web/locales/gd.yml CHANGED
@@ -95,5 +95,4 @@ gd:
95
95
  TotalExecutionTime: Ùine iomlan nan gnìomhan
96
96
  AvgExecutionTime: Ùine cuibheasach nan gnìomhan
97
97
  Context: Co-theacsa
98
- Bucket: Bucaid
99
98
  NoJobMetricsFound: Cha deach meatraigeachd o chionn goirid air obair a lorg
data/web/locales/it.yml CHANGED
@@ -6,44 +6,60 @@ it:
6
6
  AreYouSureDeleteJob: Sei sicuro di voler cancellare questo lavoro?
7
7
  AreYouSureDeleteQueue: Sei sicuro di voler cancellare la coda %{queue}?
8
8
  Arguments: Argomenti
9
+ BackToApp: Torna all'App
9
10
  Busy: Occupato
10
11
  Class: Classe
11
12
  Connections: Connessioni
13
+ CreatedAt: Creato il
12
14
  CurrentMessagesInQueue: Messaggi in <span class='title'>%{queue}</span>
13
15
  Dashboard: Dashboard
14
16
  Dead: Arrestato
15
17
  DeadJobs: Lavori arrestati
16
18
  Delete: Cancella
17
19
  DeleteAll: Cancella tutti
20
+ Deploy: Distribuire
18
21
  Enqueued: In coda
19
22
  Error: Errore
20
23
  ErrorBacktrace: Backtrace dell'errore
21
24
  ErrorClass: Classe dell'errore
22
25
  ErrorMessage: Messaggio di errore
26
+ ExecutionTime: Tempo di esecuzione
23
27
  Extras: Extra
24
28
  Failed: Fallito
25
29
  Failures: Fallimenti
30
+ Failure: Fallimento
26
31
  GoBack: ← Indietro
27
32
  History: Storia
28
33
  Job: Lavoro
29
34
  Jobs: Lavori
30
35
  Kill: Uccidere
36
+ KillAll: Uccidere tutti
31
37
  LastRetry: Ultimo tentativo
38
+ Latency: Latenza
32
39
  LivePoll: Live poll
33
40
  MemoryUsage: Memoria utilizzata
41
+ Name: Nome
34
42
  Namespace: Namespace
35
43
  NextRetry: Prossimo tentativo
36
44
  NoDeadJobsFound: Non ci sono lavori arrestati
37
45
  NoRetriesFound: Non sono stati trovati nuovi tentativi
38
46
  NoScheduledFound: Non ci sono lavori pianificati
47
+ NotYetEnqueued: Non ancora in coda
39
48
  OneMonth: 1 mese
40
49
  OneWeek: 1 settimana
41
50
  OriginallyFailed: Primo fallimento
51
+ Pause: Metti in pausa
52
+ Paused: In pausa
42
53
  PeakMemoryUsage: Memoria utilizzata (max.)
54
+ Plugins: Plugins
55
+ PollingInterval: Intervallo di polling
56
+ Process: Processo
43
57
  Processed: Processato
44
58
  Processes: Processi
45
59
  Queue: Coda
46
60
  Queues: Code
61
+ Quiet: Silenzia
62
+ QuietAll: Silenzia Tutti
47
63
  Realtime: Tempo reale
48
64
  Retries: Nuovi tentativi
49
65
  RetryAll: Riprova tutti
@@ -51,19 +67,34 @@ it:
51
67
  RetryNow: Riprova
52
68
  Scheduled: Pianificato
53
69
  ScheduledJobs: Lavori pianificati
70
+ Seconds: Secondi
54
71
  ShowAll: Mostra tutti
55
72
  SixMonths: 6 mesi
56
73
  Size: Dimensione
57
74
  Started: Iniziato
58
75
  Status: Stato
76
+ Stop: Ferma
77
+ StopAll: Ferma Tutti
59
78
  StopPolling: Ferma il polling
79
+ Success: Successo
80
+ Summary: Riepilogo
60
81
  Thread: Thread
61
- Threads: Thread
82
+ Threads: Threads
62
83
  ThreeMonths: 3 mesi
63
84
  Time: Ora
85
+ Unpause: Riattiva
64
86
  Uptime: Uptime (giorni)
87
+ Utilization: Utilizzo
65
88
  Version: Versione
66
89
  When: Quando
67
90
  Worker: Lavoratore
68
91
  active: attivo
69
92
  idle: inattivo
93
+ Metrics: Metriche
94
+ NoDataFound: Nessun dato trovato
95
+ TotalExecutionTime: Tempo totale di esecuzione
96
+ AvgExecutionTime: Tempo medio di esecuzione
97
+ Context: Contesto
98
+ NoJobMetricsFound: Metriche recenti di lavoro non trovate
99
+ Filter: Filtro
100
+ AnyJobContent: Qualsiasi contenuto di lavoro
data/web/locales/ja.yml CHANGED
@@ -87,5 +87,4 @@ ja:
87
87
  TotalExecutionTime: 合計実行時間
88
88
  AvgExecutionTime: 平均実行時間
89
89
  Context: コンテキスト
90
- Bucket: バケット
91
90
  NoJobMetricsFound: 直近のジョブメトリクスが見つかりませんでした
@@ -7,7 +7,6 @@
7
7
  Arguments: Argumentos
8
8
  AvgExecutionTime: Tempo médio de execução
9
9
  BackToApp: De volta ao aplicativo
10
- Bucket: Bucket
11
10
  Busy: Ocupados
12
11
  Class: Classe
13
12
  Connections: Conexões
@@ -93,4 +92,4 @@
93
92
  Utilization: Utilização
94
93
  Version: Versão
95
94
  When: Quando
96
- Worker: Trabalhador
95
+ Worker: Trabalhador
data/web/locales/tr.yml CHANGED
@@ -95,7 +95,6 @@ tr:
95
95
  TotalExecutionTime: Toplam Yürütme Süresi
96
96
  AvgExecutionTime: Ortalama Yürütme Süresi
97
97
  Context: Bağlam
98
- Bucket: Kova
99
98
  NoJobMetricsFound: Son iş metrikleri bulunamadı
100
99
  Filter: Filtre
101
100
  AnyJobContent: Herhangi bir iş içeriği
data/web/locales/uk.yml CHANGED
@@ -6,31 +6,39 @@ uk:
6
6
  AreYouSureDeleteJob: Ви впевнені у тому, що хочете видалити задачу?
7
7
  AreYouSureDeleteQueue: Ви впевнені у тому, що хочете видалити чергу %{queue}?
8
8
  Arguments: Аргументи
9
+ BackToApp: Назад
9
10
  Busy: Зайнятих
10
11
  Class: Клас
11
12
  Connections: З'єднань
13
+ CreatedAt: Створено
12
14
  CurrentMessagesInQueue: Поточні задачі у черзі <span class='title'>%{queue}</span>
13
15
  Dashboard: Панель керування
14
16
  Dead: Вбитих
15
17
  DeadJobs: Вбиті задачі
16
18
  Delete: Видалити
17
19
  DeleteAll: Видалити усі
20
+ Deploy: Деплой
18
21
  Enqueued: У черзі
19
22
  Error: Помилка
20
23
  ErrorBacktrace: Трасування помилки
21
24
  ErrorClass: Клас помилки
22
25
  ErrorMessage: Повідомлення про помилку
26
+ ExecutionTime: Час виконання
23
27
  Extras: Додатково
24
28
  Failed: Невдалих
25
29
  Failures: Невдачі
30
+ Failure: Невдача
26
31
  GoBack: ← Назад
27
32
  History: Історія
28
33
  Job: Задача
29
34
  Jobs: Задачі
30
- Kill: Вбиваємо
35
+ Kill: Вбити
36
+ KillAll: Вбити все
31
37
  LastRetry: Остання спроба
38
+ Latency: Затримка
32
39
  LivePoll: Постійне опитування
33
40
  MemoryUsage: Використання пам'яті
41
+ Name: Назва
34
42
  Namespace: Простір імен
35
43
  NextRetry: Наступна спроба
36
44
  NoDeadJobsFound: Вбитих задач не знайдено
@@ -40,10 +48,12 @@ uk:
40
48
  OneMonth: 1 місяць
41
49
  OneWeek: 1 тиждень
42
50
  OriginallyFailed: Перша невдала спроба
51
+ Pause: Призупинити
43
52
  Paused: Призупинено
44
53
  PeakMemoryUsage: Максимальне використання пам'яті
45
54
  Plugins: Плагіни
46
55
  PollingInterval: Інтервал опитування
56
+ Process: Процес
47
57
  Processed: Опрацьовано
48
58
  Processes: Процеси
49
59
  Queue: Черга
@@ -57,6 +67,7 @@ uk:
57
67
  RetryNow: Повторити зараз
58
68
  Scheduled: Заплановано
59
69
  ScheduledJobs: Заплановані задачі
70
+ Seconds: Секунди
60
71
  ShowAll: Відобразити усі
61
72
  SixMonths: 6 місяців
62
73
  Size: Розмір
@@ -65,13 +76,25 @@ uk:
65
76
  Stop: Зупинити
66
77
  StopAll: Зупинити усі
67
78
  StopPolling: Зупинити опитування
79
+ Success: Успіх
80
+ Summary: Підсумок
68
81
  Thread: Потік
69
82
  Threads: Потоки
70
83
  ThreeMonths: 3 місяці
71
84
  Time: Час
85
+ Unpause: Відновити
72
86
  Uptime: Днів безперебійної роботи
87
+ Utilization: Утилізація
73
88
  Version: Версія
74
89
  When: Коли
75
90
  Worker: Обробник
76
91
  active: активний
77
92
  idle: незайнятий
93
+ Metrics: Метрики
94
+ NoDataFound: Даних не знайдено
95
+ TotalExecutionTime: Загальний час виконання
96
+ AvgExecutionTime: Середній час виконання
97
+ Context: Контекст
98
+ NoJobMetricsFound: Недавніх метрик задачі не знайдено
99
+ Filter: Фільтр
100
+ AnyJobContent: Будь-який атрибут задачі
@@ -89,7 +89,6 @@ zh-cn: # <---- change this to your locale code
89
89
  TotalExecutionTime: 总执行时间
90
90
  AvgExecutionTime: 平均执行时间
91
91
  Context: 上下文
92
- Bucket: 桶
93
92
  NoJobMetricsFound: 无任务相关指标数据
94
93
  Success: 成功
95
94
  Failure: 失败
@@ -98,5 +98,4 @@ zh-tw: # <---- change this to your locale code
98
98
  TotalExecutionTime: 總執行時間
99
99
  AvgExecutionTime: 平均執行時間
100
100
  Context: 上下文
101
- Bucket: 桶
102
101
  NoJobMetricsFound: 找無工作相關計量資料
@@ -54,8 +54,8 @@
54
54
  <th><%= t('Name') %></th>
55
55
  <th><%= t('Success') %></th>
56
56
  <th><%= t('Failure') %></th>
57
- <th><%= t('TotalExecutionTime') %> (Seconds)</th>
58
- <th><%= t('AvgExecutionTime') %> (Seconds)</th>
57
+ <th><%= t('TotalExecutionTime') %> (<%= t('Seconds') %>)</th>
58
+ <th><%= t('AvgExecutionTime') %> (<%= t('Seconds') %>)</th>
59
59
  </tr>
60
60
  <% if job_results.any? %>
61
61
  <% job_results.each_with_index do |(kls, jr), i| %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.3.2
4
+ version: 7.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-04 00:00:00.000000000 Z
11
+ date: 2024-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 2.2.4
55
- - !ruby/object:Gem::Dependency
56
- name: concurrent-ruby
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "<"
60
- - !ruby/object:Gem::Version
61
- version: '2'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "<"
67
- - !ruby/object:Gem::Version
68
- version: '2'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: logger
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -96,6 +82,7 @@ files:
96
82
  - bin/sidekiq
97
83
  - bin/sidekiqload
98
84
  - bin/sidekiqmon
85
+ - lib/active_job/queue_adapters/sidekiq_adapter.rb
99
86
  - lib/generators/sidekiq/job_generator.rb
100
87
  - lib/generators/sidekiq/templates/job.rb.erb
101
88
  - lib/generators/sidekiq/templates/job_spec.rb.erb
@@ -245,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
245
232
  - !ruby/object:Gem::Version
246
233
  version: '0'
247
234
  requirements: []
248
- rubygems_version: 3.5.11
235
+ rubygems_version: 3.5.16
249
236
  signing_key:
250
237
  specification_version: 4
251
238
  summary: Simple, efficient background processing for Ruby