sidekiq 4.2.2 → 6.3.1

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.
Files changed (138) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +516 -0
  3. data/LICENSE +2 -2
  4. data/README.md +23 -36
  5. data/bin/sidekiq +26 -2
  6. data/bin/sidekiqload +28 -38
  7. data/bin/sidekiqmon +8 -0
  8. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
  9. data/lib/generators/sidekiq/templates/worker_test.rb.erb +2 -2
  10. data/lib/generators/sidekiq/worker_generator.rb +21 -13
  11. data/lib/sidekiq/api.rb +401 -243
  12. data/lib/sidekiq/cli.rb +228 -212
  13. data/lib/sidekiq/client.rb +76 -53
  14. data/lib/sidekiq/delay.rb +41 -0
  15. data/lib/sidekiq/exception_handler.rb +12 -16
  16. data/lib/sidekiq/extensions/action_mailer.rb +13 -22
  17. data/lib/sidekiq/extensions/active_record.rb +13 -10
  18. data/lib/sidekiq/extensions/class_methods.rb +14 -11
  19. data/lib/sidekiq/extensions/generic_proxy.rb +12 -4
  20. data/lib/sidekiq/fetch.rb +39 -31
  21. data/lib/sidekiq/job.rb +13 -0
  22. data/lib/sidekiq/job_logger.rb +63 -0
  23. data/lib/sidekiq/job_retry.rb +259 -0
  24. data/lib/sidekiq/launcher.rb +170 -71
  25. data/lib/sidekiq/logger.rb +166 -0
  26. data/lib/sidekiq/manager.rb +17 -20
  27. data/lib/sidekiq/middleware/chain.rb +20 -8
  28. data/lib/sidekiq/middleware/current_attributes.rb +52 -0
  29. data/lib/sidekiq/middleware/i18n.rb +5 -7
  30. data/lib/sidekiq/monitor.rb +133 -0
  31. data/lib/sidekiq/paginator.rb +18 -14
  32. data/lib/sidekiq/processor.rb +169 -78
  33. data/lib/sidekiq/rails.rb +41 -36
  34. data/lib/sidekiq/redis_connection.rb +65 -20
  35. data/lib/sidekiq/scheduled.rb +85 -34
  36. data/lib/sidekiq/sd_notify.rb +149 -0
  37. data/lib/sidekiq/systemd.rb +24 -0
  38. data/lib/sidekiq/testing/inline.rb +2 -1
  39. data/lib/sidekiq/testing.rb +52 -26
  40. data/lib/sidekiq/util.rb +48 -15
  41. data/lib/sidekiq/version.rb +2 -1
  42. data/lib/sidekiq/web/action.rb +15 -17
  43. data/lib/sidekiq/web/application.rb +114 -92
  44. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  45. data/lib/sidekiq/web/helpers.rb +151 -83
  46. data/lib/sidekiq/web/router.rb +27 -19
  47. data/lib/sidekiq/web.rb +85 -76
  48. data/lib/sidekiq/worker.rb +233 -43
  49. data/lib/sidekiq.rb +88 -64
  50. data/sidekiq.gemspec +24 -22
  51. data/web/assets/images/apple-touch-icon.png +0 -0
  52. data/web/assets/javascripts/application.js +86 -59
  53. data/web/assets/javascripts/dashboard.js +81 -85
  54. data/web/assets/stylesheets/application-dark.css +147 -0
  55. data/web/assets/stylesheets/application-rtl.css +242 -0
  56. data/web/assets/stylesheets/application.css +319 -141
  57. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  58. data/web/assets/stylesheets/bootstrap.css +2 -2
  59. data/web/locales/ar.yml +87 -0
  60. data/web/locales/de.yml +14 -2
  61. data/web/locales/en.yml +8 -1
  62. data/web/locales/es.yml +22 -5
  63. data/web/locales/fa.yml +80 -0
  64. data/web/locales/fr.yml +10 -3
  65. data/web/locales/he.yml +79 -0
  66. data/web/locales/ja.yml +12 -4
  67. data/web/locales/lt.yml +83 -0
  68. data/web/locales/pl.yml +4 -4
  69. data/web/locales/ru.yml +4 -0
  70. data/web/locales/ur.yml +80 -0
  71. data/web/locales/vi.yml +83 -0
  72. data/web/views/_footer.erb +5 -2
  73. data/web/views/_job_info.erb +4 -3
  74. data/web/views/_nav.erb +4 -18
  75. data/web/views/_paging.erb +1 -1
  76. data/web/views/_poll_link.erb +2 -5
  77. data/web/views/_summary.erb +7 -7
  78. data/web/views/busy.erb +60 -22
  79. data/web/views/dashboard.erb +23 -15
  80. data/web/views/dead.erb +3 -3
  81. data/web/views/layout.erb +14 -3
  82. data/web/views/morgue.erb +19 -12
  83. data/web/views/queue.erb +24 -14
  84. data/web/views/queues.erb +14 -4
  85. data/web/views/retries.erb +22 -13
  86. data/web/views/retry.erb +4 -4
  87. data/web/views/scheduled.erb +7 -4
  88. metadata +44 -194
  89. data/.github/contributing.md +0 -32
  90. data/.github/issue_template.md +0 -4
  91. data/.gitignore +0 -12
  92. data/.travis.yml +0 -12
  93. data/3.0-Upgrade.md +0 -70
  94. data/4.0-Upgrade.md +0 -53
  95. data/COMM-LICENSE +0 -95
  96. data/Ent-Changes.md +0 -146
  97. data/Gemfile +0 -29
  98. data/Pro-2.0-Upgrade.md +0 -138
  99. data/Pro-3.0-Upgrade.md +0 -44
  100. data/Pro-Changes.md +0 -570
  101. data/Rakefile +0 -9
  102. data/bin/sidekiqctl +0 -99
  103. data/code_of_conduct.md +0 -50
  104. data/lib/sidekiq/core_ext.rb +0 -106
  105. data/lib/sidekiq/logging.rb +0 -106
  106. data/lib/sidekiq/middleware/server/active_record.rb +0 -13
  107. data/lib/sidekiq/middleware/server/logging.rb +0 -40
  108. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
  109. data/test/config.yml +0 -9
  110. data/test/env_based_config.yml +0 -11
  111. data/test/fake_env.rb +0 -1
  112. data/test/fixtures/en.yml +0 -2
  113. data/test/helper.rb +0 -75
  114. data/test/test_actors.rb +0 -138
  115. data/test/test_api.rb +0 -528
  116. data/test/test_cli.rb +0 -418
  117. data/test/test_client.rb +0 -266
  118. data/test/test_exception_handler.rb +0 -56
  119. data/test/test_extensions.rb +0 -127
  120. data/test/test_fetch.rb +0 -50
  121. data/test/test_launcher.rb +0 -95
  122. data/test/test_logging.rb +0 -35
  123. data/test/test_manager.rb +0 -50
  124. data/test/test_middleware.rb +0 -158
  125. data/test/test_processor.rb +0 -201
  126. data/test/test_rails.rb +0 -22
  127. data/test/test_redis_connection.rb +0 -132
  128. data/test/test_retry.rb +0 -326
  129. data/test/test_retry_exhausted.rb +0 -149
  130. data/test/test_scheduled.rb +0 -115
  131. data/test/test_scheduling.rb +0 -50
  132. data/test/test_sidekiq.rb +0 -107
  133. data/test/test_testing.rb +0 -143
  134. data/test/test_testing_fake.rb +0 -357
  135. data/test/test_testing_inline.rb +0 -94
  136. data/test/test_util.rb +0 -13
  137. data/test/test_web.rb +0 -666
  138. data/test/test_web_helpers.rb +0 -54
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/util'
3
- require 'sidekiq/fetch'
4
- require 'thread'
5
- require 'concurrent/map'
6
- require 'concurrent/atomic/atomic_fixnum'
2
+
3
+ require "sidekiq/util"
4
+ require "sidekiq/fetch"
5
+ require "sidekiq/job_logger"
6
+ require "sidekiq/job_retry"
7
7
 
8
8
  module Sidekiq
9
9
  ##
@@ -23,31 +23,32 @@ module Sidekiq
23
23
  # to replace itself and exits.
24
24
  #
25
25
  class Processor
26
-
27
26
  include Util
28
27
 
29
28
  attr_reader :thread
30
29
  attr_reader :job
31
30
 
32
- def initialize(mgr)
31
+ def initialize(mgr, options)
33
32
  @mgr = mgr
34
33
  @down = false
35
34
  @done = false
36
35
  @job = nil
37
36
  @thread = nil
38
- @strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options)
39
- @reloader = Sidekiq.options[:reloader]
37
+ @strategy = options[:fetch]
38
+ @reloader = options[:reloader] || proc { |&block| block.call }
39
+ @job_logger = (options[:job_logger] || Sidekiq::JobLogger).new
40
+ @retrier = Sidekiq::JobRetry.new
40
41
  end
41
42
 
42
- def terminate(wait=false)
43
+ def terminate(wait = false)
43
44
  @done = true
44
- return if !@thread
45
+ return unless @thread
45
46
  @thread.value if wait
46
47
  end
47
48
 
48
- def kill(wait=false)
49
+ def kill(wait = false)
49
50
  @done = true
50
- return if !@thread
51
+ return unless @thread
51
52
  # unlike the other actors, terminate does not wait
52
53
  # for the thread to finish because we don't know how
53
54
  # long the job will take to finish. Instead we
@@ -64,16 +65,12 @@ module Sidekiq
64
65
  private unless $TESTING
65
66
 
66
67
  def run
67
- begin
68
- while !@done
69
- process_one
70
- end
71
- @mgr.processor_stopped(self)
72
- rescue Sidekiq::Shutdown
73
- @mgr.processor_stopped(self)
74
- rescue Exception => ex
75
- @mgr.processor_died(self, ex)
76
- end
68
+ process_one until @done
69
+ @mgr.processor_stopped(self)
70
+ rescue Sidekiq::Shutdown
71
+ @mgr.processor_stopped(self)
72
+ rescue Exception => ex
73
+ @mgr.processor_died(self, ex)
77
74
  end
78
75
 
79
76
  def process_one
@@ -83,14 +80,15 @@ module Sidekiq
83
80
  end
84
81
 
85
82
  def get_one
86
- begin
87
- work = @strategy.retrieve_work
88
- (logger.info { "Redis is online, #{Time.now - @down} sec downtime" }; @down = nil) if @down
89
- work
90
- rescue Sidekiq::Shutdown
91
- rescue => ex
92
- handle_fetch_exception(ex)
83
+ work = @strategy.retrieve_work
84
+ if @down
85
+ logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
86
+ @down = nil
93
87
  end
88
+ work
89
+ rescue Sidekiq::Shutdown
90
+ rescue => ex
91
+ handle_fetch_exception(ex)
94
92
  end
95
93
 
96
94
  def fetch
@@ -104,49 +102,92 @@ module Sidekiq
104
102
  end
105
103
 
106
104
  def handle_fetch_exception(ex)
107
- if !@down
108
- @down = Time.now
105
+ unless @down
106
+ @down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
109
107
  logger.error("Error fetching job: #{ex}")
110
- ex.backtrace.each do |bt|
111
- logger.error(bt)
112
- end
108
+ handle_exception(ex)
113
109
  end
114
110
  sleep(1)
115
111
  nil
116
112
  end
117
113
 
114
+ def dispatch(job_hash, queue, jobstr)
115
+ # since middleware can mutate the job hash
116
+ # we need to clone it to report the original
117
+ # job structure to the Web UI
118
+ # or to push back to redis when retrying.
119
+ # To avoid costly and, most of the time, useless cloning here,
120
+ # we pass original String of JSON to respected methods
121
+ # to re-parse it there if we need access to the original, untouched job
122
+
123
+ @job_logger.prepare(job_hash) do
124
+ @retrier.global(jobstr, queue) do
125
+ @job_logger.call(job_hash, queue) do
126
+ stats(jobstr, queue) do
127
+ # Rails 5 requires a Reloader to wrap code execution. In order to
128
+ # constantize the worker and instantiate an instance, we have to call
129
+ # the Reloader. It handles code loading, db connection management, etc.
130
+ # Effectively this block denotes a "unit of work" to Rails.
131
+ @reloader.call do
132
+ klass = constantize(job_hash["class"])
133
+ worker = klass.new
134
+ worker.jid = job_hash["jid"]
135
+ @retrier.local(worker, jobstr, queue) do
136
+ yield worker
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+
118
145
  def process(work)
119
146
  jobstr = work.job
120
147
  queue = work.queue_name
121
148
 
122
- @reloader.call do
123
- ack = false
124
- begin
125
- job = Sidekiq.load_json(jobstr)
126
- klass = job['class'.freeze].constantize
127
- worker = klass.new
128
- worker.jid = job['jid'.freeze]
129
-
130
- stats(worker, job, queue) do
131
- Sidekiq.server_middleware.invoke(worker, job, queue) do
132
- # Only ack if we either attempted to start this job or
133
- # successfully completed it. This prevents us from
134
- # losing jobs if a middleware raises an exception before yielding
135
- ack = true
136
- execute_job(worker, cloned(job['args'.freeze]))
137
- end
149
+ # Treat malformed JSON as a special case: job goes straight to the morgue.
150
+ job_hash = nil
151
+ begin
152
+ job_hash = Sidekiq.load_json(jobstr)
153
+ rescue => ex
154
+ handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
155
+ # we can't notify because the job isn't a valid hash payload.
156
+ DeadSet.new.kill(jobstr, notify_failure: false)
157
+ return work.acknowledge
158
+ end
159
+
160
+ ack = false
161
+ begin
162
+ dispatch(job_hash, queue, jobstr) do |worker|
163
+ Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
164
+ execute_job(worker, job_hash["args"])
165
+ end
166
+ end
167
+ ack = true
168
+ rescue Sidekiq::Shutdown
169
+ # Had to force kill this job because it didn't finish
170
+ # within the timeout. Don't acknowledge the work since
171
+ # we didn't properly finish it.
172
+ rescue Sidekiq::JobRetry::Handled => h
173
+ # this is the common case: job raised error and Sidekiq::JobRetry::Handled
174
+ # signals that we created a retry successfully. We can acknowlege the job.
175
+ ack = true
176
+ e = h.cause || h
177
+ handle_exception(e, {context: "Job raised exception", job: job_hash, jobstr: jobstr})
178
+ raise e
179
+ rescue Exception => ex
180
+ # Unexpected error! This is very bad and indicates an exception that got past
181
+ # the retry subsystem (e.g. network partition). We won't acknowledge the job
182
+ # so it can be rescued when using Sidekiq Pro.
183
+ handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
184
+ raise ex
185
+ ensure
186
+ if ack
187
+ # We don't want a shutdown signal to interrupt job acknowledgment.
188
+ Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
189
+ work.acknowledge
138
190
  end
139
- ack = true
140
- rescue Sidekiq::Shutdown
141
- # Had to force kill this job because it didn't finish
142
- # within the timeout. Don't acknowledge the work since
143
- # we didn't properly finish it.
144
- ack = false
145
- rescue Exception => ex
146
- handle_exception(ex, job || { :job => jobstr })
147
- raise
148
- ensure
149
- work.acknowledge if ack
150
191
  end
151
192
  end
152
193
  end
@@ -155,35 +196,85 @@ module Sidekiq
155
196
  worker.perform(*cloned_args)
156
197
  end
157
198
 
158
- def thread_identity
159
- @str ||= Thread.current.object_id.to_s(36)
199
+ # Ruby doesn't provide atomic counters out of the box so we'll
200
+ # implement something simple ourselves.
201
+ # https://bugs.ruby-lang.org/issues/14706
202
+ class Counter
203
+ def initialize
204
+ @value = 0
205
+ @lock = Mutex.new
206
+ end
207
+
208
+ def incr(amount = 1)
209
+ @lock.synchronize { @value += amount }
210
+ end
211
+
212
+ def reset
213
+ @lock.synchronize {
214
+ val = @value
215
+ @value = 0
216
+ val
217
+ }
218
+ end
160
219
  end
161
220
 
162
- WORKER_STATE = Concurrent::Map.new
163
- PROCESSED = Concurrent::AtomicFixnum.new
164
- FAILURE = Concurrent::AtomicFixnum.new
221
+ # jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
222
+ class SharedWorkerState
223
+ def initialize
224
+ @worker_state = {}
225
+ @lock = Mutex.new
226
+ end
227
+
228
+ def set(tid, hash)
229
+ @lock.synchronize { @worker_state[tid] = hash }
230
+ end
165
231
 
166
- def stats(worker, job, queue)
167
- tid = thread_identity
168
- WORKER_STATE[tid] = {:queue => queue, :payload => job, :run_at => Time.now.to_i }
232
+ def delete(tid)
233
+ @lock.synchronize { @worker_state.delete(tid) }
234
+ end
235
+
236
+ def dup
237
+ @lock.synchronize { @worker_state.dup }
238
+ end
239
+
240
+ def size
241
+ @lock.synchronize { @worker_state.size }
242
+ end
243
+
244
+ def clear
245
+ @lock.synchronize { @worker_state.clear }
246
+ end
247
+ end
248
+
249
+ PROCESSED = Counter.new
250
+ FAILURE = Counter.new
251
+ WORKER_STATE = SharedWorkerState.new
252
+
253
+ def stats(jobstr, queue)
254
+ WORKER_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
169
255
 
170
256
  begin
171
257
  yield
172
258
  rescue Exception
173
- FAILURE.increment
259
+ FAILURE.incr
174
260
  raise
175
261
  ensure
176
262
  WORKER_STATE.delete(tid)
177
- PROCESSED.increment
263
+ PROCESSED.incr
178
264
  end
179
265
  end
180
266
 
181
- # Deep clone the arguments passed to the worker so that if
182
- # the job fails, what is pushed back onto Redis hasn't
183
- # been mutated by the worker.
184
- def cloned(ary)
185
- Marshal.load(Marshal.dump(ary))
186
- end
267
+ def constantize(str)
268
+ return Object.const_get(str) unless str.include?("::")
187
269
 
270
+ names = str.split("::")
271
+ names.shift if names.empty? || names.first.empty?
272
+
273
+ names.inject(Object) do |constant, name|
274
+ # the false flag limits search for name to under the constant namespace
275
+ # which mimics Rails' behaviour
276
+ constant.const_get(name, false)
277
+ end
278
+ end
188
279
  end
189
280
  end
data/lib/sidekiq/rails.rb CHANGED
@@ -1,44 +1,11 @@
1
1
  # frozen_string_literal: true
2
- module Sidekiq
3
- def self.hook_rails!
4
- return if defined?(@delay_removed)
5
-
6
- ActiveSupport.on_load(:active_record) do
7
- include Sidekiq::Extensions::ActiveRecord
8
- end
9
-
10
- ActiveSupport.on_load(:action_mailer) do
11
- extend Sidekiq::Extensions::ActionMailer
12
- end
13
-
14
- Module.__send__(:include, Sidekiq::Extensions::Klass)
15
- end
16
2
 
17
- # Removes the generic aliases which MAY clash with names of already
18
- # created methods by other applications. The methods `sidekiq_delay`,
19
- # `sidekiq_delay_for` and `sidekiq_delay_until` can be used instead.
20
- def self.remove_delay!
21
- @delay_removed = true
22
-
23
- [Extensions::ActiveRecord,
24
- Extensions::ActionMailer,
25
- Extensions::Klass].each do |mod|
26
- mod.module_eval do
27
- remove_method :delay if respond_to?(:delay)
28
- remove_method :delay_for if respond_to?(:delay_for)
29
- remove_method :delay_until if respond_to?(:delay_until)
30
- end
31
- end
32
- end
3
+ require "sidekiq/worker"
33
4
 
5
+ module Sidekiq
34
6
  class Rails < ::Rails::Engine
35
- initializer 'sidekiq' do
36
- Sidekiq.hook_rails!
37
- end
38
-
39
7
  class Reloader
40
8
  def initialize(app = ::Rails.application)
41
- Sidekiq.logger.debug "Enabling Rails 5+ live code reloading, so hot!" unless app.config.cache_classes
42
9
  @app = app
43
10
  end
44
11
 
@@ -52,5 +19,43 @@ module Sidekiq
52
19
  "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
53
20
  end
54
21
  end
55
- end if defined?(::Rails)
22
+
23
+ # By including the Options module, we allow AJs to directly control sidekiq features
24
+ # via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
25
+ # AJ retries don't show up in the Sidekiq UI Retries tab, save any error data, can't be
26
+ # manually retried, don't automatically die, etc.
27
+ #
28
+ # class SomeJob < ActiveJob::Base
29
+ # queue_as :default
30
+ # sidekiq_options retry: 3, backtrace: 10
31
+ # def perform
32
+ # end
33
+ # end
34
+ initializer "sidekiq.active_job_integration" do
35
+ ActiveSupport.on_load(:active_job) do
36
+ include ::Sidekiq::Worker::Options unless respond_to?(:sidekiq_options)
37
+ end
38
+ end
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
+
51
+ # This hook happens after all initializers are run, just before returning
52
+ # from config/environment.rb back to sidekiq/cli.rb.
53
+ #
54
+ # None of this matters on the client-side, only within the Sidekiq process itself.
55
+ config.after_initialize do
56
+ Sidekiq.configure_server do |_|
57
+ Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
58
+ end
59
+ end
60
+ end
56
61
  end
@@ -1,26 +1,38 @@
1
1
  # frozen_string_literal: true
2
- require 'connection_pool'
3
- require 'redis'
4
- require 'uri'
2
+
3
+ require "connection_pool"
4
+ require "redis"
5
+ require "uri"
5
6
 
6
7
  module Sidekiq
7
8
  class RedisConnection
8
9
  class << self
10
+ def create(options = {})
11
+ symbolized_options = options.transform_keys(&:to_sym)
9
12
 
10
- def create(options={})
11
- options = options.symbolize_keys
12
-
13
- options[:url] ||= determine_redis_provider
13
+ if !symbolized_options[:url] && (u = determine_redis_provider)
14
+ symbolized_options[:url] = u
15
+ end
14
16
 
15
- size = options[:size] || (Sidekiq.server? ? (Sidekiq.options[:concurrency] + 5) : 5)
17
+ size = if symbolized_options[:size]
18
+ symbolized_options[:size]
19
+ elsif Sidekiq.server?
20
+ # Give ourselves plenty of connections. pool is lazy
21
+ # so we won't create them until we need them.
22
+ Sidekiq.options[:concurrency] + 5
23
+ elsif ENV["RAILS_MAX_THREADS"]
24
+ Integer(ENV["RAILS_MAX_THREADS"])
25
+ else
26
+ 5
27
+ end
16
28
 
17
29
  verify_sizing(size, Sidekiq.options[:concurrency]) if Sidekiq.server?
18
30
 
19
- pool_timeout = options[:pool_timeout] || 1
20
- log_info(options)
31
+ pool_timeout = symbolized_options[:pool_timeout] || 1
32
+ log_info(symbolized_options)
21
33
 
22
- ConnectionPool.new(:timeout => pool_timeout, :size => size) do
23
- build_client(options)
34
+ ConnectionPool.new(timeout: pool_timeout, size: size) do
35
+ build_client(symbolized_options)
24
36
  end
25
37
  end
26
38
 
@@ -35,7 +47,7 @@ module Sidekiq
35
47
  # - enterprise's leader election
36
48
  # - enterprise's cron support
37
49
  def verify_sizing(size, concurrency)
38
- raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but really needs to have at least #{concurrency + 2}" if size <= concurrency
50
+ raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size < (concurrency + 2)
39
51
  end
40
52
 
41
53
  def build_client(options)
@@ -44,8 +56,8 @@ module Sidekiq
44
56
  client = Redis.new client_opts(options)
45
57
  if namespace
46
58
  begin
47
- require 'redis/namespace'
48
- Redis::Namespace.new(namespace, :redis => client)
59
+ require "redis/namespace"
60
+ Redis::Namespace.new(namespace, redis: client)
49
61
  rescue LoadError
50
62
  Sidekiq.logger.error("Your Redis configuration uses the namespace '#{namespace}' but the redis-namespace gem is not included in the Gemfile." \
51
63
  "Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.")
@@ -67,15 +79,26 @@ module Sidekiq
67
79
  opts.delete(:network_timeout)
68
80
  end
69
81
 
70
- opts[:driver] = opts[:driver] || 'ruby'
82
+ opts[:driver] ||= Redis::Connection.drivers.last || "ruby"
83
+
84
+ # Issue #3303, redis-rb will silently retry an operation.
85
+ # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
86
+ # is performed twice but I believe this is much, much rarer
87
+ # than the reconnect silently fixing a problem; we keep it
88
+ # on by default.
89
+ opts[:reconnect_attempts] ||= 1
71
90
 
72
91
  opts
73
92
  end
74
93
 
75
94
  def log_info(options)
76
- # Don't log Redis AUTH password
77
95
  redacted = "REDACTED"
78
- scrubbed_options = options.dup
96
+
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]
101
+ scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
79
102
  if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
80
103
  uri.password = redacted
81
104
  scrubbed_options[:url] = uri.to_s
@@ -83,6 +106,9 @@ module Sidekiq
83
106
  if scrubbed_options[:password]
84
107
  scrubbed_options[:password] = redacted
85
108
  end
109
+ scrubbed_options[:sentinels]&.each do |sentinel|
110
+ sentinel[:password] = redacted if sentinel[:password]
111
+ end
86
112
  if Sidekiq.server?
87
113
  Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with redis options #{scrubbed_options}")
88
114
  else
@@ -91,9 +117,28 @@ module Sidekiq
91
117
  end
92
118
 
93
119
  def determine_redis_provider
94
- ENV[ENV['REDIS_PROVIDER'] || 'REDIS_URL']
95
- end
120
+ # If you have this in your environment:
121
+ # MY_REDIS_URL=redis://hostname.example.com:1238/4
122
+ # then set:
123
+ # REDIS_PROVIDER=MY_REDIS_URL
124
+ # and Sidekiq will find your custom URL variable with no custom
125
+ # initialization code at all.
126
+ #
127
+ p = ENV["REDIS_PROVIDER"]
128
+ if p && p =~ /:/
129
+ raise <<~EOM
130
+ REDIS_PROVIDER should be set to the name of the variable which contains the Redis URL, not a URL itself.
131
+ Platforms like Heroku will sell addons that publish a *_URL variable. You need to tell Sidekiq with REDIS_PROVIDER, e.g.:
132
+
133
+ REDISTOGO_URL=redis://somehost.example.com:6379/4
134
+ REDIS_PROVIDER=REDISTOGO_URL
135
+ EOM
136
+ end
96
137
 
138
+ ENV[
139
+ p || "REDIS_URL"
140
+ ]
141
+ end
97
142
  end
98
143
  end
99
144
  end