sidekiq 6.2.2 → 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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +299 -11
  3. data/LICENSE.txt +9 -0
  4. data/README.md +45 -32
  5. data/bin/sidekiq +4 -9
  6. data/bin/sidekiqload +207 -117
  7. data/bin/sidekiqmon +4 -1
  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 +334 -190
  13. data/lib/sidekiq/capsule.rb +127 -0
  14. data/lib/sidekiq/cli.rb +95 -81
  15. data/lib/sidekiq/client.rb +102 -96
  16. data/lib/sidekiq/{util.rb → component.rb} +14 -41
  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 +26 -26
  21. data/lib/sidekiq/job.rb +371 -5
  22. data/lib/sidekiq/job_logger.rb +16 -28
  23. data/lib/sidekiq/job_retry.rb +85 -59
  24. data/lib/sidekiq/job_util.rb +105 -0
  25. data/lib/sidekiq/launcher.rb +106 -94
  26. data/lib/sidekiq/logger.rb +9 -44
  27. data/lib/sidekiq/manager.rb +40 -41
  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 +96 -51
  32. data/lib/sidekiq/middleware/current_attributes.rb +95 -0
  33. data/lib/sidekiq/middleware/i18n.rb +6 -4
  34. data/lib/sidekiq/middleware/modules.rb +21 -0
  35. data/lib/sidekiq/monitor.rb +17 -4
  36. data/lib/sidekiq/paginator.rb +17 -9
  37. data/lib/sidekiq/processor.rb +60 -60
  38. data/lib/sidekiq/rails.rb +29 -6
  39. data/lib/sidekiq/redis_client_adapter.rb +96 -0
  40. data/lib/sidekiq/redis_connection.rb +17 -88
  41. data/lib/sidekiq/ring_buffer.rb +29 -0
  42. data/lib/sidekiq/scheduled.rb +101 -44
  43. data/lib/sidekiq/testing/inline.rb +4 -4
  44. data/lib/sidekiq/testing.rb +41 -68
  45. data/lib/sidekiq/transaction_aware_client.rb +44 -0
  46. data/lib/sidekiq/version.rb +2 -1
  47. data/lib/sidekiq/web/action.rb +3 -3
  48. data/lib/sidekiq/web/application.rb +47 -13
  49. data/lib/sidekiq/web/csrf_protection.rb +3 -3
  50. data/lib/sidekiq/web/helpers.rb +36 -33
  51. data/lib/sidekiq/web.rb +10 -17
  52. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  53. data/lib/sidekiq.rb +86 -201
  54. data/sidekiq.gemspec +12 -10
  55. data/web/assets/javascripts/application.js +131 -60
  56. data/web/assets/javascripts/base-charts.js +106 -0
  57. data/web/assets/javascripts/chart.min.js +13 -0
  58. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  59. data/web/assets/javascripts/dashboard-charts.js +166 -0
  60. data/web/assets/javascripts/dashboard.js +36 -273
  61. data/web/assets/javascripts/metrics.js +264 -0
  62. data/web/assets/stylesheets/application-dark.css +23 -23
  63. data/web/assets/stylesheets/application-rtl.css +2 -95
  64. data/web/assets/stylesheets/application.css +73 -402
  65. data/web/locales/ar.yml +70 -70
  66. data/web/locales/cs.yml +62 -62
  67. data/web/locales/da.yml +60 -53
  68. data/web/locales/de.yml +65 -65
  69. data/web/locales/el.yml +43 -24
  70. data/web/locales/en.yml +82 -69
  71. data/web/locales/es.yml +68 -68
  72. data/web/locales/fa.yml +65 -65
  73. data/web/locales/fr.yml +81 -67
  74. data/web/locales/gd.yml +99 -0
  75. data/web/locales/he.yml +65 -64
  76. data/web/locales/hi.yml +59 -59
  77. data/web/locales/it.yml +53 -53
  78. data/web/locales/ja.yml +73 -68
  79. data/web/locales/ko.yml +52 -52
  80. data/web/locales/lt.yml +66 -66
  81. data/web/locales/nb.yml +61 -61
  82. data/web/locales/nl.yml +52 -52
  83. data/web/locales/pl.yml +45 -45
  84. data/web/locales/pt-br.yml +63 -55
  85. data/web/locales/pt.yml +51 -51
  86. data/web/locales/ru.yml +67 -66
  87. data/web/locales/sv.yml +53 -53
  88. data/web/locales/ta.yml +60 -60
  89. data/web/locales/uk.yml +62 -61
  90. data/web/locales/ur.yml +64 -64
  91. data/web/locales/vi.yml +67 -67
  92. data/web/locales/zh-cn.yml +43 -16
  93. data/web/locales/zh-tw.yml +42 -8
  94. data/web/views/_footer.erb +6 -3
  95. data/web/views/_job_info.erb +18 -2
  96. data/web/views/_metrics_period_select.erb +12 -0
  97. data/web/views/_nav.erb +1 -1
  98. data/web/views/_paging.erb +2 -0
  99. data/web/views/_poll_link.erb +3 -6
  100. data/web/views/_summary.erb +7 -7
  101. data/web/views/busy.erb +44 -28
  102. data/web/views/dashboard.erb +44 -12
  103. data/web/views/layout.erb +1 -1
  104. data/web/views/metrics.erb +82 -0
  105. data/web/views/metrics_for_job.erb +68 -0
  106. data/web/views/morgue.erb +5 -9
  107. data/web/views/queue.erb +24 -24
  108. data/web/views/queues.erb +4 -2
  109. data/web/views/retries.erb +5 -9
  110. data/web/views/scheduled.erb +12 -13
  111. metadata +62 -31
  112. data/LICENSE +0 -9
  113. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  114. data/lib/sidekiq/delay.rb +0 -41
  115. data/lib/sidekiq/exception_handler.rb +0 -27
  116. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  117. data/lib/sidekiq/extensions/active_record.rb +0 -43
  118. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  119. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  120. data/lib/sidekiq/worker.rb +0 -244
@@ -0,0 +1,95 @@
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
+ # For multiple current attributes, pass an array of current attributes.
11
+ #
12
+ # @example
13
+ #
14
+ # # in your initializer
15
+ # require "sidekiq/middleware/current_attributes"
16
+ # Sidekiq::CurrentAttributes.persist("Myapp::Current")
17
+ # # or multiple current attributes
18
+ # Sidekiq::CurrentAttributes.persist(["Myapp::Current", "Myapp::OtherCurrent"])
19
+ #
20
+ module CurrentAttributes
21
+ class Save
22
+ include Sidekiq::ClientMiddleware
23
+
24
+ def initialize(cattrs)
25
+ @cattrs = cattrs
26
+ end
27
+
28
+ def call(_, job, _, _)
29
+ @cattrs.each do |(key, strklass)|
30
+ if !job.has_key?(key)
31
+ attrs = strklass.constantize.attributes
32
+ # Retries can push the job N times, we don't
33
+ # want retries to reset cattr. #5692, #5090
34
+ job[key] = attrs if attrs.any?
35
+ end
36
+ end
37
+ yield
38
+ end
39
+ end
40
+
41
+ class Load
42
+ include Sidekiq::ServerMiddleware
43
+
44
+ def initialize(cattrs)
45
+ @cattrs = cattrs
46
+ end
47
+
48
+ def call(_, job, _, &block)
49
+ cattrs_to_reset = []
50
+
51
+ @cattrs.each do |(key, strklass)|
52
+ if job.has_key?(key)
53
+ constklass = strklass.constantize
54
+ cattrs_to_reset << constklass
55
+
56
+ job[key].each do |(attribute, value)|
57
+ constklass.public_send("#{attribute}=", value)
58
+ end
59
+ end
60
+ end
61
+
62
+ yield
63
+ ensure
64
+ cattrs_to_reset.each(&:reset)
65
+ end
66
+ end
67
+
68
+ class << self
69
+ def persist(klass_or_array, config = Sidekiq.default_configuration)
70
+ cattrs = build_cattrs_hash(klass_or_array)
71
+
72
+ config.client_middleware.add Save, cattrs
73
+ config.server_middleware.add Load, cattrs
74
+ end
75
+
76
+ private
77
+
78
+ def build_cattrs_hash(klass_or_array)
79
+ if klass_or_array.is_a?(Array)
80
+ {}.tap do |hash|
81
+ klass_or_array.each_with_index do |klass, index|
82
+ hash[key_at(index)] = klass.to_s
83
+ end
84
+ end
85
+ else
86
+ {key_at(0) => klass_or_array.to_s}
87
+ end
88
+ end
89
+
90
+ def key_at(index)
91
+ (index == 0) ? "cattr" : "cattr_#{index}"
92
+ end
93
+ end
94
+ end
95
+ end
@@ -10,16 +10,18 @@ module Sidekiq::Middleware::I18n
10
10
  # Get the current locale and store it in the message
11
11
  # to be sent to Sidekiq.
12
12
  class Client
13
- def call(_worker, msg, _queue, _redis)
14
- msg["locale"] ||= I18n.locale
13
+ include Sidekiq::ClientMiddleware
14
+ def call(_jobclass, job, _queue, _redis)
15
+ job["locale"] ||= I18n.locale
15
16
  yield
16
17
  end
17
18
  end
18
19
 
19
20
  # Pull the msg locale out and set the current thread to use it.
20
21
  class Server
21
- def call(_worker, msg, _queue, &block)
22
- I18n.with_locale(msg.fetch("locale", I18n.default_locale), &block)
22
+ include Sidekiq::ServerMiddleware
23
+ def call(_jobclass, job, _queue, &block)
24
+ I18n.with_locale(job.fetch("locale", I18n.default_locale), &block)
23
25
  end
24
26
  end
25
27
  end
@@ -0,0 +1,21 @@
1
+ module Sidekiq
2
+ # Server-side middleware must import this Module in order
3
+ # to get access to server resources during `call`.
4
+ module ServerMiddleware
5
+ attr_accessor :config
6
+ def redis_pool
7
+ config.redis_pool
8
+ end
9
+
10
+ def logger
11
+ config.logger
12
+ end
13
+
14
+ def redis(&block)
15
+ config.redis(&block)
16
+ end
17
+ end
18
+
19
+ # no difference for now
20
+ ClientMiddleware = ServerMiddleware
21
+ end
@@ -16,8 +16,6 @@ class Sidekiq::Monitor
16
16
  return
17
17
  end
18
18
  send(section)
19
- rescue => e
20
- puts "Couldn't get status: #{e}"
21
19
  end
22
20
 
23
21
  def all
@@ -49,10 +47,25 @@ class Sidekiq::Monitor
49
47
  def processes
50
48
  puts "---- Processes (#{process_set.size}) ----"
51
49
  process_set.each_with_index do |process, index|
50
+ # Keep compatibility with legacy versions since we don't want to break sidekiqmon during rolling upgrades or downgrades.
51
+ #
52
+ # Before:
53
+ # ["default", "critical"]
54
+ #
55
+ # After:
56
+ # {"default" => 1, "critical" => 10}
57
+ queues =
58
+ if process["weights"]
59
+ process["weights"].sort_by { |queue| queue[0] }.map { |capsule| capsule.map { |name, weight| (weight > 0) ? "#{name}: #{weight}" : name }.join(", ") }
60
+ else
61
+ process["queues"].sort
62
+ end
63
+
52
64
  puts "#{process["identity"]} #{tags_for(process)}"
53
65
  puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
54
66
  puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
55
- puts " Queues: #{split_multiline(process["queues"].sort, pad: 11)}"
67
+ puts " Queues: #{split_multiline(queues, pad: 11)}"
68
+ puts " Version: #{process["version"] || "Unknown"}" if process["version"] != Sidekiq::VERSION
56
69
  puts "" unless (index + 1) == process_set.size
57
70
  end
58
71
  end
@@ -101,7 +114,7 @@ class Sidekiq::Monitor
101
114
  tags = [
102
115
  process["tag"],
103
116
  process["labels"],
104
- (process["quiet"] == "true" ? "quiet" : nil)
117
+ ((process["quiet"] == "true") ? "quiet" : nil)
105
118
  ].flatten.compact
106
119
  tags.any? ? "[#{tags.join("] [")}]" : nil
107
120
  end
@@ -3,7 +3,7 @@
3
3
  module Sidekiq
4
4
  module Paginator
5
5
  def page(key, pageidx = 1, page_size = 25, opts = nil)
6
- current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
6
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
7
7
  pageidx = current_page - 1
8
8
  total_size = 0
9
9
  items = []
@@ -16,22 +16,22 @@ module Sidekiq
16
16
 
17
17
  case type
18
18
  when "zset"
19
- total_size, items = conn.multi {
20
- conn.zcard(key)
19
+ total_size, items = conn.multi { |transaction|
20
+ transaction.zcard(key)
21
21
  if rev
22
- conn.zrevrange(key, starting, ending, with_scores: true)
22
+ transaction.zrange(key, starting, ending, "REV", withscores: true)
23
23
  else
24
- conn.zrange(key, starting, ending, with_scores: true)
24
+ transaction.zrange(key, starting, ending, withscores: true)
25
25
  end
26
26
  }
27
27
  [current_page, total_size, items]
28
28
  when "list"
29
- total_size, items = conn.multi {
30
- conn.llen(key)
29
+ total_size, items = conn.multi { |transaction|
30
+ transaction.llen(key)
31
31
  if rev
32
- conn.lrange(key, -ending - 1, -starting - 1)
32
+ transaction.lrange(key, -ending - 1, -starting - 1)
33
33
  else
34
- conn.lrange(key, starting, ending)
34
+ transaction.lrange(key, starting, ending)
35
35
  end
36
36
  }
37
37
  items.reverse! if rev
@@ -43,5 +43,13 @@ module Sidekiq
43
43
  end
44
44
  end
45
45
  end
46
+
47
+ def page_items(items, pageidx = 1, page_size = 25)
48
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
49
+ pageidx = current_page - 1
50
+ starting = pageidx * page_size
51
+ items = items.to_a
52
+ [current_page, items.size, items[starting, page_size]]
53
+ end
46
54
  end
47
55
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sidekiq/util"
4
3
  require "sidekiq/fetch"
5
4
  require "sidekiq/job_logger"
6
5
  require "sidekiq/job_retry"
@@ -11,33 +10,34 @@ module Sidekiq
11
10
  #
12
11
  # 1. fetches a job from Redis
13
12
  # 2. executes the job
14
- # a. instantiate the Worker
13
+ # a. instantiate the job class
15
14
  # b. run the middleware chain
16
15
  # c. call #perform
17
16
  #
18
- # A Processor can exit due to shutdown (processor_stopped)
19
- # 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.
20
19
  #
21
20
  # If an error occurs in the job execution, the
22
21
  # Processor calls the Manager to create a new one
23
22
  # to replace itself and exits.
24
23
  #
25
24
  class Processor
26
- include Util
25
+ include Sidekiq::Component
27
26
 
28
27
  attr_reader :thread
29
28
  attr_reader :job
29
+ attr_reader :capsule
30
30
 
31
- def initialize(mgr, options)
32
- @mgr = mgr
31
+ def initialize(capsule, &block)
32
+ @config = @capsule = capsule
33
+ @callback = block
33
34
  @down = false
34
35
  @done = false
35
36
  @job = nil
36
37
  @thread = nil
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
38
+ @reloader = Sidekiq.default_configuration[:reloader]
39
+ @job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(logger)
40
+ @retrier = Sidekiq::JobRetry.new(capsule)
41
41
  end
42
42
 
43
43
  def terminate(wait = false)
@@ -59,33 +59,37 @@ module Sidekiq
59
59
  end
60
60
 
61
61
  def start
62
- @thread ||= safe_thread("processor", &method(:run))
62
+ @thread ||= safe_thread("#{config.name}/processor", &method(:run))
63
63
  end
64
64
 
65
65
  private unless $TESTING
66
66
 
67
67
  def run
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
+
68
72
  process_one until @done
69
- @mgr.processor_stopped(self)
73
+ @callback.call(self)
70
74
  rescue Sidekiq::Shutdown
71
- @mgr.processor_stopped(self)
75
+ @callback.call(self)
72
76
  rescue Exception => ex
73
- @mgr.processor_died(self, ex)
77
+ @callback.call(self, ex)
74
78
  end
75
79
 
76
- def process_one
80
+ def process_one(&block)
77
81
  @job = fetch
78
82
  process(@job) if @job
79
83
  @job = nil
80
84
  end
81
85
 
82
86
  def get_one
83
- work = @strategy.retrieve_work
87
+ uow = capsule.fetcher.retrieve_work
84
88
  if @down
85
89
  logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
86
90
  @down = nil
87
91
  end
88
- work
92
+ uow
89
93
  rescue Sidekiq::Shutdown
90
94
  rescue => ex
91
95
  handle_fetch_exception(ex)
@@ -129,11 +133,11 @@ module Sidekiq
129
133
  # the Reloader. It handles code loading, db connection management, etc.
130
134
  # Effectively this block denotes a "unit of work" to Rails.
131
135
  @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
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
137
141
  end
138
142
  end
139
143
  end
@@ -142,9 +146,12 @@ module Sidekiq
142
146
  end
143
147
  end
144
148
 
145
- def process(work)
146
- jobstr = work.job
147
- queue = work.queue_name
149
+ IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :never}
150
+ private_constant :IGNORE_SHUTDOWN_INTERRUPTS
151
+
152
+ def process(uow)
153
+ jobstr = uow.job
154
+ queue = uow.queue_name
148
155
 
149
156
  # Treat malformed JSON as a special case: job goes straight to the morgue.
150
157
  job_hash = nil
@@ -152,16 +159,22 @@ module Sidekiq
152
159
  job_hash = Sidekiq.load_json(jobstr)
153
160
  rescue => ex
154
161
  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
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
169
+ end
170
+ return uow.acknowledge
158
171
  end
159
172
 
160
173
  ack = false
161
174
  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"])
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"])
165
178
  end
166
179
  end
167
180
  ack = true
@@ -174,7 +187,7 @@ module Sidekiq
174
187
  # signals that we created a retry successfully. We can acknowlege the job.
175
188
  ack = true
176
189
  e = h.cause || h
177
- handle_exception(e, {context: "Job raised exception", job: job_hash, jobstr: jobstr})
190
+ handle_exception(e, {context: "Job raised exception", job: job_hash})
178
191
  raise e
179
192
  rescue Exception => ex
180
193
  # Unexpected error! This is very bad and indicates an exception that got past
@@ -185,15 +198,15 @@ module Sidekiq
185
198
  ensure
186
199
  if ack
187
200
  # We don't want a shutdown signal to interrupt job acknowledgment.
188
- Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
189
- work.acknowledge
201
+ Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
202
+ uow.acknowledge
190
203
  end
191
204
  end
192
205
  end
193
206
  end
194
207
 
195
- def execute_job(worker, cloned_args)
196
- worker.perform(*cloned_args)
208
+ def execute_job(inst, cloned_args)
209
+ inst.perform(*cloned_args)
197
210
  end
198
211
 
199
212
  # Ruby doesn't provide atomic counters out of the box so we'll
@@ -219,39 +232,39 @@ module Sidekiq
219
232
  end
220
233
 
221
234
  # jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
222
- class SharedWorkerState
235
+ class SharedWorkState
223
236
  def initialize
224
- @worker_state = {}
237
+ @work_state = {}
225
238
  @lock = Mutex.new
226
239
  end
227
240
 
228
241
  def set(tid, hash)
229
- @lock.synchronize { @worker_state[tid] = hash }
242
+ @lock.synchronize { @work_state[tid] = hash }
230
243
  end
231
244
 
232
245
  def delete(tid)
233
- @lock.synchronize { @worker_state.delete(tid) }
246
+ @lock.synchronize { @work_state.delete(tid) }
234
247
  end
235
248
 
236
249
  def dup
237
- @lock.synchronize { @worker_state.dup }
250
+ @lock.synchronize { @work_state.dup }
238
251
  end
239
252
 
240
253
  def size
241
- @lock.synchronize { @worker_state.size }
254
+ @lock.synchronize { @work_state.size }
242
255
  end
243
256
 
244
257
  def clear
245
- @lock.synchronize { @worker_state.clear }
258
+ @lock.synchronize { @work_state.clear }
246
259
  end
247
260
  end
248
261
 
249
262
  PROCESSED = Counter.new
250
263
  FAILURE = Counter.new
251
- WORKER_STATE = SharedWorkerState.new
264
+ WORK_STATE = SharedWorkState.new
252
265
 
253
266
  def stats(jobstr, queue)
254
- WORKER_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
267
+ WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
255
268
 
256
269
  begin
257
270
  yield
@@ -259,22 +272,9 @@ module Sidekiq
259
272
  FAILURE.incr
260
273
  raise
261
274
  ensure
262
- WORKER_STATE.delete(tid)
275
+ WORK_STATE.delete(tid)
263
276
  PROCESSED.incr
264
277
  end
265
278
  end
266
-
267
- def constantize(str)
268
- return Object.const_get(str) unless str.include?("::")
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
279
279
  end
280
280
  end
data/lib/sidekiq/rails.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sidekiq/worker"
3
+ require "sidekiq/job"
4
+ require "rails"
4
5
 
5
6
  module Sidekiq
6
7
  class Rails < ::Rails::Engine
@@ -10,7 +11,8 @@ module Sidekiq
10
11
  end
11
12
 
12
13
  def call
13
- @app.reloader.wrap do
14
+ params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
15
+ @app.reloader.wrap(**params) do
14
16
  yield
15
17
  end
16
18
  end
@@ -18,11 +20,15 @@ module Sidekiq
18
20
  def inspect
19
21
  "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
20
22
  end
23
+
24
+ def to_json(*)
25
+ Sidekiq.dump_json(inspect)
26
+ end
21
27
  end
22
28
 
23
29
  # By including the Options module, we allow AJs to directly control sidekiq features
24
30
  # 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
31
+ # AJ retries don't show up in the Sidekiq UI Retries tab, don't save any error data, can't be
26
32
  # manually retried, don't automatically die, etc.
27
33
  #
28
34
  # class SomeJob < ActiveJob::Base
@@ -33,7 +39,24 @@ module Sidekiq
33
39
  # end
34
40
  initializer "sidekiq.active_job_integration" do
35
41
  ActiveSupport.on_load(:active_job) do
36
- include ::Sidekiq::Worker::Options unless respond_to?(:sidekiq_options)
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) }
37
60
  end
38
61
  end
39
62
 
@@ -42,8 +65,8 @@ module Sidekiq
42
65
  #
43
66
  # None of this matters on the client-side, only within the Sidekiq process itself.
44
67
  config.after_initialize do
45
- Sidekiq.configure_server do |_|
46
- Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
68
+ Sidekiq.configure_server do |config|
69
+ config[:reloader] = Sidekiq::Rails::Reloader.new
47
70
  end
48
71
  end
49
72
  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