sidekiq 6.2.1 → 6.2.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Changes.md +10 -0
- data/LICENSE +1 -1
- data/lib/sidekiq/api.rb +79 -56
- data/lib/sidekiq/extensions/generic_proxy.rb +3 -1
- data/lib/sidekiq/fetch.rb +1 -1
- data/lib/sidekiq/job.rb +8 -0
- data/lib/sidekiq/job_logger.rb +1 -1
- data/lib/sidekiq/job_retry.rb +3 -7
- data/lib/sidekiq/launcher.rb +13 -17
- data/lib/sidekiq/middleware/chain.rb +5 -3
- data/lib/sidekiq/scheduled.rb +7 -1
- data/lib/sidekiq/testing.rb +1 -3
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +1 -1
- data/lib/sidekiq/web/application.rb +3 -3
- data/lib/sidekiq/web/helpers.rb +8 -9
- data/lib/sidekiq/web.rb +4 -3
- data/web/assets/stylesheets/application-dark.css +11 -24
- data/web/assets/stylesheets/application.css +15 -131
- data/web/locales/ar.yml +8 -2
- data/web/locales/en.yml +3 -0
- data/web/locales/es.yml +18 -2
- data/web/locales/fr.yml +7 -0
- data/web/locales/ja.yml +3 -0
- data/web/locales/lt.yml +1 -1
- data/web/views/_job_info.erb +1 -1
- data/web/views/busy.erb +5 -5
- data/web/views/dashboard.erb +14 -6
- data/web/views/dead.erb +1 -1
- data/web/views/morgue.erb +6 -6
- data/web/views/queues.erb +3 -3
- data/web/views/retries.erb +7 -7
- data/web/views/retry.erb +1 -1
- data/web/views/scheduled.erb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b0da541124d097e05936cc860a93009ba170d714cbd8dbc760c1245818e8ed7
|
4
|
+
data.tar.gz: 63995ccbb57fa16e4954cda4ab9610506b0a8ff35084492b520fc18608face35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3b864db74530840437c0af43475ed0e12a876d6d2cbbd808ecc72722adddbd8d5548c9b777d4e34af5deddf94871567717518f70d787ea7f6a089f2f5b2f4ed
|
7
|
+
data.tar.gz: 4569eed5baa10134e99a0b1404c213e3b35c08ec158e2fa8b006918298750919e8d34074aa10d3b4dfb621bb18cbe68be467d1e7822fb855012cff0f64dcdc7b
|
data/Changes.md
CHANGED
@@ -2,6 +2,16 @@
|
|
2
2
|
|
3
3
|
[Sidekiq Changes](https://github.com/mperham/sidekiq/blob/master/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/master/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/master/Ent-Changes.md)
|
4
4
|
|
5
|
+
6.2.2
|
6
|
+
---------
|
7
|
+
|
8
|
+
- Reduce retry jitter, add jitter to `sidekiq_retry_in` values [#4957]
|
9
|
+
- Minimize scheduler load on Redis at scale [#4882]
|
10
|
+
- Improve logging of delay jobs [#4904, BuonOno]
|
11
|
+
- Minor CSS improvements for buttons and tables, design PRs always welcome!
|
12
|
+
- Tweak Web UI `Cache-Control` header [#4966]
|
13
|
+
- Rename internal API class `Sidekiq::Job` to `Sidekiq::JobRecord` [#4955]
|
14
|
+
|
5
15
|
6.2.1
|
6
16
|
---------
|
7
17
|
|
data/LICENSE
CHANGED
@@ -6,4 +6,4 @@ for license text.
|
|
6
6
|
|
7
7
|
Sidekiq Pro has a commercial-friendly license allowing private forks
|
8
8
|
and modifications of Sidekiq. Please see https://sidekiq.org/products/pro.html for
|
9
|
-
more detail. You can find the commercial license terms in COMM-LICENSE.
|
9
|
+
more detail. You can find the commercial license terms in COMM-LICENSE.txt.
|
data/lib/sidekiq/api.rb
CHANGED
@@ -8,7 +8,7 @@ require "base64"
|
|
8
8
|
module Sidekiq
|
9
9
|
class Stats
|
10
10
|
def initialize
|
11
|
-
|
11
|
+
fetch_stats_fast!
|
12
12
|
end
|
13
13
|
|
14
14
|
def processed
|
@@ -51,7 +51,8 @@ module Sidekiq
|
|
51
51
|
Sidekiq::Stats::Queues.new.lengths
|
52
52
|
end
|
53
53
|
|
54
|
-
|
54
|
+
# O(1) redis calls
|
55
|
+
def fetch_stats_fast!
|
55
56
|
pipe1_res = Sidekiq.redis { |conn|
|
56
57
|
conn.pipelined do
|
57
58
|
conn.get("stat:processed")
|
@@ -64,25 +65,6 @@ module Sidekiq
|
|
64
65
|
end
|
65
66
|
}
|
66
67
|
|
67
|
-
processes = Sidekiq.redis { |conn|
|
68
|
-
conn.sscan_each("processes").to_a
|
69
|
-
}
|
70
|
-
|
71
|
-
queues = Sidekiq.redis { |conn|
|
72
|
-
conn.sscan_each("queues").to_a
|
73
|
-
}
|
74
|
-
|
75
|
-
pipe2_res = Sidekiq.redis { |conn|
|
76
|
-
conn.pipelined do
|
77
|
-
processes.each { |key| conn.hget(key, "busy") }
|
78
|
-
queues.each { |queue| conn.llen("queue:#{queue}") }
|
79
|
-
end
|
80
|
-
}
|
81
|
-
|
82
|
-
s = processes.size
|
83
|
-
workers_size = pipe2_res[0...s].sum(&:to_i)
|
84
|
-
enqueued = pipe2_res[s..-1].sum(&:to_i)
|
85
|
-
|
86
68
|
default_queue_latency = if (entry = pipe1_res[6].first)
|
87
69
|
job = begin
|
88
70
|
Sidekiq.load_json(entry)
|
@@ -95,6 +77,7 @@ module Sidekiq
|
|
95
77
|
else
|
96
78
|
0
|
97
79
|
end
|
80
|
+
|
98
81
|
@stats = {
|
99
82
|
processed: pipe1_res[0].to_i,
|
100
83
|
failed: pipe1_res[1].to_i,
|
@@ -103,10 +86,38 @@ module Sidekiq
|
|
103
86
|
dead_size: pipe1_res[4],
|
104
87
|
processes_size: pipe1_res[5],
|
105
88
|
|
106
|
-
default_queue_latency: default_queue_latency
|
107
|
-
|
108
|
-
|
89
|
+
default_queue_latency: default_queue_latency
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
# O(number of processes + number of queues) redis calls
|
94
|
+
def fetch_stats_slow!
|
95
|
+
processes = Sidekiq.redis { |conn|
|
96
|
+
conn.sscan_each("processes").to_a
|
97
|
+
}
|
98
|
+
|
99
|
+
queues = Sidekiq.redis { |conn|
|
100
|
+
conn.sscan_each("queues").to_a
|
109
101
|
}
|
102
|
+
|
103
|
+
pipe2_res = Sidekiq.redis { |conn|
|
104
|
+
conn.pipelined do
|
105
|
+
processes.each { |key| conn.hget(key, "busy") }
|
106
|
+
queues.each { |queue| conn.llen("queue:#{queue}") }
|
107
|
+
end
|
108
|
+
}
|
109
|
+
|
110
|
+
s = processes.size
|
111
|
+
workers_size = pipe2_res[0...s].sum(&:to_i)
|
112
|
+
enqueued = pipe2_res[s..-1].sum(&:to_i)
|
113
|
+
|
114
|
+
@stats[:workers_size] = workers_size
|
115
|
+
@stats[:enqueued] = enqueued
|
116
|
+
end
|
117
|
+
|
118
|
+
def fetch_stats!
|
119
|
+
fetch_stats_fast!
|
120
|
+
fetch_stats_slow!
|
110
121
|
end
|
111
122
|
|
112
123
|
def reset(*stats)
|
@@ -126,7 +137,8 @@ module Sidekiq
|
|
126
137
|
private
|
127
138
|
|
128
139
|
def stat(s)
|
129
|
-
@stats[s]
|
140
|
+
fetch_stats_slow! if @stats[s].nil?
|
141
|
+
@stats[s] || raise(ArgumentError, "Unknown stat #{s}")
|
130
142
|
end
|
131
143
|
|
132
144
|
class Queues
|
@@ -141,7 +153,7 @@ module Sidekiq
|
|
141
153
|
}
|
142
154
|
|
143
155
|
array_of_arrays = queues.zip(lengths).sort_by { |_, size| -size }
|
144
|
-
|
156
|
+
array_of_arrays.to_h
|
145
157
|
end
|
146
158
|
end
|
147
159
|
end
|
@@ -255,7 +267,7 @@ module Sidekiq
|
|
255
267
|
break if entries.empty?
|
256
268
|
page += 1
|
257
269
|
entries.each do |entry|
|
258
|
-
yield
|
270
|
+
yield JobRecord.new(entry, @name)
|
259
271
|
end
|
260
272
|
deleted_size = initial_size - size
|
261
273
|
end
|
@@ -265,7 +277,7 @@ module Sidekiq
|
|
265
277
|
# Find the job with the given JID within this queue.
|
266
278
|
#
|
267
279
|
# This is a slow, inefficient operation. Do not use under
|
268
|
-
# normal conditions.
|
280
|
+
# normal conditions.
|
269
281
|
def find_job(jid)
|
270
282
|
detect { |j| j.jid == jid }
|
271
283
|
end
|
@@ -286,9 +298,9 @@ module Sidekiq
|
|
286
298
|
# sorted set.
|
287
299
|
#
|
288
300
|
# The job should be considered immutable but may be
|
289
|
-
# removed from the queue via
|
301
|
+
# removed from the queue via JobRecord#delete.
|
290
302
|
#
|
291
|
-
class
|
303
|
+
class JobRecord
|
292
304
|
attr_reader :item
|
293
305
|
attr_reader :value
|
294
306
|
|
@@ -316,21 +328,23 @@ module Sidekiq
|
|
316
328
|
|
317
329
|
def display_class
|
318
330
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
319
|
-
@klass ||=
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
331
|
+
@klass ||= self["display_class"] || begin
|
332
|
+
case klass
|
333
|
+
when /\ASidekiq::Extensions::Delayed/
|
334
|
+
safe_load(args[0], klass) do |target, method, _|
|
335
|
+
"#{target}.#{method}"
|
336
|
+
end
|
337
|
+
when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
338
|
+
job_class = @item["wrapped"] || args[0]
|
339
|
+
if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob"
|
340
|
+
# MailerClass#mailer_method
|
341
|
+
args[0]["arguments"][0..1].join("#")
|
342
|
+
else
|
343
|
+
job_class
|
344
|
+
end
|
345
|
+
else
|
346
|
+
klass
|
347
|
+
end
|
334
348
|
end
|
335
349
|
end
|
336
350
|
|
@@ -443,7 +457,7 @@ module Sidekiq
|
|
443
457
|
end
|
444
458
|
end
|
445
459
|
|
446
|
-
class SortedEntry <
|
460
|
+
class SortedEntry < JobRecord
|
447
461
|
attr_reader :score
|
448
462
|
attr_reader :parent
|
449
463
|
|
@@ -823,12 +837,13 @@ module Sidekiq
|
|
823
837
|
# For Sidekiq Enterprise customers this number (in production) must be
|
824
838
|
# less than or equal to your licensed concurrency.
|
825
839
|
def total_concurrency
|
826
|
-
sum { |x| x["concurrency"] }
|
840
|
+
sum { |x| x["concurrency"].to_i }
|
827
841
|
end
|
828
842
|
|
829
|
-
def
|
830
|
-
sum { |x| x["rss"]
|
843
|
+
def total_rss_in_kb
|
844
|
+
sum { |x| x["rss"].to_i }
|
831
845
|
end
|
846
|
+
alias_method :total_rss, :total_rss_in_kb
|
832
847
|
|
833
848
|
# Returns the identity of the current cluster leader or "" if no leader.
|
834
849
|
# This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
|
@@ -879,6 +894,10 @@ module Sidekiq
|
|
879
894
|
self["identity"]
|
880
895
|
end
|
881
896
|
|
897
|
+
def queues
|
898
|
+
self["queues"]
|
899
|
+
end
|
900
|
+
|
882
901
|
def quiet!
|
883
902
|
signal("TSTP")
|
884
903
|
end
|
@@ -909,8 +928,8 @@ module Sidekiq
|
|
909
928
|
end
|
910
929
|
|
911
930
|
##
|
912
|
-
#
|
913
|
-
#
|
931
|
+
# The WorkSet stores the work being done by this Sidekiq cluster.
|
932
|
+
# It tracks the process and thread working on each job.
|
914
933
|
#
|
915
934
|
# WARNING WARNING WARNING
|
916
935
|
#
|
@@ -918,17 +937,17 @@ module Sidekiq
|
|
918
937
|
# If you call #size => 5 and then expect #each to be
|
919
938
|
# called 5 times, you're going to have a bad time.
|
920
939
|
#
|
921
|
-
#
|
922
|
-
#
|
923
|
-
#
|
940
|
+
# works = Sidekiq::WorkSet.new
|
941
|
+
# works.size => 2
|
942
|
+
# works.each do |process_id, thread_id, work|
|
924
943
|
# # process_id is a unique identifier per Sidekiq process
|
925
944
|
# # thread_id is a unique identifier per thread
|
926
945
|
# # work is a Hash which looks like:
|
927
|
-
# # { 'queue' => name, 'run_at' => timestamp, 'payload' =>
|
946
|
+
# # { 'queue' => name, 'run_at' => timestamp, 'payload' => job_hash }
|
928
947
|
# # run_at is an epoch Integer.
|
929
948
|
# end
|
930
949
|
#
|
931
|
-
class
|
950
|
+
class WorkSet
|
932
951
|
include Enumerable
|
933
952
|
|
934
953
|
def each(&block)
|
@@ -975,4 +994,8 @@ module Sidekiq
|
|
975
994
|
end
|
976
995
|
end
|
977
996
|
end
|
997
|
+
# Since "worker" is a nebulous term, we've deprecated the use of this class name.
|
998
|
+
# Is "worker" a process, a type of job, a thread? Undefined!
|
999
|
+
# WorkSet better describes the data.
|
1000
|
+
Workers = WorkSet
|
978
1001
|
end
|
@@ -24,7 +24,9 @@ module Sidekiq
|
|
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
data/lib/sidekiq/job.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require "sidekiq/worker"
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
# Sidekiq::Job is a new alias for Sidekiq::Worker, coming in 6.3.0.
|
5
|
+
# You can opt into this by requiring 'sidekiq/job' in your initializer
|
6
|
+
# and then using `include Sidekiq::Job` rather than `Sidekiq::Worker`.
|
7
|
+
Job = Worker
|
8
|
+
end
|
data/lib/sidekiq/job_logger.rb
CHANGED
@@ -38,7 +38,7 @@ module Sidekiq
|
|
38
38
|
# If we're using a wrapper class, like ActiveJob, use the "wrapped"
|
39
39
|
# attribute to expose the underlying thing.
|
40
40
|
h = {
|
41
|
-
class: job_hash["wrapped"] || job_hash["class"],
|
41
|
+
class: job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"],
|
42
42
|
jid: job_hash["jid"]
|
43
43
|
}
|
44
44
|
h[:bid] = job_hash["bid"] if job_hash["bid"]
|
data/lib/sidekiq/job_retry.rb
CHANGED
@@ -214,16 +214,12 @@ module Sidekiq
|
|
214
214
|
end
|
215
215
|
|
216
216
|
def delay_for(worker, count, exception)
|
217
|
+
jitter = rand(10) * (count + 1)
|
217
218
|
if worker&.sidekiq_retry_in_block
|
218
219
|
custom_retry_in = retry_in(worker, count, exception).to_i
|
219
|
-
return custom_retry_in if custom_retry_in > 0
|
220
|
+
return custom_retry_in + jitter if custom_retry_in > 0
|
220
221
|
end
|
221
|
-
|
222
|
-
end
|
223
|
-
|
224
|
-
# delayed_job uses the same basic formula
|
225
|
-
def seconds_to_delay(count)
|
226
|
-
(count**4) + 15 + (rand(30) * (count + 1))
|
222
|
+
(count**4) + 15 + jitter
|
227
223
|
end
|
228
224
|
|
229
225
|
def retry_in(worker, count, exception)
|
data/lib/sidekiq/launcher.rb
CHANGED
@@ -238,26 +238,22 @@ module Sidekiq
|
|
238
238
|
end
|
239
239
|
|
240
240
|
def to_data
|
241
|
-
@data ||=
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
}
|
252
|
-
end
|
241
|
+
@data ||= {
|
242
|
+
"hostname" => hostname,
|
243
|
+
"started_at" => Time.now.to_f,
|
244
|
+
"pid" => ::Process.pid,
|
245
|
+
"tag" => @options[:tag] || "",
|
246
|
+
"concurrency" => @options[:concurrency],
|
247
|
+
"queues" => @options[:queues].uniq,
|
248
|
+
"labels" => @options[:labels],
|
249
|
+
"identity" => identity
|
250
|
+
}
|
253
251
|
end
|
254
252
|
|
255
253
|
def to_json
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
Sidekiq.dump_json(to_data)
|
260
|
-
end
|
254
|
+
# this data changes infrequently so dump it to a string
|
255
|
+
# now so we don't need to dump it every heartbeat.
|
256
|
+
@json ||= Sidekiq.dump_json(to_data)
|
261
257
|
end
|
262
258
|
end
|
263
259
|
end
|
@@ -90,12 +90,12 @@ module Sidekiq
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def add(klass, *args)
|
93
|
-
remove(klass)
|
93
|
+
remove(klass)
|
94
94
|
entries << Entry.new(klass, *args)
|
95
95
|
end
|
96
96
|
|
97
97
|
def prepend(klass, *args)
|
98
|
-
remove(klass)
|
98
|
+
remove(klass)
|
99
99
|
entries.insert(0, Entry.new(klass, *args))
|
100
100
|
end
|
101
101
|
|
@@ -132,7 +132,7 @@ module Sidekiq
|
|
132
132
|
def invoke(*args)
|
133
133
|
return yield if empty?
|
134
134
|
|
135
|
-
chain = retrieve
|
135
|
+
chain = retrieve
|
136
136
|
traverse_chain = proc do
|
137
137
|
if chain.empty?
|
138
138
|
yield
|
@@ -144,6 +144,8 @@ module Sidekiq
|
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
147
|
+
private
|
148
|
+
|
147
149
|
class Entry
|
148
150
|
attr_reader :klass
|
149
151
|
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -49,6 +49,7 @@ module Sidekiq
|
|
49
49
|
@sleeper = ConnectionPool::TimedStack.new
|
50
50
|
@done = false
|
51
51
|
@thread = nil
|
52
|
+
@count_calls = 0
|
52
53
|
end
|
53
54
|
|
54
55
|
# Shut down this instance, will pause until the thread is dead.
|
@@ -152,8 +153,13 @@ module Sidekiq
|
|
152
153
|
end
|
153
154
|
|
154
155
|
def process_count
|
155
|
-
|
156
|
+
# The work buried within Sidekiq::ProcessSet#cleanup can be
|
157
|
+
# expensive at scale. Cut it down by 90% with this counter.
|
158
|
+
# NB: This method is only called by the scheduler thread so we
|
159
|
+
# don't need to worry about the thread safety of +=.
|
160
|
+
pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
|
156
161
|
pcount = 1 if pcount == 0
|
162
|
+
@count_calls += 1
|
157
163
|
pcount
|
158
164
|
end
|
159
165
|
|