sidekiq 6.0.0 → 6.0.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/.circleci/config.yml +21 -0
- data/6.0-Upgrade.md +3 -1
- data/Changes.md +76 -1
- data/Ent-Changes.md +6 -0
- data/Gemfile.lock +3 -3
- data/Pro-Changes.md +9 -1
- data/README.md +1 -0
- data/bin/sidekiqload +8 -4
- data/bin/sidekiqmon +4 -5
- data/lib/generators/sidekiq/worker_generator.rb +10 -0
- data/lib/sidekiq.rb +8 -0
- data/lib/sidekiq/api.rb +74 -40
- data/lib/sidekiq/cli.rb +16 -16
- data/lib/sidekiq/client.rb +8 -2
- data/lib/sidekiq/fetch.rb +7 -7
- data/lib/sidekiq/job_logger.rb +11 -3
- data/lib/sidekiq/job_retry.rb +17 -6
- data/lib/sidekiq/launcher.rb +1 -3
- data/lib/sidekiq/logger.rb +107 -11
- data/lib/sidekiq/middleware/chain.rb +11 -2
- data/lib/sidekiq/monitor.rb +2 -2
- data/lib/sidekiq/paginator.rb +7 -2
- data/lib/sidekiq/processor.rb +31 -11
- data/lib/sidekiq/scheduled.rb +13 -12
- data/lib/sidekiq/testing.rb +12 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/application.rb +7 -11
- data/lib/sidekiq/web/helpers.rb +22 -4
- data/lib/sidekiq/worker.rb +4 -4
- data/sidekiq.gemspec +1 -1
- data/web/assets/javascripts/dashboard.js +2 -2
- data/web/assets/stylesheets/application-dark.css +125 -0
- data/web/assets/stylesheets/application.css +9 -0
- data/web/views/_job_info.erb +2 -1
- data/web/views/busy.erb +4 -1
- data/web/views/dead.erb +2 -2
- data/web/views/layout.erb +1 -0
- data/web/views/morgue.erb +4 -1
- data/web/views/queue.erb +10 -1
- data/web/views/retries.erb +4 -1
- data/web/views/retry.erb +2 -2
- data/web/views/scheduled.erb +4 -1
- metadata +4 -2
data/lib/sidekiq/paginator.rb
CHANGED
@@ -12,10 +12,10 @@ module Sidekiq
|
|
12
12
|
|
13
13
|
Sidekiq.redis do |conn|
|
14
14
|
type = conn.type(key)
|
15
|
+
rev = opts && opts[:reverse]
|
15
16
|
|
16
17
|
case type
|
17
18
|
when "zset"
|
18
|
-
rev = opts && opts[:reverse]
|
19
19
|
total_size, items = conn.multi {
|
20
20
|
conn.zcard(key)
|
21
21
|
if rev
|
@@ -28,8 +28,13 @@ module Sidekiq
|
|
28
28
|
when "list"
|
29
29
|
total_size, items = conn.multi {
|
30
30
|
conn.llen(key)
|
31
|
-
|
31
|
+
if rev
|
32
|
+
conn.lrange(key, -ending - 1, -starting - 1)
|
33
|
+
else
|
34
|
+
conn.lrange(key, starting, ending)
|
35
|
+
end
|
32
36
|
}
|
37
|
+
items.reverse! if rev
|
33
38
|
[current_page, total_size, items]
|
34
39
|
when "none"
|
35
40
|
[1, 0, []]
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -115,9 +115,9 @@ module Sidekiq
|
|
115
115
|
# since middleware can mutate the job hash
|
116
116
|
# we clone here so we report the original
|
117
117
|
# job structure to the Web UI
|
118
|
-
pristine =
|
118
|
+
pristine = json_clone(job_hash)
|
119
119
|
|
120
|
-
@job_logger.
|
120
|
+
@job_logger.prepare(job_hash) do
|
121
121
|
@retrier.global(pristine, queue) do
|
122
122
|
@job_logger.call(job_hash, queue) do
|
123
123
|
stats(pristine, queue) do
|
@@ -158,7 +158,7 @@ module Sidekiq
|
|
158
158
|
begin
|
159
159
|
dispatch(job_hash, queue) do |worker|
|
160
160
|
Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
|
161
|
-
execute_job(worker,
|
161
|
+
execute_job(worker, job_hash["args"])
|
162
162
|
end
|
163
163
|
end
|
164
164
|
ack = true
|
@@ -261,22 +261,42 @@ module Sidekiq
|
|
261
261
|
end
|
262
262
|
end
|
263
263
|
|
264
|
-
# Deep clone the arguments passed to the worker so that if
|
265
|
-
# the job fails, what is pushed back onto Redis hasn't
|
266
|
-
# been mutated by the worker.
|
267
|
-
def cloned(thing)
|
268
|
-
Marshal.load(Marshal.dump(thing))
|
269
|
-
end
|
270
|
-
|
271
264
|
def constantize(str)
|
265
|
+
return Object.const_get(str) unless str.include?("::")
|
266
|
+
|
272
267
|
names = str.split("::")
|
273
268
|
names.shift if names.empty? || names.first.empty?
|
274
269
|
|
275
270
|
names.inject(Object) do |constant, name|
|
276
271
|
# the false flag limits search for name to under the constant namespace
|
277
272
|
# which mimics Rails' behaviour
|
278
|
-
constant.
|
273
|
+
constant.const_get(name, false)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Deep clone the arguments passed to the worker so that if
|
278
|
+
# the job fails, what is pushed back onto Redis hasn't
|
279
|
+
# been mutated by the worker.
|
280
|
+
def json_clone(obj)
|
281
|
+
if Integer === obj || Float === obj || TrueClass === obj || FalseClass === obj || NilClass === obj
|
282
|
+
return obj
|
283
|
+
elsif String === obj
|
284
|
+
return obj.dup
|
285
|
+
elsif Array === obj
|
286
|
+
duped = Array.new(obj.size)
|
287
|
+
obj.each_with_index do |value, index|
|
288
|
+
duped[index] = json_clone(value)
|
289
|
+
end
|
290
|
+
elsif Hash === obj
|
291
|
+
duped = obj.dup
|
292
|
+
duped.each_pair do |key, value|
|
293
|
+
duped[key] = json_clone(value)
|
294
|
+
end
|
295
|
+
else
|
296
|
+
duped = obj.dup
|
279
297
|
end
|
298
|
+
|
299
|
+
duped
|
280
300
|
end
|
281
301
|
end
|
282
302
|
end
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -14,18 +14,19 @@ module Sidekiq
|
|
14
14
|
# Just check Redis for the set of jobs with a timestamp before now.
|
15
15
|
Sidekiq.redis do |conn|
|
16
16
|
sorted_sets.each do |sorted_set|
|
17
|
-
# Get
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
17
|
+
# Get next items in the queue with scores (time to execute) <= now.
|
18
|
+
until (jobs = conn.zrangebyscore(sorted_set, "-inf", now, limit: [0, 100])).empty?
|
19
|
+
# We need to go through the list one at a time to reduce the risk of something
|
20
|
+
# going wrong between the time jobs are popped from the scheduled queue and when
|
21
|
+
# they are pushed onto a work queue and losing the jobs.
|
22
|
+
jobs.each do |job|
|
23
|
+
# Pop item off the queue and add it to the work queue. If the job can't be popped from
|
24
|
+
# the queue, it's because another process already popped it so we can move on to the
|
25
|
+
# next one.
|
26
|
+
if conn.zrem(sorted_set, job)
|
27
|
+
Sidekiq::Client.push(Sidekiq.load_json(job))
|
28
|
+
Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
|
29
|
+
end
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
data/lib/sidekiq/testing.rb
CHANGED
@@ -323,6 +323,18 @@ module Sidekiq
|
|
323
323
|
end
|
324
324
|
end
|
325
325
|
end
|
326
|
+
|
327
|
+
module TestingExtensions
|
328
|
+
def jobs_for(klass)
|
329
|
+
jobs.select do |job|
|
330
|
+
marshalled = job["args"][0]
|
331
|
+
marshalled.index(klass.to_s) && YAML.load(marshalled)[0] == klass
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
Sidekiq::Extensions::DelayedMailer.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedMailer)
|
337
|
+
Sidekiq::Extensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedModel)
|
326
338
|
end
|
327
339
|
|
328
340
|
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test?
|
data/lib/sidekiq/version.rb
CHANGED
@@ -84,7 +84,7 @@ module Sidekiq
|
|
84
84
|
|
85
85
|
@count = (params["count"] || 25).to_i
|
86
86
|
@queue = Sidekiq::Queue.new(@name)
|
87
|
-
(@current_page, @total_size, @messages) = page("queue:#{@name}", params["page"], @count)
|
87
|
+
(@current_page, @total_size, @messages) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
|
88
88
|
@messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
|
89
89
|
|
90
90
|
erb(:queue)
|
@@ -283,17 +283,13 @@ module Sidekiq
|
|
283
283
|
action = self.class.match(env)
|
284
284
|
return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless action
|
285
285
|
|
286
|
-
|
287
|
-
|
286
|
+
app = @klass
|
287
|
+
resp = catch(:halt) do # rubocop:disable Standard/SemanticBlocks
|
288
288
|
self.class.run_befores(app, action)
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
end
|
294
|
-
|
295
|
-
resp
|
296
|
-
}
|
289
|
+
action.instance_exec env, &action.block
|
290
|
+
ensure
|
291
|
+
self.class.run_afters(app, action)
|
292
|
+
end
|
297
293
|
|
298
294
|
resp = case resp
|
299
295
|
when Array
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -65,7 +65,10 @@ module Sidekiq
|
|
65
65
|
|
66
66
|
def poll_path
|
67
67
|
if current_path != "" && params["poll"]
|
68
|
-
root_path + current_path
|
68
|
+
path = root_path + current_path
|
69
|
+
query_string = to_query_string(params.slice(*params.keys - %w[page poll]))
|
70
|
+
path += "?#{query_string}" unless query_string.empty?
|
71
|
+
path
|
69
72
|
else
|
70
73
|
""
|
71
74
|
end
|
@@ -112,6 +115,13 @@ module Sidekiq
|
|
112
115
|
end
|
113
116
|
end
|
114
117
|
|
118
|
+
# within is used by Sidekiq Pro
|
119
|
+
def display_tags(job, within = nil)
|
120
|
+
job.tags.map { |tag|
|
121
|
+
"<span class='jobtag label label-info'>#{::Rack::Utils.escape_html(tag)}</span>"
|
122
|
+
}.join(" ")
|
123
|
+
end
|
124
|
+
|
115
125
|
# mperham/sidekiq#3243
|
116
126
|
def unfiltered?
|
117
127
|
yield unless env["PATH_INFO"].start_with?("/filter/")
|
@@ -130,6 +140,10 @@ module Sidekiq
|
|
130
140
|
end
|
131
141
|
end
|
132
142
|
|
143
|
+
def sort_direction_label
|
144
|
+
params[:direction] == "asc" ? "↑" : "↓"
|
145
|
+
end
|
146
|
+
|
133
147
|
def workers
|
134
148
|
@workers ||= Sidekiq::Workers.new
|
135
149
|
end
|
@@ -189,7 +203,7 @@ module Sidekiq
|
|
189
203
|
[score.to_f, jid]
|
190
204
|
end
|
191
205
|
|
192
|
-
SAFE_QPARAMS = %w[page poll]
|
206
|
+
SAFE_QPARAMS = %w[page poll direction]
|
193
207
|
|
194
208
|
# Merge options with current params, filter safe params, and stringify to query string
|
195
209
|
def qparams(options)
|
@@ -198,7 +212,11 @@ module Sidekiq
|
|
198
212
|
options[key.to_s] = options.delete(key)
|
199
213
|
end
|
200
214
|
|
201
|
-
params.merge(options)
|
215
|
+
to_query_string(params.merge(options))
|
216
|
+
end
|
217
|
+
|
218
|
+
def to_query_string(params)
|
219
|
+
params.map { |key, value|
|
202
220
|
SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
|
203
221
|
}.compact.join("&")
|
204
222
|
end
|
@@ -238,7 +256,7 @@ module Sidekiq
|
|
238
256
|
queue class args retry_count retried_at failed_at
|
239
257
|
jid error_message error_class backtrace
|
240
258
|
error_backtrace enqueued_at retry wrapped
|
241
|
-
created_at
|
259
|
+
created_at tags
|
242
260
|
])
|
243
261
|
|
244
262
|
def retry_extra_items(retry_job)
|
data/lib/sidekiq/worker.rb
CHANGED
@@ -171,9 +171,9 @@ module Sidekiq
|
|
171
171
|
now = Time.now.to_f
|
172
172
|
ts = (int < 1_000_000_000 ? now + int : int)
|
173
173
|
|
174
|
-
payload = @opts.merge("class" => @klass, "args" => args
|
174
|
+
payload = @opts.merge("class" => @klass, "args" => args)
|
175
175
|
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
176
|
-
payload
|
176
|
+
payload["at"] = ts if ts > now
|
177
177
|
@klass.client_push(payload)
|
178
178
|
end
|
179
179
|
alias_method :perform_at, :perform_in
|
@@ -207,10 +207,10 @@ module Sidekiq
|
|
207
207
|
now = Time.now.to_f
|
208
208
|
ts = (int < 1_000_000_000 ? now + int : int)
|
209
209
|
|
210
|
-
item = {"class" => self, "args" => args
|
210
|
+
item = {"class" => self, "args" => args}
|
211
211
|
|
212
212
|
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
213
|
-
item
|
213
|
+
item["at"] = ts if ts > now
|
214
214
|
|
215
215
|
client_push(item)
|
216
216
|
end
|
data/sidekiq.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.homepage = "http://sidekiq.org"
|
9
9
|
gem.license = "LGPL-3.0"
|
10
10
|
|
11
|
-
gem.executables = ["sidekiq"]
|
11
|
+
gem.executables = ["sidekiq", "sidekiqmon"]
|
12
12
|
gem.files = `git ls-files | grep -Ev '^(test|myapp|examples)'`.split("\n")
|
13
13
|
gem.name = "sidekiq"
|
14
14
|
gem.version = Sidekiq::VERSION
|
@@ -27,7 +27,7 @@ var realtimeGraph = function(updatePath) {
|
|
27
27
|
renderer: 'line',
|
28
28
|
interpolation: 'linear',
|
29
29
|
|
30
|
-
series: new Rickshaw.Series.FixedDuration([{ name: graphElement.dataset.failedLabel, color: '#
|
30
|
+
series: new Rickshaw.Series.FixedDuration([{ name: graphElement.dataset.failedLabel, color: '#af0014' }, { name: graphElement.dataset.processedLabel, color: '#006f68' }], undefined, {
|
31
31
|
timeInterval: timeInterval,
|
32
32
|
maxDataPoints: 100,
|
33
33
|
})
|
@@ -125,7 +125,7 @@ var historyGraph = function() {
|
|
125
125
|
interpolation: 'linear',
|
126
126
|
series: [
|
127
127
|
{
|
128
|
-
color: "#
|
128
|
+
color: "#af0014",
|
129
129
|
data: failed,
|
130
130
|
name: graphElement.dataset.failedLabel
|
131
131
|
}, {
|
@@ -0,0 +1,125 @@
|
|
1
|
+
@media (prefers-color-scheme: dark) {
|
2
|
+
|
3
|
+
body {
|
4
|
+
background-color: #000;
|
5
|
+
color: #ccc;
|
6
|
+
}
|
7
|
+
|
8
|
+
a,
|
9
|
+
.title,
|
10
|
+
.summary_bar ul .count,
|
11
|
+
.navbar .navbar-brand {
|
12
|
+
color: #af0014;
|
13
|
+
}
|
14
|
+
|
15
|
+
.navbar .navbar-brand:hover {
|
16
|
+
color: #ccc;
|
17
|
+
}
|
18
|
+
|
19
|
+
.navbar .navbar-brand .status {
|
20
|
+
color: #ccc;
|
21
|
+
}
|
22
|
+
|
23
|
+
.navbar-inverse {
|
24
|
+
background-color: #000;
|
25
|
+
border-color: #333;
|
26
|
+
}
|
27
|
+
|
28
|
+
table.table-white {
|
29
|
+
background-color: #111;
|
30
|
+
}
|
31
|
+
|
32
|
+
.table-striped > tbody > tr:nth-of-type(odd) {
|
33
|
+
background-color: #222;
|
34
|
+
}
|
35
|
+
|
36
|
+
.table-bordered,
|
37
|
+
.table-bordered > tbody > tr > td,
|
38
|
+
.table-bordered > tbody > tr > th,
|
39
|
+
.table-bordered > tfoot > tr > td,
|
40
|
+
.table-bordered > tfoot > tr > th,
|
41
|
+
.table-bordered > thead > tr > td,
|
42
|
+
.table-bordered > thead > tr > th {
|
43
|
+
border: 1px solid #333;
|
44
|
+
}
|
45
|
+
|
46
|
+
.table-hover > tbody > tr:hover {
|
47
|
+
background-color: #333;
|
48
|
+
}
|
49
|
+
|
50
|
+
.alert {
|
51
|
+
border: none;
|
52
|
+
color: #ccc;
|
53
|
+
}
|
54
|
+
|
55
|
+
.alert-success {
|
56
|
+
background-color: #000;
|
57
|
+
}
|
58
|
+
|
59
|
+
a:link,
|
60
|
+
a:active,
|
61
|
+
a:hover,
|
62
|
+
a:visited {
|
63
|
+
color: #63798c;
|
64
|
+
}
|
65
|
+
|
66
|
+
a.btn {
|
67
|
+
color: #000;
|
68
|
+
}
|
69
|
+
|
70
|
+
.summary_bar .summary {
|
71
|
+
background-color: #000;
|
72
|
+
border: 1px solid #333;
|
73
|
+
}
|
74
|
+
|
75
|
+
.navbar-default {
|
76
|
+
background-color: #000;
|
77
|
+
border-color: #3d3d3d;
|
78
|
+
}
|
79
|
+
|
80
|
+
.navbar-default .navbar-nav > .active > a,
|
81
|
+
.navbar-default .navbar-nav > .active > a:focus,
|
82
|
+
.navbar-default .navbar-nav > .active > a:hover {
|
83
|
+
color: #ccc;
|
84
|
+
background-color: #282828;
|
85
|
+
}
|
86
|
+
|
87
|
+
.navbar-default .navbar-nav > li > a:hover {
|
88
|
+
color: #ccc;
|
89
|
+
}
|
90
|
+
|
91
|
+
.pagination > li > a,
|
92
|
+
.pagination > li > a:hover,
|
93
|
+
.pagination > li > span {
|
94
|
+
color: #ccc;
|
95
|
+
background-color: #282828;
|
96
|
+
border-color: #353535;
|
97
|
+
}
|
98
|
+
.pagination > .disabled > a,
|
99
|
+
.pagination > .disabled > a:focus,
|
100
|
+
.pagination > .disabled > a:hover,
|
101
|
+
.pagination > .disabled > span,
|
102
|
+
.pagination > .disabled > span:focus,
|
103
|
+
.pagination > .disabled > span:hover {
|
104
|
+
color: #a5a5a5;
|
105
|
+
background-color: #282828;
|
106
|
+
border-color: #353535;
|
107
|
+
}
|
108
|
+
|
109
|
+
.stat {
|
110
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
111
|
+
}
|
112
|
+
|
113
|
+
#live-poll {
|
114
|
+
color: #ccc;
|
115
|
+
}
|
116
|
+
|
117
|
+
.btn-warn {
|
118
|
+
color: #333;
|
119
|
+
}
|
120
|
+
|
121
|
+
.rickshaw_graph .y_ticks.glow text {
|
122
|
+
fill: #ccc;
|
123
|
+
color: #ccc;
|
124
|
+
}
|
125
|
+
}
|