sidekiq 5.1.1 → 7.1.2

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.

Files changed (149) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +627 -8
  3. data/LICENSE.txt +9 -0
  4. data/README.md +47 -50
  5. data/bin/sidekiq +22 -3
  6. data/bin/sidekiqload +213 -115
  7. data/bin/sidekiqmon +11 -0
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +566 -329
  13. data/lib/sidekiq/capsule.rb +127 -0
  14. data/lib/sidekiq/cli.rb +241 -256
  15. data/lib/sidekiq/client.rb +125 -102
  16. data/lib/sidekiq/component.rb +68 -0
  17. data/lib/sidekiq/config.rb +278 -0
  18. data/lib/sidekiq/deploy.rb +62 -0
  19. data/lib/sidekiq/embedded.rb +61 -0
  20. data/lib/sidekiq/fetch.rb +49 -42
  21. data/lib/sidekiq/job.rb +374 -0
  22. data/lib/sidekiq/job_logger.rb +36 -9
  23. data/lib/sidekiq/job_retry.rb +147 -98
  24. data/lib/sidekiq/job_util.rb +105 -0
  25. data/lib/sidekiq/launcher.rb +207 -103
  26. data/lib/sidekiq/logger.rb +131 -0
  27. data/lib/sidekiq/manager.rb +43 -47
  28. data/lib/sidekiq/metrics/query.rb +153 -0
  29. data/lib/sidekiq/metrics/shared.rb +95 -0
  30. data/lib/sidekiq/metrics/tracking.rb +136 -0
  31. data/lib/sidekiq/middleware/chain.rb +113 -56
  32. data/lib/sidekiq/middleware/current_attributes.rb +95 -0
  33. data/lib/sidekiq/middleware/i18n.rb +7 -7
  34. data/lib/sidekiq/middleware/modules.rb +21 -0
  35. data/lib/sidekiq/monitor.rb +146 -0
  36. data/lib/sidekiq/paginator.rb +28 -16
  37. data/lib/sidekiq/processor.rb +159 -107
  38. data/lib/sidekiq/rails.rb +54 -43
  39. data/lib/sidekiq/redis_client_adapter.rb +96 -0
  40. data/lib/sidekiq/redis_connection.rb +39 -81
  41. data/lib/sidekiq/ring_buffer.rb +29 -0
  42. data/lib/sidekiq/scheduled.rb +139 -48
  43. data/lib/sidekiq/sd_notify.rb +149 -0
  44. data/lib/sidekiq/systemd.rb +24 -0
  45. data/lib/sidekiq/testing/inline.rb +6 -5
  46. data/lib/sidekiq/testing.rb +70 -88
  47. data/lib/sidekiq/transaction_aware_client.rb +44 -0
  48. data/lib/sidekiq/version.rb +3 -1
  49. data/lib/sidekiq/web/action.rb +15 -11
  50. data/lib/sidekiq/web/application.rb +143 -77
  51. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  52. data/lib/sidekiq/web/helpers.rb +144 -106
  53. data/lib/sidekiq/web/router.rb +23 -19
  54. data/lib/sidekiq/web.rb +60 -111
  55. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  56. data/lib/sidekiq.rb +94 -183
  57. data/sidekiq.gemspec +25 -23
  58. data/web/assets/images/apple-touch-icon.png +0 -0
  59. data/web/assets/javascripts/application.js +130 -61
  60. data/web/assets/javascripts/base-charts.js +106 -0
  61. data/web/assets/javascripts/chart.min.js +13 -0
  62. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  63. data/web/assets/javascripts/dashboard-charts.js +166 -0
  64. data/web/assets/javascripts/dashboard.js +36 -282
  65. data/web/assets/javascripts/metrics.js +264 -0
  66. data/web/assets/stylesheets/application-dark.css +147 -0
  67. data/web/assets/stylesheets/application-rtl.css +2 -95
  68. data/web/assets/stylesheets/application.css +134 -521
  69. data/web/assets/stylesheets/bootstrap.css +2 -2
  70. data/web/locales/ar.yml +71 -64
  71. data/web/locales/cs.yml +62 -62
  72. data/web/locales/da.yml +60 -53
  73. data/web/locales/de.yml +65 -53
  74. data/web/locales/el.yml +43 -24
  75. data/web/locales/en.yml +84 -65
  76. data/web/locales/es.yml +70 -54
  77. data/web/locales/fa.yml +65 -65
  78. data/web/locales/fr.yml +83 -62
  79. data/web/locales/gd.yml +99 -0
  80. data/web/locales/he.yml +65 -64
  81. data/web/locales/hi.yml +59 -59
  82. data/web/locales/it.yml +53 -53
  83. data/web/locales/ja.yml +75 -64
  84. data/web/locales/ko.yml +52 -52
  85. data/web/locales/lt.yml +83 -0
  86. data/web/locales/nb.yml +61 -61
  87. data/web/locales/nl.yml +52 -52
  88. data/web/locales/pl.yml +45 -45
  89. data/web/locales/pt-br.yml +63 -55
  90. data/web/locales/pt.yml +51 -51
  91. data/web/locales/ru.yml +68 -63
  92. data/web/locales/sv.yml +53 -53
  93. data/web/locales/ta.yml +60 -60
  94. data/web/locales/uk.yml +62 -61
  95. data/web/locales/ur.yml +64 -64
  96. data/web/locales/vi.yml +83 -0
  97. data/web/locales/zh-cn.yml +43 -16
  98. data/web/locales/zh-tw.yml +42 -8
  99. data/web/views/_footer.erb +8 -2
  100. data/web/views/_job_info.erb +21 -4
  101. data/web/views/_metrics_period_select.erb +12 -0
  102. data/web/views/_nav.erb +4 -18
  103. data/web/views/_paging.erb +2 -0
  104. data/web/views/_poll_link.erb +3 -6
  105. data/web/views/_summary.erb +7 -7
  106. data/web/views/busy.erb +75 -25
  107. data/web/views/dashboard.erb +58 -18
  108. data/web/views/dead.erb +3 -3
  109. data/web/views/layout.erb +4 -2
  110. data/web/views/metrics.erb +82 -0
  111. data/web/views/metrics_for_job.erb +68 -0
  112. data/web/views/morgue.erb +14 -15
  113. data/web/views/queue.erb +33 -23
  114. data/web/views/queues.erb +14 -4
  115. data/web/views/retries.erb +19 -16
  116. data/web/views/retry.erb +3 -3
  117. data/web/views/scheduled.erb +17 -15
  118. metadata +71 -140
  119. data/.github/contributing.md +0 -32
  120. data/.github/issue_template.md +0 -11
  121. data/.gitignore +0 -13
  122. data/.travis.yml +0 -14
  123. data/3.0-Upgrade.md +0 -70
  124. data/4.0-Upgrade.md +0 -53
  125. data/5.0-Upgrade.md +0 -56
  126. data/COMM-LICENSE +0 -95
  127. data/Ent-Changes.md +0 -210
  128. data/Gemfile +0 -8
  129. data/LICENSE +0 -9
  130. data/Pro-2.0-Upgrade.md +0 -138
  131. data/Pro-3.0-Upgrade.md +0 -44
  132. data/Pro-4.0-Upgrade.md +0 -35
  133. data/Pro-Changes.md +0 -716
  134. data/Rakefile +0 -8
  135. data/bin/sidekiqctl +0 -99
  136. data/code_of_conduct.md +0 -50
  137. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  138. data/lib/sidekiq/core_ext.rb +0 -1
  139. data/lib/sidekiq/delay.rb +0 -41
  140. data/lib/sidekiq/exception_handler.rb +0 -29
  141. data/lib/sidekiq/extensions/action_mailer.rb +0 -57
  142. data/lib/sidekiq/extensions/active_record.rb +0 -40
  143. data/lib/sidekiq/extensions/class_methods.rb +0 -40
  144. data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
  145. data/lib/sidekiq/logging.rb +0 -122
  146. data/lib/sidekiq/middleware/server/active_record.rb +0 -22
  147. data/lib/sidekiq/middleware/server/active_record_cache.rb +0 -11
  148. data/lib/sidekiq/util.rb +0 -66
  149. data/lib/sidekiq/worker.rb +0 -204
@@ -1,11 +1,8 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/util'
3
- require 'sidekiq/fetch'
4
- require 'sidekiq/job_logger'
5
- require 'sidekiq/job_retry'
6
- require 'thread'
7
- require 'concurrent/map'
8
- require 'concurrent/atomic/atomic_fixnum'
2
+
3
+ require "sidekiq/fetch"
4
+ require "sidekiq/job_logger"
5
+ require "sidekiq/job_retry"
9
6
 
10
7
  module Sidekiq
11
8
  ##
@@ -13,45 +10,45 @@ module Sidekiq
13
10
  #
14
11
  # 1. fetches a job from Redis
15
12
  # 2. executes the job
16
- # a. instantiate the Worker
13
+ # a. instantiate the job class
17
14
  # b. run the middleware chain
18
15
  # c. call #perform
19
16
  #
20
- # A Processor can exit due to shutdown (processor_stopped)
21
- # or due to an error during job execution (processor_died)
17
+ # A Processor can exit due to shutdown or due to
18
+ # an error during job execution.
22
19
  #
23
20
  # If an error occurs in the job execution, the
24
21
  # Processor calls the Manager to create a new one
25
22
  # to replace itself and exits.
26
23
  #
27
24
  class Processor
28
-
29
- include Util
25
+ include Sidekiq::Component
30
26
 
31
27
  attr_reader :thread
32
28
  attr_reader :job
29
+ attr_reader :capsule
33
30
 
34
- def initialize(mgr)
35
- @mgr = mgr
31
+ def initialize(capsule, &block)
32
+ @config = @capsule = capsule
33
+ @callback = block
36
34
  @down = false
37
35
  @done = false
38
36
  @job = nil
39
37
  @thread = nil
40
- @strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options)
41
- @reloader = Sidekiq.options[:reloader]
42
- @logging = (mgr.options[:job_logger] || Sidekiq::JobLogger).new
43
- @retrier = Sidekiq::JobRetry.new
38
+ @reloader = Sidekiq.default_configuration[:reloader]
39
+ @job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(logger)
40
+ @retrier = Sidekiq::JobRetry.new(capsule)
44
41
  end
45
42
 
46
- def terminate(wait=false)
43
+ def terminate(wait = false)
47
44
  @done = true
48
- return if !@thread
45
+ return unless @thread
49
46
  @thread.value if wait
50
47
  end
51
48
 
52
- def kill(wait=false)
49
+ def kill(wait = false)
53
50
  @done = true
54
- return if !@thread
51
+ return unless @thread
55
52
  # unlike the other actors, terminate does not wait
56
53
  # for the thread to finish because we don't know how
57
54
  # long the job will take to finish. Instead we
@@ -62,39 +59,40 @@ module Sidekiq
62
59
  end
63
60
 
64
61
  def start
65
- @thread ||= safe_thread("processor", &method(:run))
62
+ @thread ||= safe_thread("#{config.name}/processor", &method(:run))
66
63
  end
67
64
 
68
65
  private unless $TESTING
69
66
 
70
67
  def run
71
- begin
72
- while !@done
73
- process_one
74
- end
75
- @mgr.processor_stopped(self)
76
- rescue Sidekiq::Shutdown
77
- @mgr.processor_stopped(self)
78
- rescue Exception => ex
79
- @mgr.processor_died(self, ex)
80
- end
68
+ # By setting this thread-local, Sidekiq.redis will access +Sidekiq::Capsule#redis_pool+
69
+ # instead of the global pool in +Sidekiq::Config#redis_pool+.
70
+ Thread.current[:sidekiq_capsule] = @capsule
71
+
72
+ process_one until @done
73
+ @callback.call(self)
74
+ rescue Sidekiq::Shutdown
75
+ @callback.call(self)
76
+ rescue Exception => ex
77
+ @callback.call(self, ex)
81
78
  end
82
79
 
83
- def process_one
80
+ def process_one(&block)
84
81
  @job = fetch
85
82
  process(@job) if @job
86
83
  @job = nil
87
84
  end
88
85
 
89
86
  def get_one
90
- begin
91
- work = @strategy.retrieve_work
92
- (logger.info { "Redis is online, #{Time.now - @down} sec downtime" }; @down = nil) if @down
93
- work
94
- rescue Sidekiq::Shutdown
95
- rescue => ex
96
- handle_fetch_exception(ex)
87
+ uow = capsule.fetcher.retrieve_work
88
+ if @down
89
+ logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
90
+ @down = nil
97
91
  end
92
+ uow
93
+ rescue Sidekiq::Shutdown
94
+ rescue => ex
95
+ handle_fetch_exception(ex)
98
96
  end
99
97
 
100
98
  def fetch
@@ -108,8 +106,8 @@ module Sidekiq
108
106
  end
109
107
 
110
108
  def handle_fetch_exception(ex)
111
- if !@down
112
- @down = Time.now
109
+ unless @down
110
+ @down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
113
111
  logger.error("Error fetching job: #{ex}")
114
112
  handle_exception(ex)
115
113
  end
@@ -117,26 +115,29 @@ module Sidekiq
117
115
  nil
118
116
  end
119
117
 
120
- def dispatch(job_hash, queue)
118
+ def dispatch(job_hash, queue, jobstr)
121
119
  # since middleware can mutate the job hash
122
- # we clone here so we report the original
120
+ # we need to clone it to report the original
123
121
  # job structure to the Web UI
124
- pristine = cloned(job_hash)
122
+ # or to push back to redis when retrying.
123
+ # To avoid costly and, most of the time, useless cloning here,
124
+ # we pass original String of JSON to respected methods
125
+ # to re-parse it there if we need access to the original, untouched job
125
126
 
126
- Sidekiq::Logging.with_job_hash_context(job_hash) do
127
- @retrier.global(pristine, queue) do
128
- @logging.call(job_hash, queue) do
129
- stats(pristine, queue) do
127
+ @job_logger.prepare(job_hash) do
128
+ @retrier.global(jobstr, queue) do
129
+ @job_logger.call(job_hash, queue) do
130
+ stats(jobstr, queue) do
130
131
  # Rails 5 requires a Reloader to wrap code execution. In order to
131
132
  # constantize the worker and instantiate an instance, we have to call
132
133
  # the Reloader. It handles code loading, db connection management, etc.
133
134
  # Effectively this block denotes a "unit of work" to Rails.
134
135
  @reloader.call do
135
- klass = constantize(job_hash['class'.freeze])
136
- worker = klass.new
137
- worker.jid = job_hash['jid'.freeze]
138
- @retrier.local(worker, pristine, queue) do
139
- yield worker
136
+ klass = Object.const_get(job_hash["class"])
137
+ inst = klass.new
138
+ inst.jid = job_hash["jid"]
139
+ @retrier.local(inst, jobstr, queue) do
140
+ yield inst
140
141
  end
141
142
  end
142
143
  end
@@ -145,84 +146,135 @@ module Sidekiq
145
146
  end
146
147
  end
147
148
 
148
- def process(work)
149
- jobstr = work.job
150
- queue = work.queue_name
149
+ IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :never}
150
+ private_constant :IGNORE_SHUTDOWN_INTERRUPTS
151
151
 
152
- ack = false
152
+ def process(uow)
153
+ jobstr = uow.job
154
+ queue = uow.queue_name
155
+
156
+ # Treat malformed JSON as a special case: job goes straight to the morgue.
157
+ job_hash = nil
153
158
  begin
154
- # Treat malformed JSON as a special case: job goes straight to the morgue.
155
- job_hash = nil
156
- begin
157
- job_hash = Sidekiq.load_json(jobstr)
158
- rescue => ex
159
- handle_exception(ex, { :context => "Invalid JSON for job", :jobstr => jobstr })
160
- # we can't notify because the job isn't a valid hash payload.
161
- DeadSet.new.kill(jobstr, notify_failure: false)
162
- ack = true
163
- raise
159
+ job_hash = Sidekiq.load_json(jobstr)
160
+ rescue => ex
161
+ handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
162
+ now = Time.now.to_f
163
+ redis do |conn|
164
+ conn.multi do |xa|
165
+ xa.zadd("dead", now.to_s, jobstr)
166
+ xa.zremrangebyscore("dead", "-inf", now - @capsule.config[:dead_timeout_in_seconds])
167
+ xa.zremrangebyrank("dead", 0, - @capsule.config[:dead_max_jobs])
168
+ end
164
169
  end
170
+ return uow.acknowledge
171
+ end
165
172
 
166
- ack = true
167
- dispatch(job_hash, queue) do |worker|
168
- Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
169
- execute_job(worker, cloned(job_hash['args'.freeze]))
173
+ ack = false
174
+ begin
175
+ dispatch(job_hash, queue, jobstr) do |inst|
176
+ config.server_middleware.invoke(inst, job_hash, queue) do
177
+ execute_job(inst, job_hash["args"])
170
178
  end
171
179
  end
180
+ ack = true
172
181
  rescue Sidekiq::Shutdown
173
182
  # Had to force kill this job because it didn't finish
174
183
  # within the timeout. Don't acknowledge the work since
175
184
  # we didn't properly finish it.
176
- ack = false
177
- rescue Exception => ex
178
- e = ex.is_a?(::Sidekiq::JobRetry::Skip) && ex.cause ? ex.cause : ex
179
- handle_exception(e, { :context => "Job raised exception", :job => job_hash, :jobstr => jobstr })
185
+ rescue Sidekiq::JobRetry::Handled => h
186
+ # this is the common case: job raised error and Sidekiq::JobRetry::Handled
187
+ # signals that we created a retry successfully. We can acknowlege the job.
188
+ ack = true
189
+ e = h.cause || h
190
+ handle_exception(e, {context: "Job raised exception", job: job_hash})
180
191
  raise e
192
+ rescue Exception => ex
193
+ # Unexpected error! This is very bad and indicates an exception that got past
194
+ # the retry subsystem (e.g. network partition). We won't acknowledge the job
195
+ # so it can be rescued when using Sidekiq Pro.
196
+ handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
197
+ raise ex
181
198
  ensure
182
- work.acknowledge if ack
199
+ if ack
200
+ # We don't want a shutdown signal to interrupt job acknowledgment.
201
+ Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
202
+ uow.acknowledge
203
+ end
204
+ end
183
205
  end
184
206
  end
185
207
 
186
- def execute_job(worker, cloned_args)
187
- worker.perform(*cloned_args)
208
+ def execute_job(inst, cloned_args)
209
+ inst.perform(*cloned_args)
188
210
  end
189
211
 
190
- WORKER_STATE = Concurrent::Map.new
191
- PROCESSED = Concurrent::AtomicFixnum.new
192
- FAILURE = Concurrent::AtomicFixnum.new
212
+ # Ruby doesn't provide atomic counters out of the box so we'll
213
+ # implement something simple ourselves.
214
+ # https://bugs.ruby-lang.org/issues/14706
215
+ class Counter
216
+ def initialize
217
+ @value = 0
218
+ @lock = Mutex.new
219
+ end
193
220
 
194
- def stats(job_hash, queue)
195
- tid = Sidekiq::Logging.tid
196
- WORKER_STATE[tid] = {:queue => queue, :payload => job_hash, :run_at => Time.now.to_i }
221
+ def incr(amount = 1)
222
+ @lock.synchronize { @value += amount }
223
+ end
197
224
 
198
- begin
199
- yield
200
- rescue Exception
201
- FAILURE.increment
202
- raise
203
- ensure
204
- WORKER_STATE.delete(tid)
205
- PROCESSED.increment
225
+ def reset
226
+ @lock.synchronize {
227
+ val = @value
228
+ @value = 0
229
+ val
230
+ }
206
231
  end
207
232
  end
208
233
 
209
- # Deep clone the arguments passed to the worker so that if
210
- # the job fails, what is pushed back onto Redis hasn't
211
- # been mutated by the worker.
212
- def cloned(thing)
213
- Marshal.load(Marshal.dump(thing))
214
- end
234
+ # jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
235
+ class SharedWorkState
236
+ def initialize
237
+ @work_state = {}
238
+ @lock = Mutex.new
239
+ end
240
+
241
+ def set(tid, hash)
242
+ @lock.synchronize { @work_state[tid] = hash }
243
+ end
215
244
 
216
- def constantize(str)
217
- names = str.split('::')
218
- names.shift if names.empty? || names.first.empty?
245
+ def delete(tid)
246
+ @lock.synchronize { @work_state.delete(tid) }
247
+ end
219
248
 
220
- names.inject(Object) do |constant, name|
221
- # the false flag limits search for name to under the constant namespace
222
- # which mimics Rails' behaviour
223
- constant.const_defined?(name, false) ? constant.const_get(name, false) : constant.const_missing(name)
249
+ def dup
250
+ @lock.synchronize { @work_state.dup }
251
+ end
252
+
253
+ def size
254
+ @lock.synchronize { @work_state.size }
255
+ end
256
+
257
+ def clear
258
+ @lock.synchronize { @work_state.clear }
224
259
  end
225
260
  end
226
261
 
262
+ PROCESSED = Counter.new
263
+ FAILURE = Counter.new
264
+ WORK_STATE = SharedWorkState.new
265
+
266
+ def stats(jobstr, queue)
267
+ WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
268
+
269
+ begin
270
+ yield
271
+ rescue Exception
272
+ FAILURE.incr
273
+ raise
274
+ ensure
275
+ WORK_STATE.delete(tid)
276
+ PROCESSED.incr
277
+ end
278
+ end
227
279
  end
228
280
  end
data/lib/sidekiq/rails.rb CHANGED
@@ -1,49 +1,18 @@
1
1
  # frozen_string_literal: true
2
- module Sidekiq
3
- class Rails < ::Rails::Engine
4
- # We need to setup this up before any application configuration which might
5
- # change Sidekiq middleware.
6
- #
7
- # This hook happens after `Rails::Application` is inherited within
8
- # config/application.rb and before config is touched, usually within the
9
- # class block. Definitely before config/environments/*.rb and
10
- # config/initializers/*.rb.
11
- config.before_configuration do
12
- if defined?(::ActiveRecord)
13
- Sidekiq.server_middleware do |chain|
14
- if ::Rails::VERSION::MAJOR < 5
15
- require 'sidekiq/middleware/server/active_record'
16
- chain.add Sidekiq::Middleware::Server::ActiveRecord
17
- end
18
-
19
- require 'sidekiq/middleware/server/active_record_cache'
20
- chain.add Sidekiq::Middleware::Server::ActiveRecordCache
21
- end
22
- end
23
- end
24
2
 
25
- config.after_initialize do
26
- # This hook happens after all initializers are run, just before returning
27
- # from config/environment.rb back to sidekiq/cli.rb.
28
- # We have to add the reloader after initialize to see if cache_classes has
29
- # been turned on.
30
- #
31
- # None of this matters on the client-side, only within the Sidekiq process itself.
32
- #
33
- Sidekiq.configure_server do |_|
34
- if ::Rails::VERSION::MAJOR >= 5
35
- Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
36
- end
37
- end
38
- end
3
+ require "sidekiq/job"
4
+ require "rails"
39
5
 
6
+ module Sidekiq
7
+ class Rails < ::Rails::Engine
40
8
  class Reloader
41
9
  def initialize(app = ::Rails.application)
42
10
  @app = app
43
11
  end
44
12
 
45
13
  def call
46
- @app.reloader.wrap do
14
+ params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
15
+ @app.reloader.wrap(**params) do
47
16
  yield
48
17
  end
49
18
  end
@@ -51,12 +20,54 @@ module Sidekiq
51
20
  def inspect
52
21
  "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
53
22
  end
23
+
24
+ def to_json(*)
25
+ Sidekiq.dump_json(inspect)
26
+ end
54
27
  end
55
- end if defined?(::Rails)
56
- end
57
28
 
58
- if defined?(::Rails) && ::Rails::VERSION::MAJOR < 4
59
- $stderr.puts("**************************************************")
60
- $stderr.puts("⛔️ WARNING: Sidekiq server is no longer supported by Rails 3.2 - please ensure your server/workers are updated")
61
- $stderr.puts("**************************************************")
29
+ # By including the Options module, we allow AJs to directly control sidekiq features
30
+ # via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
31
+ # AJ retries don't show up in the Sidekiq UI Retries tab, don't save any error data, can't be
32
+ # manually retried, don't automatically die, etc.
33
+ #
34
+ # class SomeJob < ActiveJob::Base
35
+ # queue_as :default
36
+ # sidekiq_options retry: 3, backtrace: 10
37
+ # def perform
38
+ # end
39
+ # end
40
+ initializer "sidekiq.active_job_integration" do
41
+ ActiveSupport.on_load(:active_job) do
42
+ include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
43
+ end
44
+ end
45
+
46
+ initializer "sidekiq.rails_logger" do
47
+ Sidekiq.configure_server do |config|
48
+ # This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
49
+ # it will appear in the Sidekiq console with all of the job context. See #5021 and
50
+ # https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84
51
+ unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
52
+ ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
53
+ end
54
+ end
55
+ end
56
+
57
+ initializer "sidekiq.backtrace_cleaner" do
58
+ Sidekiq.configure_server do |config|
59
+ config[:backtrace_cleaner] = ->(backtrace) { ::Rails.backtrace_cleaner.clean(backtrace) }
60
+ end
61
+ end
62
+
63
+ # This hook happens after all initializers are run, just before returning
64
+ # from config/environment.rb back to sidekiq/cli.rb.
65
+ #
66
+ # None of this matters on the client-side, only within the Sidekiq process itself.
67
+ config.after_initialize do
68
+ Sidekiq.configure_server do |config|
69
+ config[:reloader] = Sidekiq::Rails::Reloader.new
70
+ end
71
+ end
72
+ end
62
73
  end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "redis_client"
5
+ require "redis_client/decorator"
6
+
7
+ module Sidekiq
8
+ class RedisClientAdapter
9
+ BaseError = RedisClient::Error
10
+ CommandError = RedisClient::CommandError
11
+
12
+ # You can add/remove items or clear the whole thing if you don't want deprecation warnings.
13
+ DEPRECATED_COMMANDS = %i[rpoplpush zrangebyscore zrevrange zrevrangebyscore getset hmset setex setnx].to_set
14
+
15
+ module CompatMethods
16
+ def info
17
+ @client.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h }
18
+ end
19
+
20
+ def evalsha(sha, keys, argv)
21
+ @client.call("EVALSHA", sha, keys.size, *keys, *argv)
22
+ end
23
+
24
+ private
25
+
26
+ # this allows us to use methods like `conn.hmset(...)` instead of having to use
27
+ # redis-client's native `conn.call("hmset", ...)`
28
+ def method_missing(*args, &block)
29
+ warn("[sidekiq#5788] Redis has deprecated the `#{args.first}`command, called at #{caller(1..1)}") if DEPRECATED_COMMANDS.include?(args.first)
30
+ @client.call(*args, *block)
31
+ end
32
+ ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
33
+
34
+ def respond_to_missing?(name, include_private = false)
35
+ super # Appease the linter. We can't tell what is a valid command.
36
+ end
37
+ end
38
+
39
+ CompatClient = RedisClient::Decorator.create(CompatMethods)
40
+
41
+ class CompatClient
42
+ def config
43
+ @client.config
44
+ end
45
+ end
46
+
47
+ def initialize(options)
48
+ opts = client_opts(options)
49
+ @config = if opts.key?(:sentinels)
50
+ RedisClient.sentinel(**opts)
51
+ else
52
+ RedisClient.config(**opts)
53
+ end
54
+ end
55
+
56
+ def new_client
57
+ CompatClient.new(@config.new_client)
58
+ end
59
+
60
+ private
61
+
62
+ def client_opts(options)
63
+ opts = options.dup
64
+
65
+ if opts[:namespace]
66
+ raise ArgumentError, "Your Redis configuration uses the namespace '#{opts[:namespace]}' but this feature isn't supported by redis-client. " \
67
+ "Either use the redis adapter or remove the namespace."
68
+ end
69
+
70
+ opts.delete(:size)
71
+ opts.delete(:pool_timeout)
72
+
73
+ if opts[:network_timeout]
74
+ opts[:timeout] = opts[:network_timeout]
75
+ opts.delete(:network_timeout)
76
+ end
77
+
78
+ if opts[:driver]
79
+ opts[:driver] = opts[:driver].to_sym
80
+ end
81
+
82
+ opts[:name] = opts.delete(:master_name) if opts.key?(:master_name)
83
+ opts[:role] = opts[:role].to_sym if opts.key?(:role)
84
+ opts.delete(:url) if opts.key?(:sentinels)
85
+
86
+ # Issue #3303, redis-rb will silently retry an operation.
87
+ # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
88
+ # is performed twice but I believe this is much, much rarer
89
+ # than the reconnect silently fixing a problem; we keep it
90
+ # on by default.
91
+ opts[:reconnect_attempts] ||= 1
92
+
93
+ opts
94
+ end
95
+ end
96
+ end