sidekiq 6.2.2 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b0da541124d097e05936cc860a93009ba170d714cbd8dbc760c1245818e8ed7
4
- data.tar.gz: 63995ccbb57fa16e4954cda4ab9610506b0a8ff35084492b520fc18608face35
3
+ metadata.gz: b72b92d3d281aaea2b4781cb7743d95a8c7f42708526383bf85fbed420b4b82c
4
+ data.tar.gz: 9546107658b47c8f68497730e2ee6689881c1388ba96db3f2415b0fda7b7f668
5
5
  SHA512:
6
- metadata.gz: f3b864db74530840437c0af43475ed0e12a876d6d2cbbd808ecc72722adddbd8d5548c9b777d4e34af5deddf94871567717518f70d787ea7f6a089f2f5b2f4ed
7
- data.tar.gz: 4569eed5baa10134e99a0b1404c213e3b35c08ec158e2fa8b006918298750919e8d34074aa10d3b4dfb621bb18cbe68be467d1e7822fb855012cff0f64dcdc7b
6
+ metadata.gz: 78f0af1f4ed41c5b4ad01636d9808d409d1cedc0bc6146be278412e87f9d826fad2cec9c89b31acbc6da9bab59bff44e826418991b10ce27b34631bcc3a00cf2
7
+ data.tar.gz: a586b9ec613cf3da47ecb77eb4e0f5d6e497f234369d958cdfa3d8c8c7418806383e022a9b08db4c3b5d08a1c2aac3aaecdf851744234f4c42055e1a7a6ebf6c
data/Changes.md CHANGED
@@ -1,6 +1,42 @@
1
1
  # Sidekiq Changes
2
2
 
3
- [Sidekiq Changes](https://github.com/mperham/sidekiq/blob/master/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/master/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/master/Ent-Changes.md)
3
+ [Sidekiq Changes](https://github.com/mperham/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/main/Ent-Changes.md)
4
+
5
+ HEAD
6
+ ---------
7
+
8
+ - **BREAK**: The Web UI has been refactored to remove jQuery. Any UI extensions
9
+ which use jQuery will break.
10
+ - **FEATURE**: Sidekiq.logger has been enhanced so any `Rails.logger`
11
+ output in jobs now shows up in the Sidekiq console. Remove any logger
12
+ hacks in your initializer and see if it Just Works™ now. [#5021]
13
+ - **FEATURE**: Add `Sidekiq::Job` alias for `Sidekiq::Worker`, to better
14
+ reflect industry standard terminology. You can now do this:
15
+ ```ruby
16
+ class MyJob
17
+ include Sidekiq::Job
18
+ sidekiq_options ...
19
+ def perform(args)
20
+ end
21
+ end
22
+ ```
23
+ - **FEATURE**: Support for serializing ActiveSupport::CurrentAttributes into each job. [#4982]
24
+ ```ruby
25
+ # config/initializers/sidekiq.rb
26
+ require "sidekiq/middleware/current_attributes"
27
+ Sidekiq::CurrentAttributes.persist(Myapp::Current) # Your AS::CurrentAttributes singleton
28
+ ```
29
+ - **FEATURE**: Add `Sidekiq::Worker.perform_bulk` for enqueuing jobs in bulk,
30
+ similar to `Sidekiq::Client.push_bulk` [#5042]
31
+ ```ruby
32
+ MyJob.perform_bulk([[1], [2], [3]])
33
+ ```
34
+ - Implement `queue_as`, `wait` and `wait_until` for ActiveJob compatibility [#5003]
35
+ - Scheduler now uses Lua to reduce Redis load and network roundtrips [#5044]
36
+ - Retry Redis operation if we get an `UNBLOCKED` Redis error [#4985]
37
+ - Run existing signal traps, if any, before running Sidekiq's trap [#4991]
38
+ - Fix fetch bug when using weighted queues which caused Sidekiq to stop
39
+ processing queues randomly [#5031]
4
40
 
5
41
  6.2.2
6
42
  ---------
data/README.md CHANGED
@@ -46,7 +46,7 @@ See the [Getting Started wiki page](https://github.com/mperham/sidekiq/wiki/Gett
46
46
  You can watch [this Youtube playlist](https://www.youtube.com/playlist?list=PLjeHh2LSCFrWGT5uVjUuFKAcrcj5kSai1) to learn all about
47
47
  Sidekiq and see its features in action. Here's the Web UI:
48
48
 
49
- ![Web UI](https://github.com/mperham/sidekiq/raw/master/examples/web-ui.png)
49
+ ![Web UI](https://github.com/mperham/sidekiq/raw/main/examples/web-ui.png)
50
50
 
51
51
 
52
52
  Want to Upgrade?
@@ -84,7 +84,7 @@ See the [Sidekiq support page](https://sidekiq.org/support.html) for details.
84
84
  License
85
85
  -----------------
86
86
 
87
- Please see [LICENSE](https://github.com/mperham/sidekiq/blob/master/LICENSE) for licensing details.
87
+ Please see [LICENSE](https://github.com/mperham/sidekiq/blob/main/LICENSE) for licensing details.
88
88
 
89
89
 
90
90
  Author
data/lib/sidekiq/api.rb CHANGED
@@ -113,6 +113,7 @@ module Sidekiq
113
113
 
114
114
  @stats[:workers_size] = workers_size
115
115
  @stats[:enqueued] = enqueued
116
+ @stats
116
117
  end
117
118
 
118
119
  def fetch_stats!
data/lib/sidekiq/cli.rb CHANGED
@@ -46,7 +46,15 @@ module Sidekiq
46
46
  # USR1 and USR2 don't work on the JVM
47
47
  sigs << "USR2" if Sidekiq.pro? && !jruby?
48
48
  sigs.each do |sig|
49
- trap sig do
49
+ old_handler = Signal.trap(sig) do
50
+ if old_handler.respond_to?(:call)
51
+ begin
52
+ old_handler.call
53
+ rescue Exception => exc
54
+ # signal handlers can't use Logger so puts only
55
+ puts ["Error in #{sig} handler", exc].inspect
56
+ end
57
+ end
50
58
  self_write.puts(sig)
51
59
  end
52
60
  rescue ArgumentError
@@ -396,7 +404,7 @@ module Sidekiq
396
404
  opts[:queues] ||= []
397
405
  opts[:strict] = true if opts[:strict].nil?
398
406
  raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
399
- [weight.to_i, 1].max.times { opts[:queues] << queue }
407
+ [weight.to_i, 1].max.times { opts[:queues] << queue.to_s }
400
408
  opts[:strict] = false if weight.to_i > 0
401
409
  end
402
410
 
@@ -95,7 +95,7 @@ module Sidekiq
95
95
  return [] if args.empty? # no jobs to push
96
96
 
97
97
  at = items.delete("at")
98
- raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all?(Numeric))
98
+ raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all? { |entry| entry.is_a?(Numeric) })
99
99
  raise ArgumentError, "Job 'at' Array must have same size as 'args' Array" if at.is_a?(Array) && at.size != args.size
100
100
 
101
101
  normed = normalize_item(items)
@@ -186,7 +186,7 @@ module Sidekiq
186
186
 
187
187
  def raw_push(payloads)
188
188
  @redis_pool.with do |conn|
189
- conn.multi do
189
+ conn.pipelined do
190
190
  atomic_push(conn, payloads)
191
191
  end
192
192
  end
data/lib/sidekiq/fetch.rb CHANGED
@@ -79,9 +79,10 @@ module Sidekiq
79
79
  if @strictly_ordered_queues
80
80
  @queues
81
81
  else
82
- queues = @queues.shuffle!.uniq
83
- queues << TIMEOUT
84
- queues
82
+ permute = @queues.shuffle
83
+ permute.uniq!
84
+ permute << TIMEOUT
85
+ permute
85
86
  end
86
87
  end
87
88
  end
data/lib/sidekiq/job.rb CHANGED
@@ -1,8 +1,13 @@
1
1
  require "sidekiq/worker"
2
2
 
3
3
  module Sidekiq
4
- # Sidekiq::Job is a new alias for Sidekiq::Worker, coming in 6.3.0.
5
- # You can opt into this by requiring 'sidekiq/job' in your initializer
6
- # and then using `include Sidekiq::Job` rather than `Sidekiq::Worker`.
4
+ # Sidekiq::Job is a new alias for Sidekiq::Worker as of Sidekiq 6.3.0.
5
+ # Use `include Sidekiq::Job` rather than `include Sidekiq::Worker`.
6
+ #
7
+ # The term "worker" is too generic and overly confusing, used in several
8
+ # different contexts meaning different things. Many people call a Sidekiq
9
+ # process a "worker". Some people call the thread that executes jobs a
10
+ # "worker". This change brings Sidekiq closer to ActiveJob where your job
11
+ # classes extend ApplicationJob.
7
12
  Job = Worker
8
13
  end
@@ -69,10 +69,12 @@ module Sidekiq
69
69
 
70
70
  private unless $TESTING
71
71
 
72
+ BEAT_PAUSE = 5
73
+
72
74
  def start_heartbeat
73
75
  loop do
74
76
  heartbeat
75
- sleep 5
77
+ sleep BEAT_PAUSE
76
78
  end
77
79
  Sidekiq.logger.info("Heartbeat stopping...")
78
80
  end
@@ -211,6 +213,8 @@ module Sidekiq
211
213
  Your Redis network connection is performing extremely poorly.
212
214
  Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
213
215
  Ensure Redis is running in the same AZ or datacenter as Sidekiq.
216
+ If these values are close to 100,000, that means your Sidekiq process may be
217
+ CPU overloaded; see https://github.com/mperham/sidekiq/discussions/5039
214
218
  EOM
215
219
  RTT_READINGS.reset
216
220
  end
@@ -0,0 +1,48 @@
1
+ require "active_support/current_attributes"
2
+
3
+ module Sidekiq
4
+ ##
5
+ # Automatically save and load any current attributes in the execution context
6
+ # so context attributes "flow" from Rails actions into any associated jobs.
7
+ # This can be useful for multi-tenancy, i18n locale, timezone, any implicit
8
+ # per-request attribute. See +ActiveSupport::CurrentAttributes+.
9
+ #
10
+ # @example
11
+ #
12
+ # # in your initializer
13
+ # require "sidekiq/middleware/current_attributes"
14
+ # Sidekiq::CurrentAttributes.persist(Myapp::Current)
15
+ #
16
+ module CurrentAttributes
17
+ class Save
18
+ def initialize(with:)
19
+ @klass = with
20
+ end
21
+
22
+ def call(_, job, _, _)
23
+ job["ctx"] = @klass.attributes
24
+ yield
25
+ end
26
+ end
27
+
28
+ class Load
29
+ def initialize(with:)
30
+ @klass = with
31
+ end
32
+
33
+ def call(_, job, _, &block)
34
+ @klass.set(job["ctx"], &block)
35
+ end
36
+ end
37
+
38
+ def self.persist(klass)
39
+ Sidekiq.configure_client do |config|
40
+ config.client_middleware.add Save, with: klass
41
+ end
42
+ Sidekiq.configure_server do |config|
43
+ config.client_middleware.add Save, with: klass
44
+ config.server_middleware.add Load, with: klass
45
+ end
46
+ end
47
+ end
48
+ end
data/lib/sidekiq/rails.rb CHANGED
@@ -37,6 +37,17 @@ module Sidekiq
37
37
  end
38
38
  end
39
39
 
40
+ initializer "sidekiq.rails_logger" do
41
+ Sidekiq.configure_server do |_|
42
+ # This is the integration code necessary so that if code uses `Rails.logger.info "Hello"`,
43
+ # it will appear in the Sidekiq console with all of the job context. See #5021 and
44
+ # https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84
45
+ unless ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
46
+ ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(::Sidekiq.logger))
47
+ end
48
+ end
49
+ end
50
+
40
51
  # This hook happens after all initializers are run, just before returning
41
52
  # from config/environment.rb back to sidekiq/cli.rb.
42
53
  #
@@ -94,12 +94,10 @@ module Sidekiq
94
94
  def log_info(options)
95
95
  redacted = "REDACTED"
96
96
 
97
- # deep clone so we can muck with these options all we want
98
- #
99
- # exclude SSL params from dump-and-load because some information isn't
100
- # safely dumpable in current Rubies
101
- keys = options.keys
102
- keys.delete(:ssl_params)
97
+ # Deep clone so we can muck with these options all we want and exclude
98
+ # params from dump-and-load that may contain objects that Marshal is
99
+ # unable to safely dump.
100
+ keys = options.keys - [:logger, :ssl_params]
103
101
  scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
104
102
  if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
105
103
  uri.password = redacted
@@ -9,29 +9,48 @@ module Sidekiq
9
9
  SETS = %w[retry schedule]
10
10
 
11
11
  class Enq
12
+ LUA_ZPOPBYSCORE = <<~LUA
13
+ local key, now = KEYS[1], ARGV[1]
14
+ local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1)
15
+ if jobs[1] then
16
+ redis.call("zrem", key, jobs[1])
17
+ return jobs[1]
18
+ end
19
+ LUA
20
+
21
+ def initialize
22
+ @lua_zpopbyscore_sha = nil
23
+ end
24
+
12
25
  def enqueue_jobs(now = Time.now.to_f.to_s, sorted_sets = SETS)
13
26
  # A job's "score" in Redis is the time at which it should be processed.
14
27
  # Just check Redis for the set of jobs with a timestamp before now.
15
28
  Sidekiq.redis do |conn|
16
29
  sorted_sets.each do |sorted_set|
17
- # Get next items in the queue with scores (time to execute) <= now.
18
- until (jobs = conn.zrangebyscore(sorted_set, "-inf", now, limit: [0, 100])).empty?
19
- # We need to go through the list one at a time to reduce the risk of something
20
- # going wrong between the time jobs are popped from the scheduled queue and when
21
- # they are pushed onto a work queue and losing the jobs.
22
- jobs.each do |job|
23
- # Pop item off the queue and add it to the work queue. If the job can't be popped from
24
- # the queue, it's because another process already popped it so we can move on to the
25
- # next one.
26
- if conn.zrem(sorted_set, job)
27
- Sidekiq::Client.push(Sidekiq.load_json(job))
28
- Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
29
- end
30
- end
30
+ # Get next item in the queue with score (time to execute) <= now.
31
+ # We need to go through the list one at a time to reduce the risk of something
32
+ # going wrong between the time jobs are popped from the scheduled queue and when
33
+ # they are pushed onto a work queue and losing the jobs.
34
+ while (job = zpopbyscore(conn, keys: [sorted_set], argv: [now]))
35
+ Sidekiq::Client.push(Sidekiq.load_json(job))
36
+ Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
31
37
  end
32
38
  end
33
39
  end
34
40
  end
41
+
42
+ private
43
+
44
+ def zpopbyscore(conn, keys: nil, argv: nil)
45
+ @lua_zpopbyscore_sha = conn.script(:load, LUA_ZPOPBYSCORE) if @lua_zpopbyscore_sha.nil?
46
+
47
+ conn.evalsha(@lua_zpopbyscore_sha, keys: keys, argv: argv)
48
+ rescue Redis::CommandError => e
49
+ raise unless e.message.start_with?("NOSCRIPT")
50
+
51
+ @lua_zpopbyscore_sha = nil
52
+ retry
53
+ end
35
54
  end
36
55
 
37
56
  ##
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "6.2.2"
4
+ VERSION = "6.3.0"
5
5
  end
@@ -91,8 +91,8 @@ module Sidekiq
91
91
 
92
92
  @count = (params["count"] || 25).to_i
93
93
  @queue = Sidekiq::Queue.new(@name)
94
- (@current_page, @total_size, @messages) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
95
- @messages = @messages.map { |msg| Sidekiq::JobRecord.new(msg, @name) }
94
+ (@current_page, @total_size, @jobs) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
95
+ @jobs = @jobs.map { |msg| Sidekiq::JobRecord.new(msg, @name) }
96
96
 
97
97
  erb(:queue)
98
98
  end
@@ -70,17 +70,6 @@ module Sidekiq
70
70
  @head_html.join if defined?(@head_html)
71
71
  end
72
72
 
73
- def poll_path
74
- if current_path != "" && params["poll"]
75
- path = root_path + current_path
76
- query_string = to_query_string(params.slice(*params.keys - %w[page poll]))
77
- path += "?#{query_string}" unless query_string.empty?
78
- path
79
- else
80
- ""
81
- end
82
- end
83
-
84
73
  def text_direction
85
74
  get_locale["TextDirection"] || "ltr"
86
75
  end
@@ -203,7 +192,7 @@ module Sidekiq
203
192
  [score.to_f, jid]
204
193
  end
205
194
 
206
- SAFE_QPARAMS = %w[page poll direction]
195
+ SAFE_QPARAMS = %w[page direction]
207
196
 
208
197
  # Merge options with current params, filter safe params, and stringify to query string
209
198
  def qparams(options)
@@ -9,6 +9,7 @@ module Sidekiq
9
9
  #
10
10
  # class HardWorker
11
11
  # include Sidekiq::Worker
12
+ # sidekiq_options queue: 'critical', retry: 5
12
13
  #
13
14
  # def perform(*args)
14
15
  # # do some work
@@ -20,6 +21,26 @@ module Sidekiq
20
21
  # HardWorker.perform_async(1, 2, 3)
21
22
  #
22
23
  # Note that perform_async is a class method, perform is an instance method.
24
+ #
25
+ # Sidekiq::Worker also includes several APIs to provide compatibility with
26
+ # ActiveJob.
27
+ #
28
+ # class SomeWorker
29
+ # include Sidekiq::Worker
30
+ # queue_as :critical
31
+ #
32
+ # def perform(...)
33
+ # end
34
+ # end
35
+ #
36
+ # SomeWorker.set(wait_until: 1.hour).perform_async(123)
37
+ #
38
+ # Note that arguments passed to the job must still obey Sidekiq's
39
+ # best practice for simple, JSON-native data types. Sidekiq will not
40
+ # implement ActiveJob's more complex argument serialization. For
41
+ # this reason, we don't implement `perform_later` as our call semantics
42
+ # are very different.
43
+ #
23
44
  module Worker
24
45
  ##
25
46
  # The Options module is extracted so we can include it in ActiveJob::Base
@@ -153,10 +174,16 @@ module Sidekiq
153
174
  def initialize(klass, opts)
154
175
  @klass = klass
155
176
  @opts = opts
177
+
178
+ # ActiveJob compatibility
179
+ interval = @opts.delete(:wait_until) || @opts.delete(:wait)
180
+ at(interval) if interval
156
181
  end
157
182
 
158
183
  def set(options)
184
+ interval = options.delete(:wait_until) || options.delete(:wait)
159
185
  @opts.merge!(options)
186
+ at(interval) if interval
160
187
  self
161
188
  end
162
189
 
@@ -164,19 +191,29 @@ module Sidekiq
164
191
  @klass.client_push(@opts.merge("args" => args, "class" => @klass))
165
192
  end
166
193
 
194
+ def perform_bulk(args, batch_size: 1_000)
195
+ args.each_slice(batch_size).flat_map do |slice|
196
+ Sidekiq::Client.push_bulk(@opts.merge("class" => @klass, "args" => slice))
197
+ end
198
+ end
199
+
167
200
  # +interval+ must be a timestamp, numeric or something that acts
168
201
  # numeric (like an activesupport time interval).
169
202
  def perform_in(interval, *args)
203
+ at(interval).perform_async(*args)
204
+ end
205
+ alias_method :perform_at, :perform_in
206
+
207
+ private
208
+
209
+ def at(interval)
170
210
  int = interval.to_f
171
211
  now = Time.now.to_f
172
212
  ts = (int < 1_000_000_000 ? now + int : int)
173
-
174
- payload = @opts.merge("class" => @klass, "args" => args)
175
213
  # Optimization to enqueue something now that is scheduled to go out now or in the past
176
- payload["at"] = ts if ts > now
177
- @klass.client_push(payload)
214
+ @opts["at"] = ts if ts > now
215
+ self
178
216
  end
179
- alias_method :perform_at, :perform_in
180
217
  end
181
218
 
182
219
  module ClassMethods
@@ -192,6 +229,10 @@ module Sidekiq
192
229
  raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at"
193
230
  end
194
231
 
232
+ def queue_as(q)
233
+ sidekiq_options("queue" => q.to_s)
234
+ end
235
+
195
236
  def set(options)
196
237
  Setter.new(self, options)
197
238
  end
@@ -200,6 +241,32 @@ module Sidekiq
200
241
  client_push("class" => self, "args" => args)
201
242
  end
202
243
 
244
+ ##
245
+ # Push a large number of jobs to Redis, while limiting the batch of
246
+ # each job payload to 1,000. This method helps cut down on the number
247
+ # of round trips to Redis, which can increase the performance of enqueueing
248
+ # large numbers of jobs.
249
+ #
250
+ # +items+ must be an Array of Arrays.
251
+ #
252
+ # For finer-grained control, use `Sidekiq::Client.push_bulk` directly.
253
+ #
254
+ # Example (3 Redis round trips):
255
+ #
256
+ # SomeWorker.perform_async(1)
257
+ # SomeWorker.perform_async(2)
258
+ # SomeWorker.perform_async(3)
259
+ #
260
+ # Would instead become (1 Redis round trip):
261
+ #
262
+ # SomeWorker.perform_bulk([[1], [2], [3]])
263
+ #
264
+ def perform_bulk(items, batch_size: 1_000)
265
+ items.each_slice(batch_size).flat_map do |slice|
266
+ Sidekiq::Client.push_bulk("class" => self, "args" => slice)
267
+ end
268
+ end
269
+
203
270
  # +interval+ must be a timestamp, numeric or something that acts
204
271
  # numeric (like an activesupport time interval).
205
272
  def perform_in(interval, *args)
data/lib/sidekiq.rb CHANGED
@@ -6,6 +6,7 @@ fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.5.0." i
6
6
  require "sidekiq/logger"
7
7
  require "sidekiq/client"
8
8
  require "sidekiq/worker"
9
+ require "sidekiq/job"
9
10
  require "sidekiq/redis_connection"
10
11
  require "sidekiq/delay"
11
12
 
@@ -100,7 +101,8 @@ module Sidekiq
100
101
  # 2550 Failover can cause the server to become a replica, need
101
102
  # to disconnect and reopen the socket to get back to the primary.
102
103
  # 4495 Use the same logic if we have a "Not enough replicas" error from the primary
103
- if retryable && ex.message =~ /READONLY|NOREPLICAS/
104
+ # 4985 Use the same logic when a blocking command is force-unblocked
105
+ if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
104
106
  conn.disconnect!
105
107
  retryable = false
106
108
  retry
data/sidekiq.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
18
18
  "homepage_uri" => "https://sidekiq.org",
19
19
  "bug_tracker_uri" => "https://github.com/mperham/sidekiq/issues",
20
20
  "documentation_uri" => "https://github.com/mperham/sidekiq/wiki",
21
- "changelog_uri" => "https://github.com/mperham/sidekiq/blob/master/Changes.md",
21
+ "changelog_uri" => "https://github.com/mperham/sidekiq/blob/main/Changes.md",
22
22
  "source_code_uri" => "https://github.com/mperham/sidekiq"
23
23
  }
24
24