sidekiq 6.2.2 → 6.4.1
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 +76 -1
- data/LICENSE +3 -3
- data/README.md +8 -3
- data/bin/sidekiq +3 -3
- data/bin/sidekiqload +56 -58
- data/bin/sidekiqmon +1 -1
- 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 +50 -43
- data/lib/sidekiq/cli.rb +23 -5
- data/lib/sidekiq/client.rb +22 -41
- data/lib/sidekiq/delay.rb +2 -0
- data/lib/sidekiq/fetch.rb +6 -5
- data/lib/sidekiq/job.rb +8 -3
- data/lib/sidekiq/job_logger.rb +15 -27
- data/lib/sidekiq/job_retry.rb +6 -4
- data/lib/sidekiq/job_util.rb +65 -0
- data/lib/sidekiq/launcher.rb +32 -28
- data/lib/sidekiq/logger.rb +4 -0
- data/lib/sidekiq/manager.rb +7 -9
- data/lib/sidekiq/middleware/current_attributes.rb +57 -0
- data/lib/sidekiq/paginator.rb +8 -8
- data/lib/sidekiq/rails.rb +11 -0
- data/lib/sidekiq/redis_connection.rb +4 -6
- data/lib/sidekiq/scheduled.rb +44 -15
- data/lib/sidekiq/util.rb +13 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/application.rb +7 -4
- data/lib/sidekiq/web/helpers.rb +2 -13
- data/lib/sidekiq/web.rb +3 -3
- data/lib/sidekiq/worker.rb +125 -7
- data/lib/sidekiq.rb +9 -1
- data/sidekiq.gemspec +1 -1
- data/web/assets/javascripts/application.js +82 -61
- data/web/assets/javascripts/dashboard.js +51 -51
- data/web/assets/stylesheets/application-dark.css +19 -23
- data/web/assets/stylesheets/application-rtl.css +0 -4
- data/web/assets/stylesheets/application.css +12 -108
- data/web/locales/en.yml +1 -1
- data/web/views/_footer.erb +1 -1
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/dashboard.erb +8 -8
- data/web/views/layout.erb +1 -1
- data/web/views/queue.erb +10 -10
- data/web/views/queues.erb +1 -1
- metadata +10 -8
- data/lib/generators/sidekiq/worker_generator.rb +0 -57
data/lib/sidekiq/cli.rb
CHANGED
@@ -46,7 +46,15 @@ module Sidekiq
|
|
46
46
|
# USR1 and USR2 don't work on the JVM
|
47
47
|
sigs << "USR2" if Sidekiq.pro? && !jruby?
|
48
48
|
sigs.each do |sig|
|
49
|
-
trap
|
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
|
50
58
|
self_write.puts(sig)
|
51
59
|
end
|
52
60
|
rescue ArgumentError
|
@@ -107,8 +115,8 @@ module Sidekiq
|
|
107
115
|
begin
|
108
116
|
launcher.run
|
109
117
|
|
110
|
-
while (readable_io =
|
111
|
-
signal = readable_io.
|
118
|
+
while (readable_io = self_read.wait_readable)
|
119
|
+
signal = readable_io.gets.strip
|
112
120
|
handle_signal(signal)
|
113
121
|
end
|
114
122
|
rescue Interrupt
|
@@ -372,7 +380,9 @@ module Sidekiq
|
|
372
380
|
end
|
373
381
|
|
374
382
|
def parse_config(path)
|
375
|
-
|
383
|
+
erb = ERB.new(File.read(path))
|
384
|
+
erb.filename = File.expand_path(path)
|
385
|
+
opts = load_yaml(erb.result) || {}
|
376
386
|
|
377
387
|
if opts.respond_to? :deep_symbolize_keys!
|
378
388
|
opts.deep_symbolize_keys!
|
@@ -388,6 +398,14 @@ module Sidekiq
|
|
388
398
|
opts
|
389
399
|
end
|
390
400
|
|
401
|
+
def load_yaml(src)
|
402
|
+
if Psych::VERSION > "4.0"
|
403
|
+
YAML.safe_load(src, permitted_classes: [Symbol], aliases: true)
|
404
|
+
else
|
405
|
+
YAML.load(src)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
391
409
|
def parse_queues(opts, queues_and_weights)
|
392
410
|
queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
|
393
411
|
end
|
@@ -396,7 +414,7 @@ module Sidekiq
|
|
396
414
|
opts[:queues] ||= []
|
397
415
|
opts[:strict] = true if opts[:strict].nil?
|
398
416
|
raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
|
399
|
-
[weight.to_i, 1].max.times { opts[:queues] << queue }
|
417
|
+
[weight.to_i, 1].max.times { opts[:queues] << queue.to_s }
|
400
418
|
opts[:strict] = false if weight.to_i > 0
|
401
419
|
end
|
402
420
|
|
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
|
#
|
@@ -95,12 +98,12 @@ module Sidekiq
|
|
95
98
|
return [] if args.empty? # no jobs to push
|
96
99
|
|
97
100
|
at = items.delete("at")
|
98
|
-
raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all?(Numeric))
|
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) })
|
99
102
|
raise ArgumentError, "Job 'at' Array must have same size as 'args' Array" if at.is_a?(Array) && at.size != args.size
|
100
103
|
|
101
104
|
normed = normalize_item(items)
|
102
105
|
payloads = args.map.with_index { |job_args, index|
|
103
|
-
copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12)
|
106
|
+
copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12))
|
104
107
|
copy["at"] = (at.is_a?(Array) ? at[index] : at) if at
|
105
108
|
|
106
109
|
result = process_single(items["class"], copy)
|
@@ -186,8 +189,23 @@ module Sidekiq
|
|
186
189
|
|
187
190
|
def raw_push(payloads)
|
188
191
|
@redis_pool.with do |conn|
|
189
|
-
|
190
|
-
|
192
|
+
retryable = true
|
193
|
+
begin
|
194
|
+
conn.pipelined do |pipeline|
|
195
|
+
atomic_push(pipeline, payloads)
|
196
|
+
end
|
197
|
+
rescue Redis::BaseError => ex
|
198
|
+
# 2550 Failover can cause the server to become a replica, need
|
199
|
+
# to disconnect and reopen the socket to get back to the primary.
|
200
|
+
# 4495 Use the same logic if we have a "Not enough replicas" error from the primary
|
201
|
+
# 4985 Use the same logic when a blocking command is force-unblocked
|
202
|
+
# The retry logic is copied from sidekiq.rb
|
203
|
+
if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
|
204
|
+
conn.disconnect!
|
205
|
+
retryable = false
|
206
|
+
retry
|
207
|
+
end
|
208
|
+
raise
|
191
209
|
end
|
192
210
|
end
|
193
211
|
true
|
@@ -218,42 +236,5 @@ module Sidekiq
|
|
218
236
|
item
|
219
237
|
end
|
220
238
|
end
|
221
|
-
|
222
|
-
def validate(item)
|
223
|
-
raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: `#{item}`") unless item.is_a?(Hash) && item.key?("class") && item.key?("args")
|
224
|
-
raise(ArgumentError, "Job args must be an Array: `#{item}`") unless item["args"].is_a?(Array)
|
225
|
-
raise(ArgumentError, "Job class must be either a Class or String representation of the class name: `#{item}`") unless item["class"].is_a?(Class) || item["class"].is_a?(String)
|
226
|
-
raise(ArgumentError, "Job 'at' must be a Numeric timestamp: `#{item}`") if item.key?("at") && !item["at"].is_a?(Numeric)
|
227
|
-
raise(ArgumentError, "Job tags must be an Array: `#{item}`") if item["tags"] && !item["tags"].is_a?(Array)
|
228
|
-
end
|
229
|
-
|
230
|
-
def normalize_item(item)
|
231
|
-
validate(item)
|
232
|
-
# 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']
|
233
|
-
|
234
|
-
# merge in the default sidekiq_options for the item's class and/or wrapped element
|
235
|
-
# this allows ActiveJobs to control sidekiq_options too.
|
236
|
-
defaults = normalized_hash(item["class"])
|
237
|
-
defaults = defaults.merge(item["wrapped"].get_sidekiq_options) if item["wrapped"].respond_to?("get_sidekiq_options")
|
238
|
-
item = defaults.merge(item)
|
239
|
-
|
240
|
-
raise(ArgumentError, "Job must include a valid queue name") if item["queue"].nil? || item["queue"] == ""
|
241
|
-
|
242
|
-
item["class"] = item["class"].to_s
|
243
|
-
item["queue"] = item["queue"].to_s
|
244
|
-
item["jid"] ||= SecureRandom.hex(12)
|
245
|
-
item["created_at"] ||= Time.now.to_f
|
246
|
-
|
247
|
-
item
|
248
|
-
end
|
249
|
-
|
250
|
-
def normalized_hash(item_class)
|
251
|
-
if item_class.is_a?(Class)
|
252
|
-
raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") unless item_class.respond_to?("get_sidekiq_options")
|
253
|
-
item_class.get_sidekiq_options
|
254
|
-
else
|
255
|
-
Sidekiq.default_worker_options
|
256
|
-
end
|
257
|
-
end
|
258
239
|
end
|
259
240
|
end
|
data/lib/sidekiq/delay.rb
CHANGED
data/lib/sidekiq/fetch.rb
CHANGED
@@ -59,9 +59,9 @@ module Sidekiq
|
|
59
59
|
end
|
60
60
|
|
61
61
|
Sidekiq.redis do |conn|
|
62
|
-
conn.pipelined do
|
62
|
+
conn.pipelined do |pipeline|
|
63
63
|
jobs_to_requeue.each do |queue, jobs|
|
64
|
-
|
64
|
+
pipeline.rpush(queue, jobs)
|
65
65
|
end
|
66
66
|
end
|
67
67
|
end
|
@@ -79,9 +79,10 @@ module Sidekiq
|
|
79
79
|
if @strictly_ordered_queues
|
80
80
|
@queues
|
81
81
|
else
|
82
|
-
|
83
|
-
|
84
|
-
|
82
|
+
permute = @queues.shuffle
|
83
|
+
permute.uniq!
|
84
|
+
permute << TIMEOUT
|
85
|
+
permute
|
85
86
|
end
|
86
87
|
end
|
87
88
|
end
|
data/lib/sidekiq/job.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
require "sidekiq/worker"
|
2
2
|
|
3
3
|
module Sidekiq
|
4
|
-
# Sidekiq::Job is a new alias for Sidekiq::Worker
|
5
|
-
#
|
6
|
-
#
|
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.
|
7
12
|
Job = Worker
|
8
13
|
end
|
data/lib/sidekiq/job_logger.rb
CHANGED
@@ -12,46 +12,34 @@ module Sidekiq
|
|
12
12
|
|
13
13
|
yield
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
end
|
15
|
+
Sidekiq::Context.add(:elapsed, elapsed(start))
|
16
|
+
@logger.info("done")
|
18
17
|
rescue Exception
|
19
|
-
|
20
|
-
|
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
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
|
45
|
-
h[:tags] = job_hash["tags"] if job_hash
|
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
|
-
|
54
|
-
|
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
|
data/lib/sidekiq/job_retry.rb
CHANGED
@@ -34,9 +34,10 @@ module Sidekiq
|
|
34
34
|
# The job will be retried this number of times before giving up. (If simply
|
35
35
|
# 'true', Sidekiq retries 25 times)
|
36
36
|
#
|
37
|
-
#
|
37
|
+
# Relevant options for job retries:
|
38
38
|
#
|
39
|
-
# * 'queue' - the queue
|
39
|
+
# * 'queue' - the queue for the initial job
|
40
|
+
# * 'retry_queue' - if job retries should be pushed to a different (e.g. lower priority) queue
|
40
41
|
# * 'retry_count' - number of times we've retried so far.
|
41
42
|
# * 'error_message' - the message from the exception
|
42
43
|
# * 'error_class' - the exception class
|
@@ -52,11 +53,12 @@ module Sidekiq
|
|
52
53
|
#
|
53
54
|
# Sidekiq.options[:max_retries] = 7
|
54
55
|
#
|
55
|
-
# or limit the number of retries for a particular worker
|
56
|
+
# or limit the number of retries for a particular worker and send retries to
|
57
|
+
# a low priority queue with:
|
56
58
|
#
|
57
59
|
# class MyWorker
|
58
60
|
# include Sidekiq::Worker
|
59
|
-
# sidekiq_options :
|
61
|
+
# sidekiq_options retry: 10, retry_queue: 'low'
|
60
62
|
# end
|
61
63
|
#
|
62
64
|
class JobRetry
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
require "time"
|
3
|
+
|
4
|
+
module Sidekiq
|
5
|
+
module JobUtil
|
6
|
+
# These functions encapsulate various job utilities.
|
7
|
+
# They must be simple and free from side effects.
|
8
|
+
|
9
|
+
def validate(item)
|
10
|
+
raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: `#{item}`") unless item.is_a?(Hash) && item.key?("class") && item.key?("args")
|
11
|
+
raise(ArgumentError, "Job args must be an Array: `#{item}`") unless item["args"].is_a?(Array)
|
12
|
+
raise(ArgumentError, "Job class must be either a Class or String representation of the class name: `#{item}`") unless item["class"].is_a?(Class) || item["class"].is_a?(String)
|
13
|
+
raise(ArgumentError, "Job 'at' must be a Numeric timestamp: `#{item}`") if item.key?("at") && !item["at"].is_a?(Numeric)
|
14
|
+
raise(ArgumentError, "Job tags must be an Array: `#{item}`") if item["tags"] && !item["tags"].is_a?(Array)
|
15
|
+
|
16
|
+
if Sidekiq.options[:on_complex_arguments] == :raise
|
17
|
+
msg = <<~EOM
|
18
|
+
Job arguments to #{item["class"]} must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices.
|
19
|
+
To disable this error, remove `Sidekiq.strict_args!` from your initializer.
|
20
|
+
EOM
|
21
|
+
raise(ArgumentError, msg) unless json_safe?(item)
|
22
|
+
elsif Sidekiq.options[:on_complex_arguments] == :warn
|
23
|
+
Sidekiq.logger.warn <<~EOM unless json_safe?(item)
|
24
|
+
Job arguments to #{item["class"]} do not serialize to JSON safely. This will raise an error in
|
25
|
+
Sidekiq 7.0. See https://github.com/mperham/sidekiq/wiki/Best-Practices or raise an error today
|
26
|
+
by calling `Sidekiq.strict_args!` during Sidekiq initialization.
|
27
|
+
EOM
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def normalize_item(item)
|
32
|
+
validate(item)
|
33
|
+
|
34
|
+
# merge in the default sidekiq_options for the item's class and/or wrapped element
|
35
|
+
# this allows ActiveJobs to control sidekiq_options too.
|
36
|
+
defaults = normalized_hash(item["class"])
|
37
|
+
defaults = defaults.merge(item["wrapped"].get_sidekiq_options) if item["wrapped"].respond_to?(:get_sidekiq_options)
|
38
|
+
item = defaults.merge(item)
|
39
|
+
|
40
|
+
raise(ArgumentError, "Job must include a valid queue name") if item["queue"].nil? || item["queue"] == ""
|
41
|
+
|
42
|
+
item["class"] = item["class"].to_s
|
43
|
+
item["queue"] = item["queue"].to_s
|
44
|
+
item["jid"] ||= SecureRandom.hex(12)
|
45
|
+
item["created_at"] ||= Time.now.to_f
|
46
|
+
|
47
|
+
item
|
48
|
+
end
|
49
|
+
|
50
|
+
def normalized_hash(item_class)
|
51
|
+
if item_class.is_a?(Class)
|
52
|
+
raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") unless item_class.respond_to?(:get_sidekiq_options)
|
53
|
+
item_class.get_sidekiq_options
|
54
|
+
else
|
55
|
+
Sidekiq.default_worker_options
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def json_safe?(item)
|
62
|
+
JSON.parse(JSON.dump(item["args"])) == item["args"]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/sidekiq/launcher.rb
CHANGED
@@ -69,10 +69,12 @@ module Sidekiq
|
|
69
69
|
|
70
70
|
private unless $TESTING
|
71
71
|
|
72
|
+
BEAT_PAUSE = 5
|
73
|
+
|
72
74
|
def start_heartbeat
|
73
75
|
loop do
|
74
76
|
heartbeat
|
75
|
-
sleep
|
77
|
+
sleep BEAT_PAUSE
|
76
78
|
end
|
77
79
|
Sidekiq.logger.info("Heartbeat stopping...")
|
78
80
|
end
|
@@ -82,9 +84,9 @@ module Sidekiq
|
|
82
84
|
# Note we don't stop the heartbeat thread; if the process
|
83
85
|
# doesn't actually exit, it'll reappear in the Web UI.
|
84
86
|
Sidekiq.redis do |conn|
|
85
|
-
conn.pipelined do
|
86
|
-
|
87
|
-
|
87
|
+
conn.pipelined do |pipeline|
|
88
|
+
pipeline.srem("processes", identity)
|
89
|
+
pipeline.unlink("#{identity}:workers")
|
88
90
|
end
|
89
91
|
end
|
90
92
|
rescue
|
@@ -105,14 +107,14 @@ module Sidekiq
|
|
105
107
|
nowdate = Time.now.utc.strftime("%Y-%m-%d")
|
106
108
|
begin
|
107
109
|
Sidekiq.redis do |conn|
|
108
|
-
conn.pipelined do
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
110
|
+
conn.pipelined do |pipeline|
|
111
|
+
pipeline.incrby("stat:processed", procd)
|
112
|
+
pipeline.incrby("stat:processed:#{nowdate}", procd)
|
113
|
+
pipeline.expire("stat:processed:#{nowdate}", STATS_TTL)
|
114
|
+
|
115
|
+
pipeline.incrby("stat:failed", fails)
|
116
|
+
pipeline.incrby("stat:failed:#{nowdate}", fails)
|
117
|
+
pipeline.expire("stat:failed:#{nowdate}", STATS_TTL)
|
116
118
|
end
|
117
119
|
end
|
118
120
|
rescue => ex
|
@@ -136,20 +138,20 @@ module Sidekiq
|
|
136
138
|
nowdate = Time.now.utc.strftime("%Y-%m-%d")
|
137
139
|
|
138
140
|
Sidekiq.redis do |conn|
|
139
|
-
conn.multi do
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
conn.multi do |transaction|
|
142
|
+
transaction.incrby("stat:processed", procd)
|
143
|
+
transaction.incrby("stat:processed:#{nowdate}", procd)
|
144
|
+
transaction.expire("stat:processed:#{nowdate}", STATS_TTL)
|
143
145
|
|
144
|
-
|
145
|
-
|
146
|
-
|
146
|
+
transaction.incrby("stat:failed", fails)
|
147
|
+
transaction.incrby("stat:failed:#{nowdate}", fails)
|
148
|
+
transaction.expire("stat:failed:#{nowdate}", STATS_TTL)
|
147
149
|
|
148
|
-
|
150
|
+
transaction.unlink(workers_key)
|
149
151
|
curstate.each_pair do |tid, hash|
|
150
|
-
|
152
|
+
transaction.hset(workers_key, tid, Sidekiq.dump_json(hash))
|
151
153
|
end
|
152
|
-
|
154
|
+
transaction.expire(workers_key, 60)
|
153
155
|
end
|
154
156
|
end
|
155
157
|
|
@@ -159,17 +161,17 @@ module Sidekiq
|
|
159
161
|
kb = memory_usage(::Process.pid)
|
160
162
|
|
161
163
|
_, exists, _, _, msg = Sidekiq.redis { |conn|
|
162
|
-
conn.multi {
|
163
|
-
|
164
|
-
|
165
|
-
|
164
|
+
conn.multi { |transaction|
|
165
|
+
transaction.sadd("processes", key)
|
166
|
+
transaction.exists?(key)
|
167
|
+
transaction.hmset(key, "info", to_json,
|
166
168
|
"busy", curstate.size,
|
167
169
|
"beat", Time.now.to_f,
|
168
170
|
"rtt_us", rtt,
|
169
171
|
"quiet", @done,
|
170
172
|
"rss", kb)
|
171
|
-
|
172
|
-
|
173
|
+
transaction.expire(key, 60)
|
174
|
+
transaction.rpop("#{key}-signals")
|
173
175
|
}
|
174
176
|
}
|
175
177
|
|
@@ -211,6 +213,8 @@ module Sidekiq
|
|
211
213
|
Your Redis network connection is performing extremely poorly.
|
212
214
|
Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
|
213
215
|
Ensure Redis is running in the same AZ or datacenter as Sidekiq.
|
216
|
+
If these values are close to 100,000, that means your Sidekiq process may be
|
217
|
+
CPU overloaded; see https://github.com/mperham/sidekiq/discussions/5039
|
214
218
|
EOM
|
215
219
|
RTT_READINGS.reset
|
216
220
|
end
|
data/lib/sidekiq/logger.rb
CHANGED
data/lib/sidekiq/manager.rb
CHANGED
@@ -55,9 +55,6 @@ module Sidekiq
|
|
55
55
|
fire_event(:quiet, reverse: true)
|
56
56
|
end
|
57
57
|
|
58
|
-
# hack for quicker development / testing environment #2774
|
59
|
-
PAUSE_TIME = $stdout.tty? ? 0.1 : 0.5
|
60
|
-
|
61
58
|
def stop(deadline)
|
62
59
|
quiet
|
63
60
|
fire_event(:shutdown, reverse: true)
|
@@ -69,12 +66,7 @@ module Sidekiq
|
|
69
66
|
return if @workers.empty?
|
70
67
|
|
71
68
|
logger.info { "Pausing to allow workers to finish..." }
|
72
|
-
|
73
|
-
while remaining > PAUSE_TIME
|
74
|
-
return if @workers.empty?
|
75
|
-
sleep PAUSE_TIME
|
76
|
-
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
77
|
-
end
|
69
|
+
wait_for(deadline) { @workers.empty? }
|
78
70
|
return if @workers.empty?
|
79
71
|
|
80
72
|
hard_shutdown
|
@@ -130,6 +122,12 @@ module Sidekiq
|
|
130
122
|
cleanup.each do |processor|
|
131
123
|
processor.kill
|
132
124
|
end
|
125
|
+
|
126
|
+
# when this method returns, we immediately call `exit` which may not give
|
127
|
+
# the remaining threads time to run `ensure` blocks, etc. We pause here up
|
128
|
+
# to 3 seconds to give threads a minimal amount of time to run `ensure` blocks.
|
129
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + 3
|
130
|
+
wait_for(deadline) { @workers.empty? }
|
133
131
|
end
|
134
132
|
end
|
135
133
|
end
|
@@ -0,0 +1,57 @@
|
|
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
|
+
# @example
|
11
|
+
#
|
12
|
+
# # in your initializer
|
13
|
+
# require "sidekiq/middleware/current_attributes"
|
14
|
+
# Sidekiq::CurrentAttributes.persist(Myapp::Current)
|
15
|
+
#
|
16
|
+
module CurrentAttributes
|
17
|
+
class Save
|
18
|
+
def initialize(cattr)
|
19
|
+
@klass = cattr
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(_, job, _, _)
|
23
|
+
attrs = @klass.attributes
|
24
|
+
if job.has_key?("cattr")
|
25
|
+
job["cattr"].merge!(attrs)
|
26
|
+
else
|
27
|
+
job["cattr"] = attrs
|
28
|
+
end
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Load
|
34
|
+
def initialize(cattr)
|
35
|
+
@klass = cattr
|
36
|
+
end
|
37
|
+
|
38
|
+
def call(_, job, _, &block)
|
39
|
+
if job.has_key?("cattr")
|
40
|
+
@klass.set(job["cattr"], &block)
|
41
|
+
else
|
42
|
+
yield
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.persist(klass)
|
48
|
+
Sidekiq.configure_client do |config|
|
49
|
+
config.client_middleware.add Save, klass
|
50
|
+
end
|
51
|
+
Sidekiq.configure_server do |config|
|
52
|
+
config.client_middleware.add Save, klass
|
53
|
+
config.server_middleware.add Load, klass
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/sidekiq/paginator.rb
CHANGED
@@ -16,22 +16,22 @@ module Sidekiq
|
|
16
16
|
|
17
17
|
case type
|
18
18
|
when "zset"
|
19
|
-
total_size, items = conn.multi {
|
20
|
-
|
19
|
+
total_size, items = conn.multi { |transaction|
|
20
|
+
transaction.zcard(key)
|
21
21
|
if rev
|
22
|
-
|
22
|
+
transaction.zrevrange(key, starting, ending, with_scores: true)
|
23
23
|
else
|
24
|
-
|
24
|
+
transaction.zrange(key, starting, ending, with_scores: true)
|
25
25
|
end
|
26
26
|
}
|
27
27
|
[current_page, total_size, items]
|
28
28
|
when "list"
|
29
|
-
total_size, items = conn.multi {
|
30
|
-
|
29
|
+
total_size, items = conn.multi { |transaction|
|
30
|
+
transaction.llen(key)
|
31
31
|
if rev
|
32
|
-
|
32
|
+
transaction.lrange(key, -ending - 1, -starting - 1)
|
33
33
|
else
|
34
|
-
|
34
|
+
transaction.lrange(key, starting, ending)
|
35
35
|
end
|
36
36
|
}
|
37
37
|
items.reverse! if rev
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -37,6 +37,17 @@ module Sidekiq
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
initializer "sidekiq.rails_logger" do
|
41
|
+
Sidekiq.configure_server do |_|
|
42
|
+
# This is the integration code necessary so that if code uses `Rails.logger.info "Hello"`,
|
43
|
+
# it will appear in the Sidekiq console with all of the job context. See #5021 and
|
44
|
+
# https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84
|
45
|
+
unless ::Rails.logger == ::Sidekiq.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
46
|
+
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(::Sidekiq.logger))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
40
51
|
# This hook happens after all initializers are run, just before returning
|
41
52
|
# from config/environment.rb back to sidekiq/cli.rb.
|
42
53
|
#
|
@@ -94,12 +94,10 @@ module Sidekiq
|
|
94
94
|
def log_info(options)
|
95
95
|
redacted = "REDACTED"
|
96
96
|
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
|
101
|
-
keys = options.keys
|
102
|
-
keys.delete(:ssl_params)
|
97
|
+
# Deep clone so we can muck with these options all we want and exclude
|
98
|
+
# params from dump-and-load that may contain objects that Marshal is
|
99
|
+
# unable to safely dump.
|
100
|
+
keys = options.keys - [:logger, :ssl_params]
|
103
101
|
scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
|
104
102
|
if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
|
105
103
|
uri.password = redacted
|