sidekiq 7.0.1 → 7.0.8
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 +69 -9
- data/README.md +29 -22
- data/bin/sidekiqload +186 -109
- data/bin/sidekiqmon +3 -0
- data/lib/sidekiq/api.rb +43 -4
- data/lib/sidekiq/capsule.rb +17 -0
- data/lib/sidekiq/cli.rb +3 -2
- data/lib/sidekiq/client.rb +1 -1
- data/lib/sidekiq/component.rb +2 -0
- data/lib/sidekiq/config.rb +2 -3
- data/lib/sidekiq/deploy.rb +3 -3
- data/lib/sidekiq/embedded.rb +1 -1
- data/lib/sidekiq/fetch.rb +3 -5
- data/lib/sidekiq/job.rb +2 -2
- data/lib/sidekiq/job_logger.rb +1 -1
- data/lib/sidekiq/job_retry.rb +5 -4
- data/lib/sidekiq/job_util.rb +48 -14
- data/lib/sidekiq/launcher.rb +13 -7
- data/lib/sidekiq/metrics/query.rb +1 -1
- data/lib/sidekiq/metrics/tracking.rb +2 -0
- data/lib/sidekiq/middleware/chain.rb +12 -9
- data/lib/sidekiq/middleware/current_attributes.rb +5 -7
- data/lib/sidekiq/monitor.rb +17 -4
- data/lib/sidekiq/paginator.rb +2 -2
- data/lib/sidekiq/processor.rb +4 -1
- data/lib/sidekiq/rails.rb +2 -1
- data/lib/sidekiq/redis_client_adapter.rb +3 -6
- data/lib/sidekiq/scheduled.rb +1 -1
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/application.rb +20 -5
- data/lib/sidekiq/web/helpers.rb +15 -7
- data/lib/sidekiq/web.rb +4 -0
- data/sidekiq.gemspec +11 -22
- data/web/assets/javascripts/application.js +18 -0
- data/web/assets/javascripts/metrics.js +30 -2
- data/web/assets/stylesheets/application-dark.css +4 -0
- data/web/assets/stylesheets/application.css +3 -3
- data/web/locales/da.yml +11 -4
- data/web/locales/ja.yml +3 -1
- data/web/views/_footer.erb +2 -2
- data/web/views/_job_info.erb +18 -2
- data/web/views/_metrics_period_select.erb +12 -0
- data/web/views/_paging.erb +2 -0
- data/web/views/busy.erb +37 -26
- data/web/views/metrics.erb +6 -4
- data/web/views/metrics_for_job.erb +9 -7
- data/web/views/morgue.erb +5 -9
- data/web/views/queue.erb +10 -14
- data/web/views/queues.erb +3 -1
- data/web/views/retries.erb +5 -9
- data/web/views/scheduled.erb +12 -13
- metadata +17 -26
data/lib/sidekiq/api.rb
CHANGED
@@ -418,6 +418,10 @@ module Sidekiq
|
|
418
418
|
self["jid"]
|
419
419
|
end
|
420
420
|
|
421
|
+
def bid
|
422
|
+
self["bid"]
|
423
|
+
end
|
424
|
+
|
421
425
|
def enqueued_at
|
422
426
|
self["enqueued_at"] ? Time.at(self["enqueued_at"]).utc : nil
|
423
427
|
end
|
@@ -698,7 +702,7 @@ module Sidekiq
|
|
698
702
|
def find_job(jid)
|
699
703
|
Sidekiq.redis do |conn|
|
700
704
|
conn.zscan(name, match: "*#{jid}*", count: 100) do |entry, score|
|
701
|
-
job =
|
705
|
+
job = Sidekiq.load_json(entry)
|
702
706
|
matched = job["jid"] == jid
|
703
707
|
return SortedEntry.new(self, score, entry) if matched
|
704
708
|
end
|
@@ -824,6 +828,24 @@ module Sidekiq
|
|
824
828
|
class ProcessSet
|
825
829
|
include Enumerable
|
826
830
|
|
831
|
+
def self.[](identity)
|
832
|
+
exists, (info, busy, beat, quiet, rss, rtt_us) = Sidekiq.redis { |conn|
|
833
|
+
conn.multi { |transaction|
|
834
|
+
transaction.sismember("processes", identity)
|
835
|
+
transaction.hmget(identity, "info", "busy", "beat", "quiet", "rss", "rtt_us")
|
836
|
+
}
|
837
|
+
}
|
838
|
+
|
839
|
+
return nil if exists == 0 || info.nil?
|
840
|
+
|
841
|
+
hash = Sidekiq.load_json(info)
|
842
|
+
Process.new(hash.merge("busy" => busy.to_i,
|
843
|
+
"beat" => beat.to_f,
|
844
|
+
"quiet" => quiet,
|
845
|
+
"rss" => rss.to_i,
|
846
|
+
"rtt_us" => rtt_us.to_i))
|
847
|
+
end
|
848
|
+
|
827
849
|
# :nodoc:
|
828
850
|
# @api private
|
829
851
|
def initialize(clean_plz = true)
|
@@ -872,7 +894,7 @@ module Sidekiq
|
|
872
894
|
end
|
873
895
|
}
|
874
896
|
|
875
|
-
result.each do |info, busy,
|
897
|
+
result.each do |info, busy, beat, quiet, rss, rtt_us|
|
876
898
|
# If a process is stopped between when we query Redis for `procs` and
|
877
899
|
# when we query for `result`, we will have an item in `result` that is
|
878
900
|
# composed of `nil` values.
|
@@ -880,10 +902,10 @@ module Sidekiq
|
|
880
902
|
|
881
903
|
hash = Sidekiq.load_json(info)
|
882
904
|
yield Process.new(hash.merge("busy" => busy.to_i,
|
883
|
-
"beat" =>
|
905
|
+
"beat" => beat.to_f,
|
884
906
|
"quiet" => quiet,
|
885
907
|
"rss" => rss.to_i,
|
886
|
-
"rtt_us" =>
|
908
|
+
"rtt_us" => rtt_us.to_i))
|
887
909
|
end
|
888
910
|
end
|
889
911
|
|
@@ -939,6 +961,7 @@ module Sidekiq
|
|
939
961
|
# 'busy' => 10,
|
940
962
|
# 'beat' => <last heartbeat>,
|
941
963
|
# 'identity' => <unique string identifying the process>,
|
964
|
+
# 'embedded' => true,
|
942
965
|
# }
|
943
966
|
class Process
|
944
967
|
# :nodoc:
|
@@ -967,11 +990,25 @@ module Sidekiq
|
|
967
990
|
self["queues"]
|
968
991
|
end
|
969
992
|
|
993
|
+
def weights
|
994
|
+
self["weights"]
|
995
|
+
end
|
996
|
+
|
997
|
+
def version
|
998
|
+
self["version"]
|
999
|
+
end
|
1000
|
+
|
1001
|
+
def embedded?
|
1002
|
+
self["embedded"]
|
1003
|
+
end
|
1004
|
+
|
970
1005
|
# Signal this process to stop processing new jobs.
|
971
1006
|
# It will continue to execute jobs it has already fetched.
|
972
1007
|
# This method is *asynchronous* and it can take 5-10
|
973
1008
|
# seconds for the process to quiet.
|
974
1009
|
def quiet!
|
1010
|
+
raise "Can't quiet an embedded process" if embedded?
|
1011
|
+
|
975
1012
|
signal("TSTP")
|
976
1013
|
end
|
977
1014
|
|
@@ -980,6 +1017,8 @@ module Sidekiq
|
|
980
1017
|
# This method is *asynchronous* and it can take 5-10
|
981
1018
|
# seconds for the process to start shutting down.
|
982
1019
|
def stop!
|
1020
|
+
raise "Can't stop an embedded process" if embedded?
|
1021
|
+
|
983
1022
|
signal("TERM")
|
984
1023
|
end
|
985
1024
|
|
data/lib/sidekiq/capsule.rb
CHANGED
@@ -21,12 +21,16 @@ module Sidekiq
|
|
21
21
|
attr_reader :name
|
22
22
|
attr_reader :queues
|
23
23
|
attr_accessor :concurrency
|
24
|
+
attr_reader :mode
|
25
|
+
attr_reader :weights
|
24
26
|
|
25
27
|
def initialize(name, config)
|
26
28
|
@name = name
|
27
29
|
@config = config
|
28
30
|
@queues = ["default"]
|
31
|
+
@weights = {"default" => 0}
|
29
32
|
@concurrency = config[:concurrency]
|
33
|
+
@mode = :strict
|
30
34
|
end
|
31
35
|
|
32
36
|
def fetcher
|
@@ -41,15 +45,28 @@ module Sidekiq
|
|
41
45
|
fetcher&.bulk_requeue([])
|
42
46
|
end
|
43
47
|
|
48
|
+
# Sidekiq checks queues in three modes:
|
49
|
+
# - :strict - all queues have 0 weight and are checked strictly in order
|
50
|
+
# - :weighted - queues have arbitrary weight between 1 and N
|
51
|
+
# - :random - all queues have weight of 1
|
44
52
|
def queues=(val)
|
53
|
+
@weights = {}
|
45
54
|
@queues = Array(val).each_with_object([]) do |qstr, memo|
|
46
55
|
arr = qstr
|
47
56
|
arr = qstr.split(",") if qstr.is_a?(String)
|
48
57
|
name, weight = arr
|
58
|
+
@weights[name] = weight.to_i
|
49
59
|
[weight.to_i, 1].max.times do
|
50
60
|
memo << name
|
51
61
|
end
|
52
62
|
end
|
63
|
+
@mode = if @weights.values.all?(&:zero?)
|
64
|
+
:strict
|
65
|
+
elsif @weights.values.all? { |x| x == 1 }
|
66
|
+
:random
|
67
|
+
else
|
68
|
+
:weighted
|
69
|
+
end
|
53
70
|
end
|
54
71
|
|
55
72
|
# Allow the middleware to be different per-capsule.
|
data/lib/sidekiq/cli.rb
CHANGED
@@ -77,13 +77,14 @@ module Sidekiq # :nodoc:
|
|
77
77
|
raise "You are connecting to Redis #{ver}, Sidekiq requires Redis 6.2.0 or greater" if ver < Gem::Version.new("6.2.0")
|
78
78
|
|
79
79
|
maxmemory_policy = info["maxmemory_policy"]
|
80
|
-
if maxmemory_policy != "noeviction"
|
80
|
+
if maxmemory_policy != "noeviction" && maxmemory_policy != ""
|
81
|
+
# Redis Enterprise Cloud returns "" for their policy 😳
|
81
82
|
logger.warn <<~EOM
|
82
83
|
|
83
84
|
|
84
85
|
WARNING: Your Redis instance will evict Sidekiq data under heavy load.
|
85
86
|
The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
|
86
|
-
See: https://github.com/
|
87
|
+
See: https://github.com/sidekiq/sidekiq/wiki/Using-Redis#memory
|
87
88
|
|
88
89
|
EOM
|
89
90
|
end
|
data/lib/sidekiq/client.rb
CHANGED
@@ -189,7 +189,7 @@ module Sidekiq
|
|
189
189
|
def enqueue_to_in(queue, interval, klass, *args)
|
190
190
|
int = interval.to_f
|
191
191
|
now = Time.now.to_f
|
192
|
-
ts = (int < 1_000_000_000 ? now + int : int)
|
192
|
+
ts = ((int < 1_000_000_000) ? now + int : int)
|
193
193
|
|
194
194
|
item = {"class" => klass, "args" => args, "at" => ts, "queue" => queue}
|
195
195
|
item.delete("at") if ts <= now
|
data/lib/sidekiq/component.rb
CHANGED
data/lib/sidekiq/config.rb
CHANGED
@@ -129,12 +129,12 @@ module Sidekiq
|
|
129
129
|
def new_redis_pool(size, name = "unset")
|
130
130
|
# connection pool is lazy, it will not create connections unless you actually need them
|
131
131
|
# so don't be skimpy!
|
132
|
-
RedisConnection.create(
|
132
|
+
RedisConnection.create({size: size, logger: logger, pool_name: name}.merge(@redis_config))
|
133
133
|
end
|
134
134
|
|
135
135
|
def redis_info
|
136
136
|
redis do |conn|
|
137
|
-
conn.
|
137
|
+
conn.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h }
|
138
138
|
rescue RedisClientAdapter::CommandError => ex
|
139
139
|
# 2850 return fake version when INFO command has (probably) been renamed
|
140
140
|
raise unless /unknown command/.match?(ex.message)
|
@@ -248,7 +248,6 @@ module Sidekiq
|
|
248
248
|
return
|
249
249
|
end
|
250
250
|
|
251
|
-
logger.extend(Sidekiq::LoggingUtils)
|
252
251
|
@logger = logger
|
253
252
|
end
|
254
253
|
|
data/lib/sidekiq/deploy.rb
CHANGED
@@ -21,15 +21,15 @@ module Sidekiq
|
|
21
21
|
}
|
22
22
|
|
23
23
|
def self.mark!(label = nil)
|
24
|
-
label
|
25
|
-
Sidekiq::Deploy.new.mark(label: label)
|
24
|
+
Sidekiq::Deploy.new.mark!(label: label)
|
26
25
|
end
|
27
26
|
|
28
27
|
def initialize(pool = Sidekiq::RedisConnection.create)
|
29
28
|
@pool = pool
|
30
29
|
end
|
31
30
|
|
32
|
-
def mark(at: Time.now, label:
|
31
|
+
def mark!(at: Time.now, label: nil)
|
32
|
+
label ||= LABEL_MAKER.call
|
33
33
|
# we need to round the timestamp so that we gracefully
|
34
34
|
# handle an very common error in marking deploys:
|
35
35
|
# having every process mark its deploy, leading
|
data/lib/sidekiq/embedded.rb
CHANGED
@@ -49,7 +49,7 @@ module Sidekiq
|
|
49
49
|
|
50
50
|
WARNING: Your Redis instance will evict Sidekiq data under heavy load.
|
51
51
|
The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
|
52
|
-
See: https://github.com/
|
52
|
+
See: https://github.com/sidekiq/sidekiq/wiki/Using-Redis#memory
|
53
53
|
|
54
54
|
EOM
|
55
55
|
end
|
data/lib/sidekiq/fetch.rb
CHANGED
@@ -30,11 +30,9 @@ module Sidekiq # :nodoc:
|
|
30
30
|
def initialize(cap)
|
31
31
|
raise ArgumentError, "missing queue list" unless cap.queues
|
32
32
|
@config = cap
|
33
|
-
@strictly_ordered_queues =
|
33
|
+
@strictly_ordered_queues = cap.mode == :strict
|
34
34
|
@queues = config.queues.map { |q| "queue:#{q}" }
|
35
|
-
if @strictly_ordered_queues
|
36
|
-
@queues.uniq!
|
37
|
-
end
|
35
|
+
@queues.uniq! if @strictly_ordered_queues
|
38
36
|
end
|
39
37
|
|
40
38
|
def retrieve_work
|
@@ -46,7 +44,7 @@ module Sidekiq # :nodoc:
|
|
46
44
|
return nil
|
47
45
|
end
|
48
46
|
|
49
|
-
queue, job = redis { |conn| conn.blocking_call(
|
47
|
+
queue, job = redis { |conn| conn.blocking_call(TIMEOUT + 1, "brpop", *qs, TIMEOUT) }
|
50
48
|
UnitOfWork.new(queue, job, config) if queue
|
51
49
|
end
|
52
50
|
|
data/lib/sidekiq/job.rb
CHANGED
@@ -258,7 +258,7 @@ module Sidekiq
|
|
258
258
|
def at(interval)
|
259
259
|
int = interval.to_f
|
260
260
|
now = Time.now.to_f
|
261
|
-
ts = (int < 1_000_000_000 ? now + int : int)
|
261
|
+
ts = ((int < 1_000_000_000) ? now + int : int)
|
262
262
|
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
263
263
|
@opts["at"] = ts if ts > now
|
264
264
|
self
|
@@ -325,7 +325,7 @@ module Sidekiq
|
|
325
325
|
def perform_in(interval, *args)
|
326
326
|
int = interval.to_f
|
327
327
|
now = Time.now.to_f
|
328
|
-
ts = (int < 1_000_000_000 ? now + int : int)
|
328
|
+
ts = ((int < 1_000_000_000) ? now + int : int)
|
329
329
|
|
330
330
|
item = {"class" => self, "args" => args}
|
331
331
|
|
data/lib/sidekiq/job_logger.rb
CHANGED
data/lib/sidekiq/job_retry.rb
CHANGED
@@ -49,7 +49,7 @@ module Sidekiq
|
|
49
49
|
# The default number of retries is 25 which works out to about 3 weeks
|
50
50
|
# You can change the default maximum number of retries in your initializer:
|
51
51
|
#
|
52
|
-
# Sidekiq.
|
52
|
+
# Sidekiq.default_configuration[:max_retries] = 7
|
53
53
|
#
|
54
54
|
# or limit the number of retries for a particular job and send retries to
|
55
55
|
# a low priority queue with:
|
@@ -171,7 +171,7 @@ module Sidekiq
|
|
171
171
|
# Goodbye dear message, you (re)tried your best I'm sure.
|
172
172
|
return retries_exhausted(jobinst, msg, exception) if count >= max_retry_attempts
|
173
173
|
|
174
|
-
strategy, delay = delay_for(jobinst, count, exception)
|
174
|
+
strategy, delay = delay_for(jobinst, count, exception, msg)
|
175
175
|
case strategy
|
176
176
|
when :discard
|
177
177
|
return # poof!
|
@@ -190,17 +190,18 @@ module Sidekiq
|
|
190
190
|
end
|
191
191
|
|
192
192
|
# returns (strategy, seconds)
|
193
|
-
def delay_for(jobinst, count, exception)
|
193
|
+
def delay_for(jobinst, count, exception, msg)
|
194
194
|
rv = begin
|
195
195
|
# sidekiq_retry_in can return two different things:
|
196
196
|
# 1. When to retry next, as an integer of seconds
|
197
197
|
# 2. A symbol which re-routes the job elsewhere, e.g. :discard, :kill, :default
|
198
|
-
jobinst&.sidekiq_retry_in_block&.call(count, exception)
|
198
|
+
jobinst&.sidekiq_retry_in_block&.call(count, exception, msg)
|
199
199
|
rescue Exception => e
|
200
200
|
handle_exception(e, {context: "Failure scheduling retry using the defined `sidekiq_retry_in` in #{jobinst.class.name}, falling back to default"})
|
201
201
|
nil
|
202
202
|
end
|
203
203
|
|
204
|
+
rv = rv.to_i if rv.respond_to?(:to_i)
|
204
205
|
delay = (count**4) + 15
|
205
206
|
if Integer === rv && rv > 0
|
206
207
|
delay = rv
|
data/lib/sidekiq/job_util.rb
CHANGED
@@ -17,18 +17,23 @@ module Sidekiq
|
|
17
17
|
|
18
18
|
def verify_json(item)
|
19
19
|
job_class = item["wrapped"] || item["class"]
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
20
|
+
args = item["args"]
|
21
|
+
mode = Sidekiq::Config::DEFAULTS[:on_complex_arguments]
|
22
|
+
|
23
|
+
if mode == :raise || mode == :warn
|
24
|
+
if (unsafe_item = json_unsafe?(args))
|
25
|
+
msg = <<~EOM
|
26
|
+
Job arguments to #{job_class} must be native JSON types, but #{unsafe_item.inspect} is a #{unsafe_item.class}.
|
27
|
+
See https://github.com/sidekiq/sidekiq/wiki/Best-Practices.
|
28
|
+
To disable this error, add `Sidekiq.strict_args!(false)` to your initializer.
|
29
|
+
EOM
|
30
|
+
|
31
|
+
if mode == :raise
|
32
|
+
raise(ArgumentError, msg)
|
33
|
+
else
|
34
|
+
warn(msg)
|
35
|
+
end
|
36
|
+
end
|
32
37
|
end
|
33
38
|
end
|
34
39
|
|
@@ -64,8 +69,37 @@ module Sidekiq
|
|
64
69
|
|
65
70
|
private
|
66
71
|
|
67
|
-
|
68
|
-
|
72
|
+
RECURSIVE_JSON_UNSAFE = {
|
73
|
+
Integer => ->(val) {},
|
74
|
+
Float => ->(val) {},
|
75
|
+
TrueClass => ->(val) {},
|
76
|
+
FalseClass => ->(val) {},
|
77
|
+
NilClass => ->(val) {},
|
78
|
+
String => ->(val) {},
|
79
|
+
Array => ->(val) {
|
80
|
+
val.each do |e|
|
81
|
+
unsafe_item = RECURSIVE_JSON_UNSAFE[e.class].call(e)
|
82
|
+
return unsafe_item unless unsafe_item.nil?
|
83
|
+
end
|
84
|
+
nil
|
85
|
+
},
|
86
|
+
Hash => ->(val) {
|
87
|
+
val.each do |k, v|
|
88
|
+
return k unless String === k
|
89
|
+
|
90
|
+
unsafe_item = RECURSIVE_JSON_UNSAFE[v.class].call(v)
|
91
|
+
return unsafe_item unless unsafe_item.nil?
|
92
|
+
end
|
93
|
+
nil
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
RECURSIVE_JSON_UNSAFE.default = ->(val) { val }
|
98
|
+
RECURSIVE_JSON_UNSAFE.compare_by_identity
|
99
|
+
private_constant :RECURSIVE_JSON_UNSAFE
|
100
|
+
|
101
|
+
def json_unsafe?(item)
|
102
|
+
RECURSIVE_JSON_UNSAFE[item.class].call(item)
|
69
103
|
end
|
70
104
|
end
|
71
105
|
end
|
data/lib/sidekiq/launcher.rb
CHANGED
@@ -37,6 +37,7 @@ module Sidekiq
|
|
37
37
|
# and instead have thread call Launcher#heartbeat every N seconds.
|
38
38
|
def run(async_beat: true)
|
39
39
|
Sidekiq.freeze!
|
40
|
+
logger.debug { @config.merge!({}) }
|
40
41
|
@thread = safe_thread("heartbeat", &method(:start_heartbeat)) if async_beat
|
41
42
|
@poller.start
|
42
43
|
@managers.each(&:start)
|
@@ -161,7 +162,7 @@ module Sidekiq
|
|
161
162
|
fails = procd = 0
|
162
163
|
kb = memory_usage(::Process.pid)
|
163
164
|
|
164
|
-
_, exists, _, _,
|
165
|
+
_, exists, _, _, signal = redis { |conn|
|
165
166
|
conn.multi { |transaction|
|
166
167
|
transaction.sadd("processes", [key])
|
167
168
|
transaction.exists(key)
|
@@ -180,9 +181,7 @@ module Sidekiq
|
|
180
181
|
fire_event(:heartbeat) unless exists > 0
|
181
182
|
fire_event(:beat, oneshot: false)
|
182
183
|
|
183
|
-
|
184
|
-
|
185
|
-
::Process.kill(msg, ::Process.pid)
|
184
|
+
::Process.kill(signal, ::Process.pid) if signal && !@embedded
|
186
185
|
rescue => e
|
187
186
|
# ignore all redis/network issues
|
188
187
|
logger.error("heartbeat: #{e}")
|
@@ -216,7 +215,7 @@ module Sidekiq
|
|
216
215
|
Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
|
217
216
|
Ensure Redis is running in the same AZ or datacenter as Sidekiq.
|
218
217
|
If these values are close to 100,000, that means your Sidekiq process may be
|
219
|
-
CPU-saturated; reduce your concurrency and/or see https://github.com/
|
218
|
+
CPU-saturated; reduce your concurrency and/or see https://github.com/sidekiq/sidekiq/discussions/5039
|
220
219
|
EOM
|
221
220
|
RTT_READINGS.reset
|
222
221
|
end
|
@@ -250,12 +249,19 @@ module Sidekiq
|
|
250
249
|
"pid" => ::Process.pid,
|
251
250
|
"tag" => @config[:tag] || "",
|
252
251
|
"concurrency" => @config.total_concurrency,
|
253
|
-
"queues" => @config.capsules.values.
|
252
|
+
"queues" => @config.capsules.values.flat_map { |cap| cap.queues }.uniq,
|
253
|
+
"weights" => to_weights,
|
254
254
|
"labels" => @config[:labels].to_a,
|
255
|
-
"identity" => identity
|
255
|
+
"identity" => identity,
|
256
|
+
"version" => Sidekiq::VERSION,
|
257
|
+
"embedded" => @embedded
|
256
258
|
}
|
257
259
|
end
|
258
260
|
|
261
|
+
def to_weights
|
262
|
+
@config.capsules.values.map(&:weights)
|
263
|
+
end
|
264
|
+
|
259
265
|
def to_json
|
260
266
|
# this data changes infrequently so dump it to a string
|
261
267
|
# now so we don't need to dump it every heartbeat.
|
@@ -123,7 +123,7 @@ module Sidekiq
|
|
123
123
|
def series_avg(metric = "ms")
|
124
124
|
series[metric].each_with_object(Hash.new(0)) do |(bucket, value), result|
|
125
125
|
completed = series.dig("p", bucket) - series.dig("f", bucket)
|
126
|
-
result[bucket] = completed == 0 ? 0 : value.to_f / completed
|
126
|
+
result[bucket] = (completed == 0) ? 0 : value.to_f / completed
|
127
127
|
end
|
128
128
|
end
|
129
129
|
end
|
@@ -166,23 +166,26 @@ module Sidekiq
|
|
166
166
|
|
167
167
|
# Used by Sidekiq to execute the middleware at runtime
|
168
168
|
# @api private
|
169
|
-
def invoke(*args)
|
169
|
+
def invoke(*args, &block)
|
170
170
|
return yield if empty?
|
171
171
|
|
172
172
|
chain = retrieve
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
173
|
+
traverse(chain, 0, args, &block)
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def traverse(chain, index, args, &block)
|
179
|
+
if index >= chain.size
|
180
|
+
yield
|
181
|
+
else
|
182
|
+
chain[index].call(*args) do
|
183
|
+
traverse(chain, index + 1, args, &block)
|
178
184
|
end
|
179
185
|
end
|
180
|
-
traverse_chain.call
|
181
186
|
end
|
182
187
|
end
|
183
188
|
|
184
|
-
private
|
185
|
-
|
186
189
|
# Represents each link in the middleware chain
|
187
190
|
# @api private
|
188
191
|
class Entry
|
@@ -22,13 +22,11 @@ module Sidekiq
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def call(_, job, _, _)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
job["cattr"] = attrs
|
31
|
-
end
|
25
|
+
if !job.has_key?("cattr")
|
26
|
+
attrs = @strklass.constantize.attributes
|
27
|
+
# Retries can push the job N times, we don't
|
28
|
+
# want retries to reset cattr. #5692, #5090
|
29
|
+
job["cattr"] = attrs if attrs.any?
|
32
30
|
end
|
33
31
|
yield
|
34
32
|
end
|
data/lib/sidekiq/monitor.rb
CHANGED
@@ -16,8 +16,6 @@ class Sidekiq::Monitor
|
|
16
16
|
return
|
17
17
|
end
|
18
18
|
send(section)
|
19
|
-
rescue => e
|
20
|
-
abort "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(
|
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
|
data/lib/sidekiq/paginator.rb
CHANGED
@@ -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 = []
|
@@ -45,7 +45,7 @@ module Sidekiq
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def page_items(items, pageidx = 1, page_size = 25)
|
48
|
-
current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
|
48
|
+
current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
|
49
49
|
pageidx = current_page - 1
|
50
50
|
starting = pageidx * page_size
|
51
51
|
items = items.to_a
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -146,6 +146,9 @@ module Sidekiq
|
|
146
146
|
end
|
147
147
|
end
|
148
148
|
|
149
|
+
IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :never}
|
150
|
+
private_constant :IGNORE_SHUTDOWN_INTERRUPTS
|
151
|
+
|
149
152
|
def process(uow)
|
150
153
|
jobstr = uow.job
|
151
154
|
queue = uow.queue_name
|
@@ -195,7 +198,7 @@ module Sidekiq
|
|
195
198
|
ensure
|
196
199
|
if ack
|
197
200
|
# We don't want a shutdown signal to interrupt job acknowledgment.
|
198
|
-
Thread.handle_interrupt(
|
201
|
+
Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
|
199
202
|
uow.acknowledge
|
200
203
|
end
|
201
204
|
end
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -9,10 +9,12 @@ module Sidekiq
|
|
9
9
|
CommandError = RedisClient::CommandError
|
10
10
|
|
11
11
|
module CompatMethods
|
12
|
+
# TODO Deprecate and remove this
|
12
13
|
def info
|
13
14
|
@client.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h }
|
14
15
|
end
|
15
16
|
|
17
|
+
# TODO Deprecate and remove this
|
16
18
|
def evalsha(sha, keys, argv)
|
17
19
|
@client.call("EVALSHA", sha, keys.size, *keys, *argv)
|
18
20
|
end
|
@@ -34,12 +36,7 @@ module Sidekiq
|
|
34
36
|
CompatClient = RedisClient::Decorator.create(CompatMethods)
|
35
37
|
|
36
38
|
class CompatClient
|
37
|
-
|
38
|
-
def _client
|
39
|
-
@client
|
40
|
-
end
|
41
|
-
|
42
|
-
def _config
|
39
|
+
def config
|
43
40
|
@client.config
|
44
41
|
end
|
45
42
|
|