sidekiq 6.1.0 → 7.0.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.

Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +256 -7
  3. data/LICENSE.txt +9 -0
  4. data/README.md +21 -16
  5. data/bin/sidekiq +4 -9
  6. data/bin/sidekiqload +71 -76
  7. data/bin/sidekiqmon +1 -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 +352 -229
  13. data/lib/sidekiq/capsule.rb +110 -0
  14. data/lib/sidekiq/cli.rb +109 -89
  15. data/lib/sidekiq/client.rb +75 -86
  16. data/lib/sidekiq/{util.rb → component.rb} +13 -14
  17. data/lib/sidekiq/config.rb +271 -0
  18. data/lib/sidekiq/deploy.rb +62 -0
  19. data/lib/sidekiq/embedded.rb +61 -0
  20. data/lib/sidekiq/fetch.rb +31 -23
  21. data/lib/sidekiq/{worker.rb → job.rb} +162 -28
  22. data/lib/sidekiq/job_logger.rb +17 -29
  23. data/lib/sidekiq/job_retry.rb +80 -60
  24. data/lib/sidekiq/job_util.rb +71 -0
  25. data/lib/sidekiq/launcher.rb +143 -92
  26. data/lib/sidekiq/logger.rb +11 -45
  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 +134 -0
  31. data/lib/sidekiq/middleware/chain.rb +90 -46
  32. data/lib/sidekiq/middleware/current_attributes.rb +58 -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 +1 -1
  36. data/lib/sidekiq/paginator.rb +16 -8
  37. data/lib/sidekiq/processor.rb +56 -59
  38. data/lib/sidekiq/rails.rb +17 -5
  39. data/lib/sidekiq/redis_client_adapter.rb +118 -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 +102 -39
  43. data/lib/sidekiq/testing/inline.rb +4 -4
  44. data/lib/sidekiq/testing.rb +42 -71
  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 +42 -17
  49. data/lib/sidekiq/web/csrf_protection.rb +33 -6
  50. data/lib/sidekiq/web/helpers.rb +52 -41
  51. data/lib/sidekiq/web/router.rb +4 -1
  52. data/lib/sidekiq/web.rb +26 -81
  53. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  54. data/lib/sidekiq.rb +86 -201
  55. data/sidekiq.gemspec +38 -6
  56. data/web/assets/images/apple-touch-icon.png +0 -0
  57. data/web/assets/javascripts/application.js +113 -65
  58. data/web/assets/javascripts/base-charts.js +106 -0
  59. data/web/assets/javascripts/chart.min.js +13 -0
  60. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  61. data/web/assets/javascripts/dashboard-charts.js +166 -0
  62. data/web/assets/javascripts/dashboard.js +36 -273
  63. data/web/assets/javascripts/metrics.js +236 -0
  64. data/web/assets/stylesheets/application-dark.css +61 -51
  65. data/web/assets/stylesheets/application-rtl.css +2 -95
  66. data/web/assets/stylesheets/application.css +98 -532
  67. data/web/locales/ar.yml +71 -65
  68. data/web/locales/cs.yml +62 -62
  69. data/web/locales/da.yml +52 -52
  70. data/web/locales/de.yml +65 -65
  71. data/web/locales/el.yml +43 -24
  72. data/web/locales/en.yml +83 -67
  73. data/web/locales/es.yml +70 -54
  74. data/web/locales/fa.yml +65 -65
  75. data/web/locales/fr.yml +69 -62
  76. data/web/locales/he.yml +65 -64
  77. data/web/locales/hi.yml +59 -59
  78. data/web/locales/it.yml +53 -53
  79. data/web/locales/ja.yml +72 -66
  80. data/web/locales/ko.yml +52 -52
  81. data/web/locales/lt.yml +66 -66
  82. data/web/locales/nb.yml +61 -61
  83. data/web/locales/nl.yml +52 -52
  84. data/web/locales/pl.yml +45 -45
  85. data/web/locales/pt-br.yml +63 -55
  86. data/web/locales/pt.yml +51 -51
  87. data/web/locales/ru.yml +68 -63
  88. data/web/locales/sv.yml +53 -53
  89. data/web/locales/ta.yml +60 -60
  90. data/web/locales/uk.yml +62 -61
  91. data/web/locales/ur.yml +64 -64
  92. data/web/locales/vi.yml +67 -67
  93. data/web/locales/zh-cn.yml +37 -11
  94. data/web/locales/zh-tw.yml +42 -8
  95. data/web/views/_footer.erb +6 -3
  96. data/web/views/_job_info.erb +1 -1
  97. data/web/views/_nav.erb +1 -1
  98. data/web/views/_poll_link.erb +2 -5
  99. data/web/views/_summary.erb +7 -7
  100. data/web/views/busy.erb +57 -21
  101. data/web/views/dashboard.erb +58 -18
  102. data/web/views/dead.erb +1 -1
  103. data/web/views/layout.erb +2 -1
  104. data/web/views/metrics.erb +80 -0
  105. data/web/views/metrics_for_job.erb +69 -0
  106. data/web/views/morgue.erb +6 -6
  107. data/web/views/queue.erb +15 -11
  108. data/web/views/queues.erb +4 -4
  109. data/web/views/retries.erb +7 -7
  110. data/web/views/retry.erb +1 -1
  111. data/web/views/scheduled.erb +1 -1
  112. metadata +87 -52
  113. data/.circleci/config.yml +0 -71
  114. data/.github/contributing.md +0 -32
  115. data/.github/issue_template.md +0 -11
  116. data/.gitignore +0 -13
  117. data/.standard.yml +0 -20
  118. data/3.0-Upgrade.md +0 -70
  119. data/4.0-Upgrade.md +0 -53
  120. data/5.0-Upgrade.md +0 -56
  121. data/6.0-Upgrade.md +0 -72
  122. data/COMM-LICENSE +0 -97
  123. data/Ent-2.0-Upgrade.md +0 -37
  124. data/Ent-Changes.md +0 -269
  125. data/Gemfile +0 -24
  126. data/Gemfile.lock +0 -208
  127. data/LICENSE +0 -9
  128. data/Pro-2.0-Upgrade.md +0 -138
  129. data/Pro-3.0-Upgrade.md +0 -44
  130. data/Pro-4.0-Upgrade.md +0 -35
  131. data/Pro-5.0-Upgrade.md +0 -25
  132. data/Pro-Changes.md +0 -790
  133. data/Rakefile +0 -10
  134. data/code_of_conduct.md +0 -50
  135. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  136. data/lib/sidekiq/delay.rb +0 -41
  137. data/lib/sidekiq/exception_handler.rb +0 -27
  138. data/lib/sidekiq/extensions/action_mailer.rb +0 -47
  139. data/lib/sidekiq/extensions/active_record.rb +0 -43
  140. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  141. data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
@@ -0,0 +1,61 @@
1
+ require "sidekiq/component"
2
+ require "sidekiq/launcher"
3
+ require "sidekiq/metrics/tracking"
4
+
5
+ module Sidekiq
6
+ class Embedded
7
+ include Sidekiq::Component
8
+
9
+ def initialize(config)
10
+ @config = config
11
+ end
12
+
13
+ def run
14
+ housekeeping
15
+ fire_event(:startup, reverse: false, reraise: true)
16
+ @launcher = Sidekiq::Launcher.new(@config, embedded: true)
17
+ @launcher.run
18
+ sleep 0.1 # pause to give threads time to spin up
19
+
20
+ logger.info "Embedded mode running with #{Thread.list.size} threads"
21
+ logger.debug { Thread.list.map(&:name) }
22
+ end
23
+
24
+ def quiet
25
+ @launcher&.quiet
26
+ end
27
+
28
+ def stop
29
+ @launcher&.stop
30
+ end
31
+
32
+ private
33
+
34
+ def housekeeping
35
+ logger.info "Running in #{RUBY_DESCRIPTION}"
36
+ logger.info Sidekiq::LICENSE
37
+ logger.info "Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org" unless defined?(::Sidekiq::Pro)
38
+
39
+ # touch the connection pool so it is created before we
40
+ # fire startup and start multithreading.
41
+ info = config.redis_info
42
+ ver = Gem::Version.new(info["redis_version"])
43
+ raise "You are connecting to Redis #{ver}, Sidekiq requires Redis 6.2.0 or greater" if ver < Gem::Version.new("6.2.0")
44
+
45
+ maxmemory_policy = info["maxmemory_policy"]
46
+ if maxmemory_policy != "noeviction"
47
+ logger.warn <<~EOM
48
+
49
+
50
+ WARNING: Your Redis instance will evict Sidekiq data under heavy load.
51
+ The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
52
+ See: https://github.com/mperham/sidekiq/wiki/Using-Redis#memory
53
+
54
+ EOM
55
+ end
56
+
57
+ logger.debug { "Client Middleware: #{@config.default_capsule.client_middleware.map(&:klass).join(", ")}" }
58
+ logger.debug { "Server Middleware: #{@config.default_capsule.server_middleware.map(&:klass).join(", ")}" }
59
+ end
60
+ end
61
+ end
data/lib/sidekiq/fetch.rb CHANGED
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq"
4
+ require "sidekiq/component"
5
+ require "sidekiq/capsule"
4
6
 
5
- module Sidekiq
7
+ module Sidekiq # :nodoc:
6
8
  class BasicFetch
9
+ include Sidekiq::Component
7
10
  # We want the fetch operation to timeout every few seconds so the thread
8
11
  # can check if the process is shutting down.
9
12
  TIMEOUT = 2
10
13
 
11
- UnitOfWork = Struct.new(:queue, :job) {
14
+ UnitOfWork = Struct.new(:queue, :job, :config) {
12
15
  def acknowledge
13
16
  # nothing to do
14
17
  end
@@ -18,50 +21,55 @@ module Sidekiq
18
21
  end
19
22
 
20
23
  def requeue
21
- Sidekiq.redis do |conn|
24
+ config.redis do |conn|
22
25
  conn.rpush(queue, job)
23
26
  end
24
27
  end
25
28
  }
26
29
 
27
- def initialize(options)
28
- raise ArgumentError, "missing queue list" unless options[:queues]
29
- @options = options
30
- @strictly_ordered_queues = !!@options[:strict]
31
- @queues = @options[:queues].map { |q| "queue:#{q}" }
30
+ def initialize(cap)
31
+ raise ArgumentError, "missing queue list" unless cap.queues
32
+ @config = cap
33
+ @strictly_ordered_queues = (config.queues.size == config.queues.uniq.size)
34
+ @queues = config.queues.map { |q| "queue:#{q}" }
32
35
  if @strictly_ordered_queues
33
36
  @queues.uniq!
34
- @queues << TIMEOUT
35
37
  end
36
38
  end
37
39
 
38
40
  def retrieve_work
39
- work = Sidekiq.redis { |conn| conn.brpop(*queues_cmd) }
40
- UnitOfWork.new(*work) if work
41
+ qs = queues_cmd
42
+ # 4825 Sidekiq Pro with all queues paused will return an
43
+ # empty set of queues
44
+ if qs.size <= 0
45
+ sleep(TIMEOUT)
46
+ return nil
47
+ end
48
+
49
+ queue, job = redis { |conn| conn.blocking_call(false, "brpop", *qs, TIMEOUT) }
50
+ UnitOfWork.new(queue, job, config) if queue
41
51
  end
42
52
 
43
- # By leaving this as a class method, it can be pluggable and used by the Manager actor. Making it
44
- # an instance method will make it async to the Fetcher actor
45
- def bulk_requeue(inprogress, options)
53
+ def bulk_requeue(inprogress)
46
54
  return if inprogress.empty?
47
55
 
48
- Sidekiq.logger.debug { "Re-queueing terminated jobs" }
56
+ logger.debug { "Re-queueing terminated jobs" }
49
57
  jobs_to_requeue = {}
50
58
  inprogress.each do |unit_of_work|
51
59
  jobs_to_requeue[unit_of_work.queue] ||= []
52
60
  jobs_to_requeue[unit_of_work.queue] << unit_of_work.job
53
61
  end
54
62
 
55
- Sidekiq.redis do |conn|
56
- conn.pipelined do
63
+ redis do |conn|
64
+ conn.pipelined do |pipeline|
57
65
  jobs_to_requeue.each do |queue, jobs|
58
- conn.rpush(queue, jobs)
66
+ pipeline.rpush(queue, jobs)
59
67
  end
60
68
  end
61
69
  end
62
- Sidekiq.logger.info("Pushed #{inprogress.size} jobs back to Redis")
70
+ logger.info("Pushed #{inprogress.size} jobs back to Redis")
63
71
  rescue => ex
64
- Sidekiq.logger.warn("Failed to requeue #{inprogress.size} jobs: #{ex.message}")
72
+ logger.warn("Failed to requeue #{inprogress.size} jobs: #{ex.message}")
65
73
  end
66
74
 
67
75
  # Creating the Redis#brpop command takes into account any
@@ -73,9 +81,9 @@ module Sidekiq
73
81
  if @strictly_ordered_queues
74
82
  @queues
75
83
  else
76
- queues = @queues.shuffle!.uniq
77
- queues << TIMEOUT
78
- queues
84
+ permute = @queues.shuffle
85
+ permute.uniq!
86
+ permute
79
87
  end
80
88
  end
81
89
  end
@@ -4,11 +4,12 @@ require "sidekiq/client"
4
4
 
5
5
  module Sidekiq
6
6
  ##
7
- # Include this module in your worker class and you can easily create
7
+ # Include this module in your job class and you can easily create
8
8
  # asynchronous jobs:
9
9
  #
10
- # class HardWorker
11
- # include Sidekiq::Worker
10
+ # class HardJob
11
+ # include Sidekiq::Job
12
+ # sidekiq_options queue: 'critical', retry: 5
12
13
  #
13
14
  # def perform(*args)
14
15
  # # do some work
@@ -17,10 +18,30 @@ module Sidekiq
17
18
  #
18
19
  # Then in your Rails app, you can do this:
19
20
  #
20
- # HardWorker.perform_async(1, 2, 3)
21
+ # HardJob.perform_async(1, 2, 3)
21
22
  #
22
23
  # Note that perform_async is a class method, perform is an instance method.
23
- module Worker
24
+ #
25
+ # Sidekiq::Job also includes several APIs to provide compatibility with
26
+ # ActiveJob.
27
+ #
28
+ # class SomeJob
29
+ # include Sidekiq::Job
30
+ # queue_as :critical
31
+ #
32
+ # def perform(...)
33
+ # end
34
+ # end
35
+ #
36
+ # SomeJob.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
+ #
44
+ module Job
24
45
  ##
25
46
  # The Options module is extracted so we can include it in ActiveJob::Base
26
47
  # and allow native AJs to configure Sidekiq features/internals.
@@ -36,11 +57,11 @@ module Sidekiq
36
57
  ACCESSOR_MUTEX = Mutex.new
37
58
 
38
59
  ##
39
- # Allows customization for this type of Worker.
60
+ # Allows customization for this type of Job.
40
61
  # Legal options:
41
62
  #
42
63
  # queue - name of queue to use for this job type, default *default*
43
- # retry - enable retries for this Worker in case of error during execution,
64
+ # retry - enable retries for this Job in case of error during execution,
44
65
  # *true* to use the default or *Integer* count
45
66
  # backtrace - whether to save any error backtrace in the retry payload to display in web UI,
46
67
  # can be true, false or an integer number of lines to save, default *false*
@@ -61,7 +82,7 @@ module Sidekiq
61
82
  end
62
83
 
63
84
  def get_sidekiq_options # :nodoc:
64
- self.sidekiq_options_hash ||= Sidekiq.default_worker_options
85
+ self.sidekiq_options_hash ||= Sidekiq.default_job_options
65
86
  end
66
87
 
67
88
  def sidekiq_class_attribute(*attrs)
@@ -135,7 +156,7 @@ module Sidekiq
135
156
  attr_accessor :jid
136
157
 
137
158
  def self.included(base)
138
- raise ArgumentError, "Sidekiq::Worker cannot be included in an ActiveJob: #{base.name}" if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
159
+ raise ArgumentError, "Sidekiq::Job cannot be included in an ActiveJob: #{base.name}" if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
139
160
 
140
161
  base.include(Options)
141
162
  base.extend(ClassMethods)
@@ -147,49 +168,118 @@ module Sidekiq
147
168
 
148
169
  # This helper class encapsulates the set options for `set`, e.g.
149
170
  #
150
- # SomeWorker.set(queue: 'foo').perform_async(....)
171
+ # SomeJob.set(queue: 'foo').perform_async(....)
151
172
  #
152
173
  class Setter
174
+ include Sidekiq::JobUtil
175
+
153
176
  def initialize(klass, opts)
154
177
  @klass = klass
155
- @opts = opts
178
+ # NB: the internal hash always has stringified keys
179
+ @opts = opts.transform_keys(&:to_s)
180
+
181
+ # ActiveJob compatibility
182
+ interval = @opts.delete("wait_until") || @opts.delete("wait")
183
+ at(interval) if interval
156
184
  end
157
185
 
158
186
  def set(options)
159
- @opts.merge!(options)
187
+ hash = options.transform_keys(&:to_s)
188
+ interval = hash.delete("wait_until") || @opts.delete("wait")
189
+ @opts.merge!(hash)
190
+ at(interval) if interval
160
191
  self
161
192
  end
162
193
 
163
194
  def perform_async(*args)
164
- @klass.client_push(@opts.merge("args" => args, "class" => @klass))
195
+ if @opts["sync"] == true
196
+ perform_inline(*args)
197
+ else
198
+ @klass.client_push(@opts.merge("args" => args, "class" => @klass))
199
+ end
200
+ end
201
+
202
+ # Explicit inline execution of a job. Returns nil if the job did not
203
+ # execute, true otherwise.
204
+ def perform_inline(*args)
205
+ raw = @opts.merge("args" => args, "class" => @klass)
206
+
207
+ # validate and normalize payload
208
+ item = normalize_item(raw)
209
+ queue = item["queue"]
210
+
211
+ # run client-side middleware
212
+ cfg = Sidekiq.default_configuration
213
+ result = cfg.client_middleware.invoke(item["class"], item, queue, cfg.redis_pool) do
214
+ item
215
+ end
216
+ return nil unless result
217
+
218
+ # round-trip the payload via JSON
219
+ msg = Sidekiq.load_json(Sidekiq.dump_json(item))
220
+
221
+ # prepare the job instance
222
+ klass = Object.const_get(msg["class"])
223
+ job = klass.new
224
+ job.jid = msg["jid"]
225
+ job.bid = msg["bid"] if job.respond_to?(:bid)
226
+
227
+ # run the job through server-side middleware
228
+ result = cfg.server_middleware.invoke(job, msg, msg["queue"]) do
229
+ # perform it
230
+ job.perform(*msg["args"])
231
+ true
232
+ end
233
+ return nil unless result
234
+ # jobs do not return a result. they should store any
235
+ # modified state.
236
+ true
237
+ end
238
+ alias_method :perform_sync, :perform_inline
239
+
240
+ def perform_bulk(args, batch_size: 1_000)
241
+ client = @klass.build_client
242
+ result = args.each_slice(batch_size).flat_map do |slice|
243
+ client.push_bulk(@opts.merge("class" => @klass, "args" => slice))
244
+ end
245
+
246
+ result.is_a?(Enumerator::Lazy) ? result.force : result
165
247
  end
166
248
 
167
249
  # +interval+ must be a timestamp, numeric or something that acts
168
250
  # numeric (like an activesupport time interval).
169
251
  def perform_in(interval, *args)
252
+ at(interval).perform_async(*args)
253
+ end
254
+ alias_method :perform_at, :perform_in
255
+
256
+ private
257
+
258
+ def at(interval)
170
259
  int = interval.to_f
171
260
  now = Time.now.to_f
172
261
  ts = (int < 1_000_000_000 ? now + int : int)
173
-
174
- payload = @opts.merge("class" => @klass, "args" => args)
175
262
  # 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)
263
+ @opts["at"] = ts if ts > now
264
+ self
178
265
  end
179
- alias_method :perform_at, :perform_in
180
266
  end
181
267
 
182
268
  module ClassMethods
183
269
  def delay(*args)
184
- raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
270
+ raise ArgumentError, "Do not call .delay on a Sidekiq::Job class, call .perform_async"
185
271
  end
186
272
 
187
273
  def delay_for(*args)
188
- raise ArgumentError, "Do not call .delay_for on a Sidekiq::Worker class, call .perform_in"
274
+ raise ArgumentError, "Do not call .delay_for on a Sidekiq::Job class, call .perform_in"
189
275
  end
190
276
 
191
277
  def delay_until(*args)
192
- raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at"
278
+ raise ArgumentError, "Do not call .delay_until on a Sidekiq::Job class, call .perform_at"
279
+ end
280
+
281
+ def queue_as(q)
282
+ sidekiq_options("queue" => q.to_s)
193
283
  end
194
284
 
195
285
  def set(options)
@@ -197,7 +287,37 @@ module Sidekiq
197
287
  end
198
288
 
199
289
  def perform_async(*args)
200
- client_push("class" => self, "args" => args)
290
+ Setter.new(self, {}).perform_async(*args)
291
+ end
292
+
293
+ # Inline execution of job's perform method after passing through Sidekiq.client_middleware and Sidekiq.server_middleware
294
+ def perform_inline(*args)
295
+ Setter.new(self, {}).perform_inline(*args)
296
+ end
297
+ alias_method :perform_sync, :perform_inline
298
+
299
+ ##
300
+ # Push a large number of jobs to Redis, while limiting the batch of
301
+ # each job payload to 1,000. This method helps cut down on the number
302
+ # of round trips to Redis, which can increase the performance of enqueueing
303
+ # large numbers of jobs.
304
+ #
305
+ # +items+ must be an Array of Arrays.
306
+ #
307
+ # For finer-grained control, use `Sidekiq::Client.push_bulk` directly.
308
+ #
309
+ # Example (3 Redis round trips):
310
+ #
311
+ # SomeJob.perform_async(1)
312
+ # SomeJob.perform_async(2)
313
+ # SomeJob.perform_async(3)
314
+ #
315
+ # Would instead become (1 Redis round trip):
316
+ #
317
+ # SomeJob.perform_bulk([[1], [2], [3]])
318
+ #
319
+ def perform_bulk(*args, **kwargs)
320
+ Setter.new(self, {}).perform_bulk(*args, **kwargs)
201
321
  end
202
322
 
203
323
  # +interval+ must be a timestamp, numeric or something that acts
@@ -217,11 +337,11 @@ module Sidekiq
217
337
  alias_method :perform_at, :perform_in
218
338
 
219
339
  ##
220
- # Allows customization for this type of Worker.
340
+ # Allows customization for this type of Job.
221
341
  # Legal options:
222
342
  #
223
- # queue - use a named queue for this Worker, default 'default'
224
- # retry - enable the RetryJobs middleware for this Worker, *true* to use the default
343
+ # queue - use a named queue for this Job, default 'default'
344
+ # retry - enable the RetryJobs middleware for this Job, *true* to use the default
225
345
  # or *Integer* count
226
346
  # backtrace - whether to save any error backtrace in the retry payload to display in web UI,
227
347
  # can be true, false or an integer number of lines to save, default *false*
@@ -234,10 +354,24 @@ module Sidekiq
234
354
  end
235
355
 
236
356
  def client_push(item) # :nodoc:
237
- pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
238
- stringified_item = item.transform_keys(&:to_s)
357
+ raise ArgumentError, "Job payloads should contain no Symbols: #{item}" if item.any? { |k, v| k.is_a?(::Symbol) }
358
+
359
+ # allow the user to dynamically re-target jobs to another shard using the "pool" attribute
360
+ # FooJob.set(pool: SOME_POOL).perform_async
361
+ old = Thread.current[:sidekiq_redis_pool]
362
+ pool = item.delete("pool")
363
+ Thread.current[:sidekiq_redis_pool] = pool if pool
364
+ begin
365
+ build_client.push(item)
366
+ ensure
367
+ Thread.current[:sidekiq_redis_pool] = old
368
+ end
369
+ end
239
370
 
240
- Sidekiq::Client.new(pool).push(stringified_item)
371
+ def build_client # :nodoc:
372
+ pool = Thread.current[:sidekiq_redis_pool] || get_sidekiq_options["pool"] || Sidekiq.default_configuration.redis_pool
373
+ client_class = get_sidekiq_options["client_class"] || Sidekiq::Client
374
+ client_class.new(pool: pool)
241
375
  end
242
376
  end
243
377
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Sidekiq
4
4
  class JobLogger
5
- def initialize(logger = Sidekiq.logger)
5
+ def initialize(logger)
6
6
  @logger = logger
7
7
  end
8
8
 
@@ -12,46 +12,34 @@ module Sidekiq
12
12
 
13
13
  yield
14
14
 
15
- with_elapsed_time_context(start) do
16
- @logger.info("done")
17
- end
15
+ Sidekiq::Context.add(:elapsed, elapsed(start))
16
+ @logger.info("done")
18
17
  rescue Exception
19
- with_elapsed_time_context(start) do
20
- @logger.info("fail")
21
- end
18
+ Sidekiq::Context.add(:elapsed, elapsed(start))
19
+ @logger.info("fail")
22
20
 
23
21
  raise
24
22
  end
25
23
 
26
24
  def prepare(job_hash, &block)
27
- level = job_hash["log_level"]
28
- if level
29
- @logger.log_at(level) do
30
- Sidekiq::Context.with(job_hash_context(job_hash), &block)
31
- end
32
- else
33
- Sidekiq::Context.with(job_hash_context(job_hash), &block)
34
- end
35
- end
36
-
37
- def job_hash_context(job_hash)
38
25
  # If we're using a wrapper class, like ActiveJob, use the "wrapped"
39
26
  # attribute to expose the underlying thing.
40
27
  h = {
41
- class: job_hash["wrapped"] || job_hash["class"],
28
+ class: job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"],
42
29
  jid: job_hash["jid"]
43
30
  }
44
- h[:bid] = job_hash["bid"] if job_hash["bid"]
45
- h[:tags] = job_hash["tags"] if job_hash["tags"]
46
- h
47
- end
48
-
49
- def with_elapsed_time_context(start, &block)
50
- Sidekiq::Context.with(elapsed_time_context(start), &block)
51
- end
31
+ h[:bid] = job_hash["bid"] if job_hash.has_key?("bid")
32
+ h[:tags] = job_hash["tags"] if job_hash.has_key?("tags")
52
33
 
53
- def elapsed_time_context(start)
54
- {elapsed: elapsed(start).to_s}
34
+ Thread.current[:sidekiq_context] = h
35
+ level = job_hash["log_level"]
36
+ if level
37
+ @logger.log_at(level, &block)
38
+ else
39
+ yield
40
+ end
41
+ ensure
42
+ Thread.current[:sidekiq_context] = nil
55
43
  end
56
44
 
57
45
  private