sidekiq 6.0.0 → 6.0.5
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/6.0-Upgrade.md +3 -1
- data/Changes.md +110 -1
- data/Ent-Changes.md +7 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +105 -93
- data/Pro-Changes.md +9 -1
- data/README.md +3 -1
- data/bin/sidekiqload +8 -4
- data/bin/sidekiqmon +4 -5
- data/lib/generators/sidekiq/worker_generator.rb +11 -1
- data/lib/sidekiq.rb +12 -0
- data/lib/sidekiq/api.rb +124 -91
- data/lib/sidekiq/cli.rb +29 -18
- data/lib/sidekiq/client.rb +18 -4
- data/lib/sidekiq/fetch.rb +7 -7
- data/lib/sidekiq/job_logger.rb +11 -3
- data/lib/sidekiq/job_retry.rb +23 -10
- data/lib/sidekiq/launcher.rb +3 -5
- data/lib/sidekiq/logger.rb +107 -11
- data/lib/sidekiq/middleware/chain.rb +11 -2
- data/lib/sidekiq/monitor.rb +1 -16
- data/lib/sidekiq/paginator.rb +7 -2
- data/lib/sidekiq/processor.rb +18 -20
- data/lib/sidekiq/redis_connection.rb +3 -0
- data/lib/sidekiq/scheduled.rb +13 -12
- data/lib/sidekiq/testing.rb +12 -0
- data/lib/sidekiq/util.rb +0 -2
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/application.rb +19 -18
- data/lib/sidekiq/web/helpers.rb +23 -11
- data/lib/sidekiq/worker.rb +4 -4
- data/sidekiq.gemspec +2 -2
- 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/locales/de.yml +14 -2
- data/web/locales/en.yml +2 -0
- data/web/locales/ja.yml +2 -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/queues.erb +8 -0
- data/web/views/retries.erb +4 -1
- data/web/views/retry.erb +2 -2
- data/web/views/scheduled.erb +4 -1
- metadata +9 -8
@@ -67,7 +67,6 @@ module Sidekiq
|
|
67
67
|
module Middleware
|
68
68
|
class Chain
|
69
69
|
include Enumerable
|
70
|
-
attr_reader :entries
|
71
70
|
|
72
71
|
def initialize_copy(copy)
|
73
72
|
copy.instance_variable_set(:@entries, entries.dup)
|
@@ -78,10 +77,14 @@ module Sidekiq
|
|
78
77
|
end
|
79
78
|
|
80
79
|
def initialize
|
81
|
-
@entries =
|
80
|
+
@entries = nil
|
82
81
|
yield self if block_given?
|
83
82
|
end
|
84
83
|
|
84
|
+
def entries
|
85
|
+
@entries ||= []
|
86
|
+
end
|
87
|
+
|
85
88
|
def remove(klass)
|
86
89
|
entries.delete_if { |entry| entry.klass == klass }
|
87
90
|
end
|
@@ -114,6 +117,10 @@ module Sidekiq
|
|
114
117
|
any? { |entry| entry.klass == klass }
|
115
118
|
end
|
116
119
|
|
120
|
+
def empty?
|
121
|
+
@entries.nil? || @entries.empty?
|
122
|
+
end
|
123
|
+
|
117
124
|
def retrieve
|
118
125
|
map(&:make_new)
|
119
126
|
end
|
@@ -123,6 +130,8 @@ module Sidekiq
|
|
123
130
|
end
|
124
131
|
|
125
132
|
def invoke(*args)
|
133
|
+
return yield if empty?
|
134
|
+
|
126
135
|
chain = retrieve.dup
|
127
136
|
traverse_chain = lambda do
|
128
137
|
if chain.empty?
|
data/lib/sidekiq/monitor.rb
CHANGED
@@ -4,21 +4,6 @@ require "fileutils"
|
|
4
4
|
require "sidekiq/api"
|
5
5
|
|
6
6
|
class Sidekiq::Monitor
|
7
|
-
CMD = File.basename($PROGRAM_NAME)
|
8
|
-
|
9
|
-
attr_reader :stage
|
10
|
-
|
11
|
-
def self.print_usage
|
12
|
-
puts "#{CMD} - monitor Sidekiq from the command line."
|
13
|
-
puts
|
14
|
-
puts "Usage: #{CMD} status <section>"
|
15
|
-
puts
|
16
|
-
puts " <section> (optional) view a specific section of the status output"
|
17
|
-
puts " Valid sections are: #{Sidekiq::Monitor::Status::VALID_SECTIONS.join(", ")}"
|
18
|
-
puts
|
19
|
-
puts "Set REDIS_URL to the location of your Redis server if not monitoring localhost."
|
20
|
-
end
|
21
|
-
|
22
7
|
class Status
|
23
8
|
VALID_SECTIONS = %w[all version overview processes queues]
|
24
9
|
COL_PAD = 2
|
@@ -47,7 +32,7 @@ class Sidekiq::Monitor
|
|
47
32
|
|
48
33
|
def version
|
49
34
|
puts "Sidekiq #{Sidekiq::VERSION}"
|
50
|
-
puts Time.now
|
35
|
+
puts Time.now.utc
|
51
36
|
end
|
52
37
|
|
53
38
|
def overview
|
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
@@ -111,16 +111,19 @@ module Sidekiq
|
|
111
111
|
nil
|
112
112
|
end
|
113
113
|
|
114
|
-
def dispatch(job_hash, queue)
|
114
|
+
def dispatch(job_hash, queue, jobstr)
|
115
115
|
# since middleware can mutate the job hash
|
116
|
-
# we clone
|
116
|
+
# we need to clone it to report the original
|
117
117
|
# job structure to the Web UI
|
118
|
-
|
118
|
+
# or to push back to redis when retrying.
|
119
|
+
# To avoid costly and, most of the time, useless cloning here,
|
120
|
+
# we pass original String of JSON to respected methods
|
121
|
+
# to re-parse it there if we need access to the original, untouched job
|
119
122
|
|
120
|
-
@job_logger.
|
121
|
-
@retrier.global(
|
123
|
+
@job_logger.prepare(job_hash) do
|
124
|
+
@retrier.global(jobstr, queue) do
|
122
125
|
@job_logger.call(job_hash, queue) do
|
123
|
-
stats(
|
126
|
+
stats(jobstr, queue) do
|
124
127
|
# Rails 5 requires a Reloader to wrap code execution. In order to
|
125
128
|
# constantize the worker and instantiate an instance, we have to call
|
126
129
|
# the Reloader. It handles code loading, db connection management, etc.
|
@@ -129,7 +132,7 @@ module Sidekiq
|
|
129
132
|
klass = constantize(job_hash["class"])
|
130
133
|
worker = klass.new
|
131
134
|
worker.jid = job_hash["jid"]
|
132
|
-
@retrier.local(worker,
|
135
|
+
@retrier.local(worker, jobstr, queue) do
|
133
136
|
yield worker
|
134
137
|
end
|
135
138
|
end
|
@@ -156,9 +159,9 @@ module Sidekiq
|
|
156
159
|
|
157
160
|
ack = false
|
158
161
|
begin
|
159
|
-
dispatch(job_hash, queue) do |worker|
|
162
|
+
dispatch(job_hash, queue, jobstr) do |worker|
|
160
163
|
Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
|
161
|
-
execute_job(worker,
|
164
|
+
execute_job(worker, job_hash["args"])
|
162
165
|
end
|
163
166
|
end
|
164
167
|
ack = true
|
@@ -178,7 +181,7 @@ module Sidekiq
|
|
178
181
|
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
179
182
|
# so it can be rescued when using Sidekiq Pro.
|
180
183
|
handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
|
181
|
-
raise
|
184
|
+
raise ex
|
182
185
|
ensure
|
183
186
|
if ack
|
184
187
|
# We don't want a shutdown signal to interrupt job acknowledgment.
|
@@ -247,8 +250,8 @@ module Sidekiq
|
|
247
250
|
FAILURE = Counter.new
|
248
251
|
WORKER_STATE = SharedWorkerState.new
|
249
252
|
|
250
|
-
def stats(
|
251
|
-
WORKER_STATE.set(tid, {queue: queue, payload:
|
253
|
+
def stats(jobstr, queue)
|
254
|
+
WORKER_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
|
252
255
|
|
253
256
|
begin
|
254
257
|
yield
|
@@ -261,21 +264,16 @@ module Sidekiq
|
|
261
264
|
end
|
262
265
|
end
|
263
266
|
|
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
267
|
def constantize(str)
|
268
|
+
return Object.const_get(str) unless str.include?("::")
|
269
|
+
|
272
270
|
names = str.split("::")
|
273
271
|
names.shift if names.empty? || names.first.empty?
|
274
272
|
|
275
273
|
names.inject(Object) do |constant, name|
|
276
274
|
# the false flag limits search for name to under the constant namespace
|
277
275
|
# which mimics Rails' behaviour
|
278
|
-
constant.
|
276
|
+
constant.const_get(name, false)
|
279
277
|
end
|
280
278
|
end
|
281
279
|
end
|
@@ -103,6 +103,9 @@ module Sidekiq
|
|
103
103
|
if scrubbed_options[:password]
|
104
104
|
scrubbed_options[:password] = redacted
|
105
105
|
end
|
106
|
+
scrubbed_options[:sentinels]&.each do |sentinel|
|
107
|
+
sentinel[:password] = redacted if sentinel[:password]
|
108
|
+
end
|
106
109
|
if Sidekiq.server?
|
107
110
|
Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with redis options #{scrubbed_options}")
|
108
111
|
else
|
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/util.rb
CHANGED
data/lib/sidekiq/version.rb
CHANGED
@@ -5,7 +5,6 @@ module Sidekiq
|
|
5
5
|
extend WebRouter
|
6
6
|
|
7
7
|
CONTENT_LENGTH = "Content-Length"
|
8
|
-
CONTENT_TYPE = "Content-Type"
|
9
8
|
REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
|
10
9
|
CSP_HEADER = [
|
11
10
|
"default-src 'self' https: http:",
|
@@ -84,14 +83,22 @@ module Sidekiq
|
|
84
83
|
|
85
84
|
@count = (params["count"] || 25).to_i
|
86
85
|
@queue = Sidekiq::Queue.new(@name)
|
87
|
-
(@current_page, @total_size, @messages) = page("queue:#{@name}", params["page"], @count)
|
86
|
+
(@current_page, @total_size, @messages) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
|
88
87
|
@messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
|
89
88
|
|
90
89
|
erb(:queue)
|
91
90
|
end
|
92
91
|
|
93
92
|
post "/queues/:name" do
|
94
|
-
Sidekiq::Queue.new(route_params[:name])
|
93
|
+
queue = Sidekiq::Queue.new(route_params[:name])
|
94
|
+
|
95
|
+
if Sidekiq.pro? && params["pause"]
|
96
|
+
queue.pause!
|
97
|
+
elsif Sidekiq.pro? && params["unpause"]
|
98
|
+
queue.unpause!
|
99
|
+
else
|
100
|
+
queue.clear
|
101
|
+
end
|
95
102
|
|
96
103
|
redirect "#{root_path}queues"
|
97
104
|
end
|
@@ -283,36 +290,30 @@ module Sidekiq
|
|
283
290
|
action = self.class.match(env)
|
284
291
|
return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless action
|
285
292
|
|
286
|
-
|
287
|
-
|
293
|
+
app = @klass
|
294
|
+
resp = catch(:halt) do # rubocop:disable Standard/SemanticBlocks
|
288
295
|
self.class.run_befores(app, action)
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
end
|
294
|
-
|
295
|
-
resp
|
296
|
-
}
|
296
|
+
action.instance_exec env, &action.block
|
297
|
+
ensure
|
298
|
+
self.class.run_afters(app, action)
|
299
|
+
end
|
297
300
|
|
298
301
|
resp = case resp
|
299
302
|
when Array
|
303
|
+
# redirects go here
|
300
304
|
resp
|
301
305
|
else
|
306
|
+
# rendered content goes here
|
302
307
|
headers = {
|
303
308
|
"Content-Type" => "text/html",
|
304
309
|
"Cache-Control" => "no-cache",
|
305
310
|
"Content-Language" => action.locale,
|
306
311
|
"Content-Security-Policy" => CSP_HEADER,
|
307
312
|
}
|
308
|
-
|
313
|
+
# we'll let Rack calculate Content-Length for us.
|
309
314
|
[200, headers, [resp]]
|
310
315
|
end
|
311
316
|
|
312
|
-
resp[1] = resp[1].dup
|
313
|
-
|
314
|
-
resp[1][CONTENT_LENGTH] = resp[2].inject(0) { |l, p| l + p.bytesize }.to_s
|
315
|
-
|
316
317
|
resp
|
317
318
|
end
|
318
319
|
|
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
|
@@ -142,12 +156,6 @@ module Sidekiq
|
|
142
156
|
@stats ||= Sidekiq::Stats.new
|
143
157
|
end
|
144
158
|
|
145
|
-
def retries_with_score(score)
|
146
|
-
Sidekiq.redis { |conn|
|
147
|
-
conn.zrangebyscore("retry", score, score)
|
148
|
-
}.map { |msg| Sidekiq.load_json(msg) }
|
149
|
-
end
|
150
|
-
|
151
159
|
def redis_connection
|
152
160
|
Sidekiq.redis do |conn|
|
153
161
|
c = conn.connection
|
@@ -189,7 +197,7 @@ module Sidekiq
|
|
189
197
|
[score.to_f, jid]
|
190
198
|
end
|
191
199
|
|
192
|
-
SAFE_QPARAMS = %w[page poll]
|
200
|
+
SAFE_QPARAMS = %w[page poll direction]
|
193
201
|
|
194
202
|
# Merge options with current params, filter safe params, and stringify to query string
|
195
203
|
def qparams(options)
|
@@ -198,7 +206,11 @@ module Sidekiq
|
|
198
206
|
options[key.to_s] = options.delete(key)
|
199
207
|
end
|
200
208
|
|
201
|
-
params.merge(options)
|
209
|
+
to_query_string(params.merge(options))
|
210
|
+
end
|
211
|
+
|
212
|
+
def to_query_string(params)
|
213
|
+
params.map { |key, value|
|
202
214
|
SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
|
203
215
|
}.compact.join("&")
|
204
216
|
end
|
@@ -238,7 +250,7 @@ module Sidekiq
|
|
238
250
|
queue class args retry_count retried_at failed_at
|
239
251
|
jid error_message error_class backtrace
|
240
252
|
error_backtrace enqueued_at retry wrapped
|
241
|
-
created_at
|
253
|
+
created_at tags
|
242
254
|
])
|
243
255
|
|
244
256
|
def retry_extra_items(retry_job)
|
@@ -283,7 +295,7 @@ module Sidekiq
|
|
283
295
|
end
|
284
296
|
|
285
297
|
def environment_title_prefix
|
286
|
-
environment = Sidekiq.options[:environment] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
298
|
+
environment = Sidekiq.options[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
287
299
|
|
288
300
|
"[#{environment.upcase}] " unless environment == "production"
|
289
301
|
end
|
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
|