sidekiq 6.0.0 → 6.4.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Changes.md +290 -2
- data/LICENSE +3 -3
- data/README.md +7 -9
- data/bin/sidekiq +26 -2
- data/bin/sidekiqload +8 -4
- data/bin/sidekiqmon +4 -5
- data/lib/generators/sidekiq/job_generator.rb +57 -0
- data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
- data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
- data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
- data/lib/sidekiq/api.rb +222 -145
- data/lib/sidekiq/cli.rb +67 -28
- data/lib/sidekiq/client.rb +17 -34
- data/lib/sidekiq/delay.rb +2 -0
- data/lib/sidekiq/extensions/action_mailer.rb +5 -4
- data/lib/sidekiq/extensions/active_record.rb +6 -5
- data/lib/sidekiq/extensions/class_methods.rb +7 -6
- data/lib/sidekiq/extensions/generic_proxy.rb +5 -3
- data/lib/sidekiq/fetch.rb +36 -27
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +13 -5
- data/lib/sidekiq/job_retry.rb +33 -21
- data/lib/sidekiq/job_util.rb +65 -0
- data/lib/sidekiq/launcher.rb +110 -28
- data/lib/sidekiq/logger.rb +109 -12
- data/lib/sidekiq/manager.rb +10 -12
- data/lib/sidekiq/middleware/chain.rb +17 -6
- data/lib/sidekiq/middleware/current_attributes.rb +57 -0
- data/lib/sidekiq/monitor.rb +3 -18
- data/lib/sidekiq/paginator.rb +7 -2
- data/lib/sidekiq/processor.rb +22 -24
- data/lib/sidekiq/rails.rb +27 -18
- data/lib/sidekiq/redis_connection.rb +19 -13
- data/lib/sidekiq/scheduled.rb +48 -12
- data/lib/sidekiq/sd_notify.rb +149 -0
- data/lib/sidekiq/systemd.rb +24 -0
- data/lib/sidekiq/testing.rb +14 -4
- data/lib/sidekiq/util.rb +40 -1
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +2 -2
- data/lib/sidekiq/web/application.rb +41 -31
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +51 -33
- data/lib/sidekiq/web/router.rb +6 -5
- data/lib/sidekiq/web.rb +37 -73
- data/lib/sidekiq/worker.rb +133 -16
- data/lib/sidekiq.rb +29 -8
- data/sidekiq.gemspec +13 -6
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +83 -64
- data/web/assets/javascripts/dashboard.js +53 -53
- data/web/assets/stylesheets/application-dark.css +143 -0
- data/web/assets/stylesheets/application-rtl.css +0 -4
- data/web/assets/stylesheets/application.css +43 -232
- data/web/locales/ar.yml +8 -2
- data/web/locales/de.yml +14 -2
- data/web/locales/en.yml +6 -1
- data/web/locales/es.yml +18 -2
- data/web/locales/fr.yml +10 -3
- data/web/locales/ja.yml +5 -0
- data/web/locales/lt.yml +83 -0
- data/web/locales/pl.yml +4 -4
- data/web/locales/ru.yml +4 -0
- data/web/locales/vi.yml +83 -0
- data/web/views/_footer.erb +1 -1
- data/web/views/_job_info.erb +3 -2
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +54 -20
- data/web/views/dashboard.erb +22 -14
- data/web/views/dead.erb +3 -3
- data/web/views/layout.erb +3 -1
- data/web/views/morgue.erb +9 -6
- data/web/views/queue.erb +19 -10
- data/web/views/queues.erb +10 -2
- data/web/views/retries.erb +11 -8
- data/web/views/retry.erb +3 -3
- data/web/views/scheduled.erb +5 -2
- metadata +34 -54
- data/.circleci/config.yml +0 -61
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -11
- data/.gitignore +0 -13
- data/.standard.yml +0 -20
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/5.0-Upgrade.md +0 -56
- data/6.0-Upgrade.md +0 -70
- data/COMM-LICENSE +0 -97
- data/Ent-2.0-Upgrade.md +0 -37
- data/Ent-Changes.md +0 -250
- data/Gemfile +0 -24
- data/Gemfile.lock +0 -196
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-3.0-Upgrade.md +0 -44
- data/Pro-4.0-Upgrade.md +0 -35
- data/Pro-5.0-Upgrade.md +0 -25
- data/Pro-Changes.md +0 -768
- data/Rakefile +0 -10
- data/code_of_conduct.md +0 -50
- data/lib/generators/sidekiq/worker_generator.rb +0 -47
data/lib/sidekiq/cli.rb
CHANGED
@@ -33,17 +33,29 @@ module Sidekiq
|
|
33
33
|
# Code within this method is not tested because it alters
|
34
34
|
# global process state irreversibly. PRs which improve the
|
35
35
|
# test coverage of Sidekiq::CLI are welcomed.
|
36
|
-
def run
|
37
|
-
|
36
|
+
def run(boot_app: true)
|
37
|
+
boot_application if boot_app
|
38
|
+
|
38
39
|
if environment == "development" && $stdout.tty? && Sidekiq.log_formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
|
39
40
|
print_banner
|
40
41
|
end
|
42
|
+
logger.info "Booted Rails #{::Rails.version} application in #{environment} environment" if rails_app?
|
41
43
|
|
42
44
|
self_read, self_write = IO.pipe
|
43
45
|
sigs = %w[INT TERM TTIN TSTP]
|
46
|
+
# USR1 and USR2 don't work on the JVM
|
47
|
+
sigs << "USR2" if Sidekiq.pro? && !jruby?
|
44
48
|
sigs.each do |sig|
|
45
|
-
trap
|
46
|
-
|
49
|
+
old_handler = Signal.trap(sig) do
|
50
|
+
if old_handler.respond_to?(:call)
|
51
|
+
begin
|
52
|
+
old_handler.call
|
53
|
+
rescue Exception => exc
|
54
|
+
# signal handlers can't use Logger so puts only
|
55
|
+
puts ["Error in #{sig} handler", exc].inspect
|
56
|
+
end
|
57
|
+
end
|
58
|
+
self_write.puts(sig)
|
47
59
|
end
|
48
60
|
rescue ArgumentError
|
49
61
|
puts "Signal #{sig} not supported"
|
@@ -51,12 +63,25 @@ module Sidekiq
|
|
51
63
|
|
52
64
|
logger.info "Running in #{RUBY_DESCRIPTION}"
|
53
65
|
logger.info Sidekiq::LICENSE
|
54
|
-
logger.info "Upgrade to Sidekiq Pro for more features and support:
|
66
|
+
logger.info "Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org" unless defined?(::Sidekiq::Pro)
|
55
67
|
|
56
68
|
# touch the connection pool so it is created before we
|
57
69
|
# fire startup and start multithreading.
|
58
|
-
|
59
|
-
|
70
|
+
info = Sidekiq.redis_info
|
71
|
+
ver = info["redis_version"]
|
72
|
+
raise "You are connecting to Redis v#{ver}, Sidekiq requires Redis v4.0.0 or greater" if ver < "4"
|
73
|
+
|
74
|
+
maxmemory_policy = info["maxmemory_policy"]
|
75
|
+
if maxmemory_policy != "noeviction"
|
76
|
+
logger.warn <<~EOM
|
77
|
+
|
78
|
+
|
79
|
+
WARNING: Your Redis instance will evict Sidekiq data under heavy load.
|
80
|
+
The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
|
81
|
+
See: https://github.com/mperham/sidekiq/wiki/Using-Redis#memory
|
82
|
+
|
83
|
+
EOM
|
84
|
+
end
|
60
85
|
|
61
86
|
# Since the user can pass us a connection pool explicitly in the initializer, we
|
62
87
|
# need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
|
@@ -160,17 +185,14 @@ module Sidekiq
|
|
160
185
|
Sidekiq.logger.warn "<no backtrace available>"
|
161
186
|
end
|
162
187
|
end
|
163
|
-
}
|
188
|
+
}
|
164
189
|
}
|
190
|
+
UNHANDLED_SIGNAL_HANDLER = ->(cli) { Sidekiq.logger.info "No signal handler registered, ignoring" }
|
191
|
+
SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER
|
165
192
|
|
166
193
|
def handle_signal(sig)
|
167
194
|
Sidekiq.logger.debug "Got #{sig} signal"
|
168
|
-
|
169
|
-
if handy
|
170
|
-
handy.call(self)
|
171
|
-
else
|
172
|
-
Sidekiq.logger.info { "No signal handler for #{sig}" }
|
173
|
-
end
|
195
|
+
SIGNAL_HANDLERS[sig].call(self)
|
174
196
|
end
|
175
197
|
|
176
198
|
private
|
@@ -182,7 +204,11 @@ module Sidekiq
|
|
182
204
|
end
|
183
205
|
|
184
206
|
def set_environment(cli_env)
|
185
|
-
|
207
|
+
# See #984 for discussion.
|
208
|
+
# APP_ENV is now the preferred ENV term since it is not tech-specific.
|
209
|
+
# Both Sinatra 2.0+ and Sidekiq support this term.
|
210
|
+
# RAILS_ENV and RACK_ENV are there for legacy support.
|
211
|
+
@environment = cli_env || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
186
212
|
end
|
187
213
|
|
188
214
|
def symbolize_keys_deep!(hash)
|
@@ -204,7 +230,7 @@ module Sidekiq
|
|
204
230
|
|
205
231
|
# check config file presence
|
206
232
|
if opts[:config_file]
|
207
|
-
|
233
|
+
unless File.exist?(opts[:config_file])
|
208
234
|
raise ArgumentError, "No such file #{opts[:config_file]}"
|
209
235
|
end
|
210
236
|
else
|
@@ -224,8 +250,7 @@ module Sidekiq
|
|
224
250
|
opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
|
225
251
|
|
226
252
|
# set defaults
|
227
|
-
opts[:queues] =
|
228
|
-
opts[:strict] = true if opts[:strict].nil?
|
253
|
+
opts[:queues] = ["default"] if opts[:queues].nil?
|
229
254
|
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
|
230
255
|
|
231
256
|
# merge with defaults
|
@@ -236,7 +261,7 @@ module Sidekiq
|
|
236
261
|
Sidekiq.options
|
237
262
|
end
|
238
263
|
|
239
|
-
def
|
264
|
+
def boot_application
|
240
265
|
ENV["RACK_ENV"] = ENV["RAILS_ENV"] = environment
|
241
266
|
|
242
267
|
if File.directory?(options[:require])
|
@@ -283,8 +308,13 @@ module Sidekiq
|
|
283
308
|
|
284
309
|
def parse_options(argv)
|
285
310
|
opts = {}
|
311
|
+
@parser = option_parser(opts)
|
312
|
+
@parser.parse!(argv)
|
313
|
+
opts
|
314
|
+
end
|
286
315
|
|
287
|
-
|
316
|
+
def option_parser(opts)
|
317
|
+
parser = OptionParser.new { |o|
|
288
318
|
o.on "-c", "--concurrency INT", "processor threads to use" do |arg|
|
289
319
|
opts[:concurrency] = Integer(arg)
|
290
320
|
end
|
@@ -336,15 +366,13 @@ module Sidekiq
|
|
336
366
|
end
|
337
367
|
}
|
338
368
|
|
339
|
-
|
340
|
-
|
341
|
-
logger.info
|
369
|
+
parser.banner = "sidekiq [options]"
|
370
|
+
parser.on_tail "-h", "--help", "Show help" do
|
371
|
+
logger.info parser
|
342
372
|
die 1
|
343
373
|
end
|
344
374
|
|
345
|
-
|
346
|
-
|
347
|
-
opts
|
375
|
+
parser
|
348
376
|
end
|
349
377
|
|
350
378
|
def initialize_logger
|
@@ -352,7 +380,9 @@ module Sidekiq
|
|
352
380
|
end
|
353
381
|
|
354
382
|
def parse_config(path)
|
355
|
-
|
383
|
+
erb = ERB.new(File.read(path))
|
384
|
+
erb.filename = File.expand_path(path)
|
385
|
+
opts = YAML.load(erb.result) || {}
|
356
386
|
|
357
387
|
if opts.respond_to? :deep_symbolize_keys!
|
358
388
|
opts.deep_symbolize_keys!
|
@@ -361,6 +391,8 @@ module Sidekiq
|
|
361
391
|
end
|
362
392
|
|
363
393
|
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
394
|
+
opts.delete(:strict)
|
395
|
+
|
364
396
|
parse_queues(opts, opts.delete(:queues) || [])
|
365
397
|
|
366
398
|
opts
|
@@ -372,9 +404,16 @@ module Sidekiq
|
|
372
404
|
|
373
405
|
def parse_queue(opts, queue, weight = nil)
|
374
406
|
opts[:queues] ||= []
|
407
|
+
opts[:strict] = true if opts[:strict].nil?
|
375
408
|
raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
|
376
|
-
[weight.to_i, 1].max.times { opts[:queues] << queue }
|
409
|
+
[weight.to_i, 1].max.times { opts[:queues] << queue.to_s }
|
377
410
|
opts[:strict] = false if weight.to_i > 0
|
378
411
|
end
|
412
|
+
|
413
|
+
def rails_app?
|
414
|
+
defined?(::Rails) && ::Rails.respond_to?(:application)
|
415
|
+
end
|
379
416
|
end
|
380
417
|
end
|
418
|
+
|
419
|
+
require "sidekiq/systemd"
|
data/lib/sidekiq/client.rb
CHANGED
@@ -2,9 +2,12 @@
|
|
2
2
|
|
3
3
|
require "securerandom"
|
4
4
|
require "sidekiq/middleware/chain"
|
5
|
+
require "sidekiq/job_util"
|
5
6
|
|
6
7
|
module Sidekiq
|
7
8
|
class Client
|
9
|
+
include Sidekiq::JobUtil
|
10
|
+
|
8
11
|
##
|
9
12
|
# Define client-side middleware:
|
10
13
|
#
|
@@ -19,7 +22,7 @@ module Sidekiq
|
|
19
22
|
#
|
20
23
|
def middleware(&block)
|
21
24
|
@chain ||= Sidekiq.client_middleware
|
22
|
-
if
|
25
|
+
if block
|
23
26
|
@chain = @chain.dup
|
24
27
|
yield @chain
|
25
28
|
end
|
@@ -90,13 +93,19 @@ module Sidekiq
|
|
90
93
|
# Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less
|
91
94
|
# than the number given if the middleware stopped processing for one or more jobs.
|
92
95
|
def push_bulk(items)
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
+
args = items["args"]
|
97
|
+
raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless args.is_a?(Array) && args.all?(Array)
|
98
|
+
return [] if args.empty? # no jobs to push
|
99
|
+
|
100
|
+
at = items.delete("at")
|
101
|
+
raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all? { |entry| entry.is_a?(Numeric) })
|
102
|
+
raise ArgumentError, "Job 'at' Array must have same size as 'args' Array" if at.is_a?(Array) && at.size != args.size
|
96
103
|
|
97
104
|
normed = normalize_item(items)
|
98
|
-
payloads =
|
99
|
-
copy = normed.merge("args" =>
|
105
|
+
payloads = args.map.with_index { |job_args, index|
|
106
|
+
copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12), "enqueued_at" => Time.now.to_f)
|
107
|
+
copy["at"] = (at.is_a?(Array) ? at[index] : at) if at
|
108
|
+
|
100
109
|
result = process_single(items["class"], copy)
|
101
110
|
result || nil
|
102
111
|
}.compact
|
@@ -180,7 +189,7 @@ module Sidekiq
|
|
180
189
|
|
181
190
|
def raw_push(payloads)
|
182
191
|
@redis_pool.with do |conn|
|
183
|
-
conn.
|
192
|
+
conn.pipelined do
|
184
193
|
atomic_push(conn, payloads)
|
185
194
|
end
|
186
195
|
end
|
@@ -188,7 +197,7 @@ module Sidekiq
|
|
188
197
|
end
|
189
198
|
|
190
199
|
def atomic_push(conn, payloads)
|
191
|
-
if payloads.first
|
200
|
+
if payloads.first.key?("at")
|
192
201
|
conn.zadd("schedule", payloads.map { |hash|
|
193
202
|
at = hash.delete("at").to_s
|
194
203
|
[at, Sidekiq.dump_json(hash)]
|
@@ -212,31 +221,5 @@ module Sidekiq
|
|
212
221
|
item
|
213
222
|
end
|
214
223
|
end
|
215
|
-
|
216
|
-
def normalize_item(item)
|
217
|
-
raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash) && item.key?("class") && item.key?("args")
|
218
|
-
raise(ArgumentError, "Job args must be an Array") unless item["args"].is_a?(Array)
|
219
|
-
raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item["class"].is_a?(Class) || item["class"].is_a?(String)
|
220
|
-
raise(ArgumentError, "Job 'at' must be a Numeric timestamp") if item.key?("at") && !item["at"].is_a?(Numeric)
|
221
|
-
# raise(ArgumentError, "Arguments must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices") unless JSON.load(JSON.dump(item['args'])) == item['args']
|
222
|
-
|
223
|
-
normalized_hash(item["class"])
|
224
|
-
.each { |key, value| item[key] = value if item[key].nil? }
|
225
|
-
|
226
|
-
item["class"] = item["class"].to_s
|
227
|
-
item["queue"] = item["queue"].to_s
|
228
|
-
item["jid"] ||= SecureRandom.hex(12)
|
229
|
-
item["created_at"] ||= Time.now.to_f
|
230
|
-
item
|
231
|
-
end
|
232
|
-
|
233
|
-
def normalized_hash(item_class)
|
234
|
-
if item_class.is_a?(Class)
|
235
|
-
raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") unless item_class.respond_to?("get_sidekiq_options")
|
236
|
-
item_class.get_sidekiq_options
|
237
|
-
else
|
238
|
-
Sidekiq.default_worker_options
|
239
|
-
end
|
240
|
-
end
|
241
224
|
end
|
242
225
|
end
|
data/lib/sidekiq/delay.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
module Sidekiq
|
4
4
|
module Extensions
|
5
5
|
def self.enable_delay!
|
6
|
+
Sidekiq.logger.error "Sidekiq's Delayed Extensions will be removed in Sidekiq 7.0. #{caller(1..1).first}"
|
7
|
+
|
6
8
|
if defined?(::ActiveSupport)
|
7
9
|
require "sidekiq/extensions/active_record"
|
8
10
|
require "sidekiq/extensions/action_mailer"
|
@@ -5,9 +5,10 @@ require "sidekiq/extensions/generic_proxy"
|
|
5
5
|
module Sidekiq
|
6
6
|
module Extensions
|
7
7
|
##
|
8
|
-
# Adds
|
9
|
-
# delivery to Sidekiq.
|
8
|
+
# Adds +delay+, +delay_for+ and +delay_until+ methods to ActionMailer to offload arbitrary email
|
9
|
+
# delivery to Sidekiq.
|
10
10
|
#
|
11
|
+
# @example
|
11
12
|
# UserMailer.delay.send_welcome_email(new_user)
|
12
13
|
# UserMailer.delay_for(5.days).send_welcome_email(new_user)
|
13
14
|
# UserMailer.delay_until(5.days.from_now).send_welcome_email(new_user)
|
@@ -15,8 +16,8 @@ module Sidekiq
|
|
15
16
|
include Sidekiq::Worker
|
16
17
|
|
17
18
|
def perform(yml)
|
18
|
-
(target, method_name, args) = YAML.load(yml)
|
19
|
-
msg = target.public_send(method_name, *args)
|
19
|
+
(target, method_name, args, kwargs) = YAML.load(yml)
|
20
|
+
msg = kwargs.empty? ? target.public_send(method_name, *args) : target.public_send(method_name, *args, **kwargs)
|
20
21
|
# The email method can return nil, which causes ActionMailer to return
|
21
22
|
# an undeliverable empty message.
|
22
23
|
if msg
|
@@ -5,10 +5,11 @@ require "sidekiq/extensions/generic_proxy"
|
|
5
5
|
module Sidekiq
|
6
6
|
module Extensions
|
7
7
|
##
|
8
|
-
# Adds
|
9
|
-
# execution to Sidekiq.
|
8
|
+
# Adds +delay+, +delay_for+ and +delay_until+ methods to ActiveRecord to offload instance method
|
9
|
+
# execution to Sidekiq.
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# @example
|
12
|
+
# User.recent_signups.each { |user| user.delay.mark_as_awesome }
|
12
13
|
#
|
13
14
|
# Please note, this is not recommended as this will serialize the entire
|
14
15
|
# object to Redis. Your Sidekiq jobs should pass IDs, not entire instances.
|
@@ -17,8 +18,8 @@ module Sidekiq
|
|
17
18
|
include Sidekiq::Worker
|
18
19
|
|
19
20
|
def perform(yml)
|
20
|
-
(target, method_name, args) = YAML.load(yml)
|
21
|
-
target.__send__(method_name, *args)
|
21
|
+
(target, method_name, args, kwargs) = YAML.load(yml)
|
22
|
+
kwargs.empty? ? target.__send__(method_name, *args) : target.__send__(method_name, *args, **kwargs)
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
@@ -5,18 +5,19 @@ require "sidekiq/extensions/generic_proxy"
|
|
5
5
|
module Sidekiq
|
6
6
|
module Extensions
|
7
7
|
##
|
8
|
-
# Adds
|
9
|
-
# execution to Sidekiq.
|
8
|
+
# Adds `delay`, `delay_for` and `delay_until` methods to all Classes to offload class method
|
9
|
+
# execution to Sidekiq.
|
10
10
|
#
|
11
|
-
#
|
12
|
-
#
|
11
|
+
# @example
|
12
|
+
# User.delay.delete_inactive
|
13
|
+
# Wikipedia.delay.download_changes_for(Date.today)
|
13
14
|
#
|
14
15
|
class DelayedClass
|
15
16
|
include Sidekiq::Worker
|
16
17
|
|
17
18
|
def perform(yml)
|
18
|
-
(target, method_name, args) = YAML.load(yml)
|
19
|
-
target.__send__(method_name, *args)
|
19
|
+
(target, method_name, args, kwargs) = YAML.load(yml)
|
20
|
+
kwargs.empty? ? target.__send__(method_name, *args) : target.__send__(method_name, *args, **kwargs)
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
@@ -13,18 +13,20 @@ module Sidekiq
|
|
13
13
|
@opts = options
|
14
14
|
end
|
15
15
|
|
16
|
-
def method_missing(name, *args)
|
16
|
+
def method_missing(name, *args, **kwargs)
|
17
17
|
# Sidekiq has a limitation in that its message must be JSON.
|
18
18
|
# JSON can't round trip real Ruby objects so we use YAML to
|
19
19
|
# serialize the objects to a String. The YAML will be converted
|
20
20
|
# to JSON and then deserialized on the other side back into a
|
21
21
|
# Ruby object.
|
22
|
-
obj = [@target, name, args]
|
22
|
+
obj = [@target, name, args, kwargs]
|
23
23
|
marshalled = ::YAML.dump(obj)
|
24
24
|
if marshalled.size > SIZE_LIMIT
|
25
25
|
::Sidekiq.logger.warn { "#{@target}.#{name} job argument is #{marshalled.bytesize} bytes, you should refactor it to reduce the size" }
|
26
26
|
end
|
27
|
-
@performable.client_push({"class" => @performable,
|
27
|
+
@performable.client_push({"class" => @performable,
|
28
|
+
"args" => [marshalled],
|
29
|
+
"display_class" => "#{@target}.#{name}"}.merge(@opts))
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
data/lib/sidekiq/fetch.rb
CHANGED
@@ -14,61 +14,54 @@ module Sidekiq
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def queue_name
|
17
|
-
queue.
|
17
|
+
queue.delete_prefix("queue:")
|
18
18
|
end
|
19
19
|
|
20
20
|
def requeue
|
21
21
|
Sidekiq.redis do |conn|
|
22
|
-
conn.rpush(
|
22
|
+
conn.rpush(queue, job)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
}
|
26
26
|
|
27
27
|
def initialize(options)
|
28
|
-
|
29
|
-
@
|
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
32
|
if @strictly_ordered_queues
|
31
|
-
@queues
|
33
|
+
@queues.uniq!
|
32
34
|
@queues << TIMEOUT
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
38
|
def retrieve_work
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
# data from the first queue that has pending elements. We
|
44
|
-
# recreate the queue command each time we invoke Redis#brpop
|
45
|
-
# to honor weights and avoid queue starvation.
|
46
|
-
def queues_cmd
|
47
|
-
if @strictly_ordered_queues
|
48
|
-
@queues
|
49
|
-
else
|
50
|
-
queues = @queues.shuffle.uniq
|
51
|
-
queues << TIMEOUT
|
52
|
-
queues
|
39
|
+
qs = queues_cmd
|
40
|
+
# 4825 Sidekiq Pro with all queues paused will return an
|
41
|
+
# empty set of queues with a trailing TIMEOUT value.
|
42
|
+
if qs.size <= 1
|
43
|
+
sleep(TIMEOUT)
|
44
|
+
return nil
|
53
45
|
end
|
46
|
+
|
47
|
+
work = Sidekiq.redis { |conn| conn.brpop(*qs) }
|
48
|
+
UnitOfWork.new(*work) if work
|
54
49
|
end
|
55
50
|
|
56
|
-
|
57
|
-
# an instance method will make it async to the Fetcher actor
|
58
|
-
def self.bulk_requeue(inprogress, options)
|
51
|
+
def bulk_requeue(inprogress, options)
|
59
52
|
return if inprogress.empty?
|
60
53
|
|
61
54
|
Sidekiq.logger.debug { "Re-queueing terminated jobs" }
|
62
55
|
jobs_to_requeue = {}
|
63
56
|
inprogress.each do |unit_of_work|
|
64
|
-
jobs_to_requeue[unit_of_work.
|
65
|
-
jobs_to_requeue[unit_of_work.
|
57
|
+
jobs_to_requeue[unit_of_work.queue] ||= []
|
58
|
+
jobs_to_requeue[unit_of_work.queue] << unit_of_work.job
|
66
59
|
end
|
67
60
|
|
68
61
|
Sidekiq.redis do |conn|
|
69
62
|
conn.pipelined do
|
70
63
|
jobs_to_requeue.each do |queue, jobs|
|
71
|
-
conn.rpush(
|
64
|
+
conn.rpush(queue, jobs)
|
72
65
|
end
|
73
66
|
end
|
74
67
|
end
|
@@ -76,5 +69,21 @@ module Sidekiq
|
|
76
69
|
rescue => ex
|
77
70
|
Sidekiq.logger.warn("Failed to requeue #{inprogress.size} jobs: #{ex.message}")
|
78
71
|
end
|
72
|
+
|
73
|
+
# Creating the Redis#brpop command takes into account any
|
74
|
+
# configured queue weights. By default Redis#brpop returns
|
75
|
+
# data from the first queue that has pending elements. We
|
76
|
+
# recreate the queue command each time we invoke Redis#brpop
|
77
|
+
# to honor weights and avoid queue starvation.
|
78
|
+
def queues_cmd
|
79
|
+
if @strictly_ordered_queues
|
80
|
+
@queues
|
81
|
+
else
|
82
|
+
permute = @queues.shuffle
|
83
|
+
permute.uniq!
|
84
|
+
permute << TIMEOUT
|
85
|
+
permute
|
86
|
+
end
|
87
|
+
end
|
79
88
|
end
|
80
89
|
end
|
data/lib/sidekiq/job.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "sidekiq/worker"
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
# Sidekiq::Job is a new alias for Sidekiq::Worker as of Sidekiq 6.3.0.
|
5
|
+
# Use `include Sidekiq::Job` rather than `include Sidekiq::Worker`.
|
6
|
+
#
|
7
|
+
# The term "worker" is too generic and overly confusing, used in several
|
8
|
+
# different contexts meaning different things. Many people call a Sidekiq
|
9
|
+
# process a "worker". Some people call the thread that executes jobs a
|
10
|
+
# "worker". This change brings Sidekiq closer to ActiveJob where your job
|
11
|
+
# classes extend ApplicationJob.
|
12
|
+
Job = Worker
|
13
|
+
end
|
data/lib/sidekiq/job_logger.rb
CHANGED
@@ -23,23 +23,31 @@ module Sidekiq
|
|
23
23
|
raise
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
27
|
-
|
26
|
+
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
|
28
35
|
end
|
29
36
|
|
30
37
|
def job_hash_context(job_hash)
|
31
38
|
# If we're using a wrapper class, like ActiveJob, use the "wrapped"
|
32
39
|
# attribute to expose the underlying thing.
|
33
40
|
h = {
|
34
|
-
class: job_hash["wrapped"] || job_hash["class"],
|
35
|
-
jid: job_hash["jid"]
|
41
|
+
class: job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"],
|
42
|
+
jid: job_hash["jid"]
|
36
43
|
}
|
37
44
|
h[:bid] = job_hash["bid"] if job_hash["bid"]
|
45
|
+
h[:tags] = job_hash["tags"] if job_hash["tags"]
|
38
46
|
h
|
39
47
|
end
|
40
48
|
|
41
49
|
def with_elapsed_time_context(start, &block)
|
42
|
-
|
50
|
+
Sidekiq::Context.with(elapsed_time_context(start), &block)
|
43
51
|
end
|
44
52
|
|
45
53
|
def elapsed_time_context(start)
|