sidekiq 3.5.4 → 5.2.7
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 +5 -5
- data/.circleci/config.yml +61 -0
- data/{Contributing.md → .github/contributing.md} +0 -0
- data/.github/issue_template.md +11 -0
- data/.gitignore +3 -0
- data/.travis.yml +5 -10
- data/4.0-Upgrade.md +53 -0
- data/5.0-Upgrade.md +56 -0
- data/COMM-LICENSE +13 -11
- data/Changes.md +376 -1
- data/Ent-Changes.md +201 -2
- data/Gemfile +14 -18
- data/LICENSE +1 -1
- data/Pro-3.0-Upgrade.md +44 -0
- data/Pro-4.0-Upgrade.md +35 -0
- data/Pro-Changes.md +307 -2
- data/README.md +34 -22
- data/Rakefile +3 -3
- data/bin/sidekiq +0 -1
- data/bin/sidekiqctl +13 -86
- data/bin/sidekiqload +23 -27
- data/code_of_conduct.md +50 -0
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +3 -3
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +6 -6
- data/lib/sidekiq.rb +72 -25
- data/lib/sidekiq/api.rb +206 -73
- data/lib/sidekiq/cli.rb +145 -101
- data/lib/sidekiq/client.rb +42 -36
- data/lib/sidekiq/core_ext.rb +1 -105
- data/lib/sidekiq/ctl.rb +221 -0
- data/lib/sidekiq/delay.rb +42 -0
- data/lib/sidekiq/exception_handler.rb +4 -5
- data/lib/sidekiq/extensions/action_mailer.rb +1 -0
- data/lib/sidekiq/extensions/active_record.rb +1 -0
- data/lib/sidekiq/extensions/class_methods.rb +1 -0
- data/lib/sidekiq/extensions/generic_proxy.rb +8 -1
- data/lib/sidekiq/fetch.rb +36 -111
- data/lib/sidekiq/job_logger.rb +25 -0
- data/lib/sidekiq/job_retry.rb +262 -0
- data/lib/sidekiq/launcher.rb +129 -55
- data/lib/sidekiq/logging.rb +21 -3
- data/lib/sidekiq/manager.rb +83 -182
- data/lib/sidekiq/middleware/chain.rb +1 -0
- data/lib/sidekiq/middleware/i18n.rb +1 -0
- data/lib/sidekiq/middleware/server/active_record.rb +10 -0
- data/lib/sidekiq/paginator.rb +1 -0
- data/lib/sidekiq/processor.rb +221 -103
- data/lib/sidekiq/rails.rb +47 -27
- data/lib/sidekiq/redis_connection.rb +74 -7
- data/lib/sidekiq/scheduled.rb +87 -28
- data/lib/sidekiq/testing.rb +150 -19
- data/lib/sidekiq/testing/inline.rb +1 -0
- data/lib/sidekiq/util.rb +15 -17
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web.rb +120 -184
- data/lib/sidekiq/web/action.rb +89 -0
- data/lib/sidekiq/web/application.rb +353 -0
- data/lib/sidekiq/{web_helpers.rb → web/helpers.rb} +123 -47
- data/lib/sidekiq/web/router.rb +100 -0
- data/lib/sidekiq/worker.rb +135 -18
- data/sidekiq.gemspec +8 -14
- data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
- data/web/assets/javascripts/application.js +24 -20
- data/web/assets/javascripts/dashboard.js +33 -18
- data/web/assets/stylesheets/application-rtl.css +246 -0
- data/web/assets/stylesheets/application.css +401 -7
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +4 -8
- data/web/locales/ar.yml +81 -0
- data/web/locales/cs.yml +11 -1
- data/web/locales/de.yml +1 -1
- data/web/locales/en.yml +4 -0
- data/web/locales/es.yml +4 -3
- data/web/locales/fa.yml +80 -0
- data/web/locales/fr.yml +21 -12
- data/web/locales/he.yml +79 -0
- data/web/locales/ja.yml +24 -13
- data/web/locales/ru.yml +3 -0
- data/web/locales/ur.yml +80 -0
- data/web/views/_footer.erb +7 -9
- data/web/views/_job_info.erb +5 -1
- data/web/views/_nav.erb +5 -19
- data/web/views/_paging.erb +1 -1
- data/web/views/busy.erb +18 -9
- data/web/views/dashboard.erb +5 -5
- data/web/views/dead.erb +1 -1
- data/web/views/layout.erb +13 -5
- data/web/views/morgue.erb +16 -12
- data/web/views/queue.erb +12 -11
- data/web/views/queues.erb +5 -3
- data/web/views/retries.erb +19 -13
- data/web/views/retry.erb +2 -2
- data/web/views/scheduled.erb +4 -4
- data/web/views/scheduled_job_info.erb +1 -1
- metadata +45 -227
- data/lib/sidekiq/actor.rb +0 -39
- data/lib/sidekiq/middleware/server/logging.rb +0 -40
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -206
- data/test/config.yml +0 -9
- data/test/env_based_config.yml +0 -11
- data/test/fake_env.rb +0 -0
- data/test/fixtures/en.yml +0 -2
- data/test/helper.rb +0 -49
- data/test/test_api.rb +0 -493
- data/test/test_cli.rb +0 -335
- data/test/test_client.rb +0 -194
- data/test/test_exception_handler.rb +0 -55
- data/test/test_extensions.rb +0 -126
- data/test/test_fetch.rb +0 -104
- data/test/test_logging.rb +0 -34
- data/test/test_manager.rb +0 -168
- data/test/test_middleware.rb +0 -159
- data/test/test_processor.rb +0 -237
- data/test/test_rails.rb +0 -21
- data/test/test_redis_connection.rb +0 -126
- data/test/test_retry.rb +0 -325
- data/test/test_scheduled.rb +0 -114
- data/test/test_scheduling.rb +0 -49
- data/test/test_sidekiq.rb +0 -99
- data/test/test_testing.rb +0 -142
- data/test/test_testing_fake.rb +0 -268
- data/test/test_testing_inline.rb +0 -93
- data/test/test_util.rb +0 -16
- data/test/test_web.rb +0 -608
- data/test/test_web_helpers.rb +0 -53
- data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/web/assets/images/status/active.png +0 -0
- data/web/assets/images/status/idle.png +0 -0
- data/web/assets/javascripts/locales/README.md +0 -27
- data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
- data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
- data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
- data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
- data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
- data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
- data/web/views/_poll_js.erb +0 -5
data/lib/sidekiq/api.rb
CHANGED
@@ -1,8 +1,24 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
require 'sidekiq'
|
3
3
|
|
4
4
|
module Sidekiq
|
5
|
+
|
6
|
+
module RedisScanner
|
7
|
+
def sscan(conn, key)
|
8
|
+
cursor = '0'
|
9
|
+
result = []
|
10
|
+
loop do
|
11
|
+
cursor, values = conn.sscan(key, cursor)
|
12
|
+
result.push(*values)
|
13
|
+
break if cursor == '0'
|
14
|
+
end
|
15
|
+
result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
5
19
|
class Stats
|
20
|
+
include RedisScanner
|
21
|
+
|
6
22
|
def initialize
|
7
23
|
fetch_stats!
|
8
24
|
end
|
@@ -50,31 +66,40 @@ module Sidekiq
|
|
50
66
|
def fetch_stats!
|
51
67
|
pipe1_res = Sidekiq.redis do |conn|
|
52
68
|
conn.pipelined do
|
53
|
-
conn.get('stat:processed'
|
54
|
-
conn.get('stat:failed'
|
55
|
-
conn.zcard('schedule'
|
56
|
-
conn.zcard('retry'
|
57
|
-
conn.zcard('dead'
|
58
|
-
conn.scard('processes'
|
59
|
-
conn.lrange('queue:default'
|
60
|
-
conn.smembers('processes'.freeze)
|
61
|
-
conn.smembers('queues'.freeze)
|
69
|
+
conn.get('stat:processed')
|
70
|
+
conn.get('stat:failed')
|
71
|
+
conn.zcard('schedule')
|
72
|
+
conn.zcard('retry')
|
73
|
+
conn.zcard('dead')
|
74
|
+
conn.scard('processes')
|
75
|
+
conn.lrange('queue:default', -1, -1)
|
62
76
|
end
|
63
77
|
end
|
64
78
|
|
79
|
+
processes = Sidekiq.redis do |conn|
|
80
|
+
sscan(conn, 'processes')
|
81
|
+
end
|
82
|
+
|
83
|
+
queues = Sidekiq.redis do |conn|
|
84
|
+
sscan(conn, 'queues')
|
85
|
+
end
|
86
|
+
|
65
87
|
pipe2_res = Sidekiq.redis do |conn|
|
66
88
|
conn.pipelined do
|
67
|
-
|
68
|
-
|
89
|
+
processes.each {|key| conn.hget(key, 'busy') }
|
90
|
+
queues.each {|queue| conn.llen("queue:#{queue}") }
|
69
91
|
end
|
70
92
|
end
|
71
93
|
|
72
|
-
s =
|
94
|
+
s = processes.size
|
73
95
|
workers_size = pipe2_res[0...s].map(&:to_i).inject(0, &:+)
|
74
96
|
enqueued = pipe2_res[s..-1].map(&:to_i).inject(0, &:+)
|
75
97
|
|
76
98
|
default_queue_latency = if (entry = pipe1_res[6].first)
|
77
|
-
|
99
|
+
job = Sidekiq.load_json(entry) rescue {}
|
100
|
+
now = Time.now.to_f
|
101
|
+
thence = job['enqueued_at'] || now
|
102
|
+
now - thence
|
78
103
|
else
|
79
104
|
0
|
80
105
|
end
|
@@ -113,9 +138,11 @@ module Sidekiq
|
|
113
138
|
end
|
114
139
|
|
115
140
|
class Queues
|
141
|
+
include RedisScanner
|
142
|
+
|
116
143
|
def lengths
|
117
144
|
Sidekiq.redis do |conn|
|
118
|
-
queues = conn
|
145
|
+
queues = sscan(conn, 'queues')
|
119
146
|
|
120
147
|
lengths = conn.pipelined do
|
121
148
|
queues.each do |queue|
|
@@ -142,11 +169,11 @@ module Sidekiq
|
|
142
169
|
end
|
143
170
|
|
144
171
|
def processed
|
145
|
-
date_stat_hash("processed")
|
172
|
+
@processed ||= date_stat_hash("processed")
|
146
173
|
end
|
147
174
|
|
148
175
|
def failed
|
149
|
-
date_stat_hash("failed")
|
176
|
+
@failed ||= date_stat_hash("failed")
|
150
177
|
end
|
151
178
|
|
152
179
|
private
|
@@ -159,16 +186,21 @@ module Sidekiq
|
|
159
186
|
|
160
187
|
while i < @days_previous
|
161
188
|
date = @start_date - i
|
162
|
-
datestr = date.strftime("%Y-%m-%d"
|
189
|
+
datestr = date.strftime("%Y-%m-%d")
|
163
190
|
keys << "stat:#{stat}:#{datestr}"
|
164
191
|
dates << datestr
|
165
192
|
i += 1
|
166
193
|
end
|
167
194
|
|
168
|
-
|
169
|
-
|
170
|
-
|
195
|
+
begin
|
196
|
+
Sidekiq.redis do |conn|
|
197
|
+
conn.mget(keys).each_with_index do |value, idx|
|
198
|
+
stat_hash[dates[idx]] = value ? value.to_i : 0
|
199
|
+
end
|
171
200
|
end
|
201
|
+
rescue Redis::CommandError
|
202
|
+
# mget will trigger a CROSSSLOT error when run against a Cluster
|
203
|
+
# TODO Someone want to add Cluster support?
|
172
204
|
end
|
173
205
|
|
174
206
|
stat_hash
|
@@ -190,15 +222,19 @@ module Sidekiq
|
|
190
222
|
#
|
191
223
|
class Queue
|
192
224
|
include Enumerable
|
225
|
+
extend RedisScanner
|
193
226
|
|
227
|
+
##
|
228
|
+
# Return all known queues within Redis.
|
229
|
+
#
|
194
230
|
def self.all
|
195
|
-
Sidekiq.redis {|c| c
|
231
|
+
Sidekiq.redis { |c| sscan(c, 'queues') }.sort.map { |q| Sidekiq::Queue.new(q) }
|
196
232
|
end
|
197
233
|
|
198
234
|
attr_reader :name
|
199
235
|
|
200
236
|
def initialize(name="default")
|
201
|
-
@name = name
|
237
|
+
@name = name.to_s
|
202
238
|
@rname = "queue:#{name}"
|
203
239
|
end
|
204
240
|
|
@@ -211,12 +247,20 @@ module Sidekiq
|
|
211
247
|
false
|
212
248
|
end
|
213
249
|
|
250
|
+
##
|
251
|
+
# Calculates this queue's latency, the difference in seconds since the oldest
|
252
|
+
# job in the queue was enqueued.
|
253
|
+
#
|
254
|
+
# @return Float
|
214
255
|
def latency
|
215
256
|
entry = Sidekiq.redis do |conn|
|
216
257
|
conn.lrange(@rname, -1, -1)
|
217
258
|
end.first
|
218
259
|
return 0 unless entry
|
219
|
-
|
260
|
+
job = Sidekiq.load_json(entry)
|
261
|
+
now = Time.now.to_f
|
262
|
+
thence = job['enqueued_at'] || now
|
263
|
+
now - thence
|
220
264
|
end
|
221
265
|
|
222
266
|
def each
|
@@ -225,9 +269,9 @@ module Sidekiq
|
|
225
269
|
page = 0
|
226
270
|
page_size = 50
|
227
271
|
|
228
|
-
|
272
|
+
while true do
|
229
273
|
range_start = page * page_size - deleted_size
|
230
|
-
range_end =
|
274
|
+
range_end = range_start + page_size - 1
|
231
275
|
entries = Sidekiq.redis do |conn|
|
232
276
|
conn.lrange @rname, range_start, range_end
|
233
277
|
end
|
@@ -240,6 +284,11 @@ module Sidekiq
|
|
240
284
|
end
|
241
285
|
end
|
242
286
|
|
287
|
+
##
|
288
|
+
# Find the job with the given JID within this queue.
|
289
|
+
#
|
290
|
+
# This is a slow, inefficient operation. Do not use under
|
291
|
+
# normal conditions. Sidekiq Pro contains a faster version.
|
243
292
|
def find_job(jid)
|
244
293
|
detect { |j| j.jid == jid }
|
245
294
|
end
|
@@ -248,7 +297,7 @@ module Sidekiq
|
|
248
297
|
Sidekiq.redis do |conn|
|
249
298
|
conn.multi do
|
250
299
|
conn.del(@rname)
|
251
|
-
conn.srem("queues"
|
300
|
+
conn.srem("queues", name)
|
252
301
|
end
|
253
302
|
end
|
254
303
|
end
|
@@ -264,15 +313,28 @@ module Sidekiq
|
|
264
313
|
#
|
265
314
|
class Job
|
266
315
|
attr_reader :item
|
316
|
+
attr_reader :value
|
267
317
|
|
268
318
|
def initialize(item, queue_name=nil)
|
319
|
+
@args = nil
|
269
320
|
@value = item
|
270
|
-
@item = item.is_a?(Hash) ? item :
|
321
|
+
@item = item.is_a?(Hash) ? item : parse(item)
|
271
322
|
@queue = queue_name || @item['queue']
|
272
323
|
end
|
273
324
|
|
325
|
+
def parse(item)
|
326
|
+
Sidekiq.load_json(item)
|
327
|
+
rescue JSON::ParserError
|
328
|
+
# If the job payload in Redis is invalid JSON, we'll load
|
329
|
+
# the item as an empty hash and store the invalid JSON as
|
330
|
+
# the job 'args' for display in the Web UI.
|
331
|
+
@invalid = true
|
332
|
+
@args = [item]
|
333
|
+
{}
|
334
|
+
end
|
335
|
+
|
274
336
|
def klass
|
275
|
-
|
337
|
+
self['class']
|
276
338
|
end
|
277
339
|
|
278
340
|
def display_class
|
@@ -283,7 +345,13 @@ module Sidekiq
|
|
283
345
|
"#{target}.#{method}"
|
284
346
|
end
|
285
347
|
when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
286
|
-
@item['wrapped'] || args[0]
|
348
|
+
job_class = @item['wrapped'] || args[0]
|
349
|
+
if 'ActionMailer::DeliveryJob' == job_class
|
350
|
+
# MailerClass#mailer_method
|
351
|
+
args[0]['arguments'][0..1].join('#')
|
352
|
+
else
|
353
|
+
job_class
|
354
|
+
end
|
287
355
|
else
|
288
356
|
klass
|
289
357
|
end
|
@@ -291,32 +359,42 @@ module Sidekiq
|
|
291
359
|
|
292
360
|
def display_args
|
293
361
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
294
|
-
@
|
362
|
+
@display_args ||= case klass
|
295
363
|
when /\ASidekiq::Extensions::Delayed/
|
296
364
|
safe_load(args[0], args) do |_, _, arg|
|
297
365
|
arg
|
298
366
|
end
|
299
367
|
when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
300
|
-
|
368
|
+
job_args = self['wrapped'] ? args[0]["arguments"] : []
|
369
|
+
if 'ActionMailer::DeliveryJob' == (self['wrapped'] || args[0])
|
370
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
371
|
+
job_args.drop(3)
|
372
|
+
else
|
373
|
+
job_args
|
374
|
+
end
|
301
375
|
else
|
376
|
+
if self['encrypt']
|
377
|
+
# no point in showing 150+ bytes of random garbage
|
378
|
+
args[-1] = '[encrypted data]'
|
379
|
+
end
|
302
380
|
args
|
303
381
|
end
|
304
382
|
end
|
305
383
|
|
306
384
|
def args
|
307
|
-
@item['args']
|
385
|
+
@args || @item['args']
|
308
386
|
end
|
309
387
|
|
310
388
|
def jid
|
311
|
-
|
389
|
+
self['jid']
|
312
390
|
end
|
313
391
|
|
314
392
|
def enqueued_at
|
315
|
-
|
393
|
+
self['enqueued_at'] ? Time.at(self['enqueued_at']).utc : nil
|
316
394
|
end
|
317
395
|
|
318
396
|
def created_at
|
319
|
-
Time.at(
|
397
|
+
Time.at(self['created_at'] || self['enqueued_at'] || 0).utc
|
320
398
|
end
|
321
399
|
|
322
400
|
def queue
|
@@ -324,7 +402,8 @@ module Sidekiq
|
|
324
402
|
end
|
325
403
|
|
326
404
|
def latency
|
327
|
-
Time.now.to_f
|
405
|
+
now = Time.now.to_f
|
406
|
+
now - (@item['enqueued_at'] || @item['created_at'] || now)
|
328
407
|
end
|
329
408
|
|
330
409
|
##
|
@@ -337,7 +416,10 @@ module Sidekiq
|
|
337
416
|
end
|
338
417
|
|
339
418
|
def [](name)
|
340
|
-
|
419
|
+
# nil will happen if the JSON fails to parse.
|
420
|
+
# We don't guarantee Sidekiq will work with bad job JSON but we should
|
421
|
+
# make a best effort to minimize the damage.
|
422
|
+
@item ? @item[name] : nil
|
341
423
|
end
|
342
424
|
|
343
425
|
private
|
@@ -389,10 +471,9 @@ module Sidekiq
|
|
389
471
|
end
|
390
472
|
|
391
473
|
def retry
|
392
|
-
raise "Retry not available on jobs which have not failed" unless item["failed_at"]
|
393
474
|
remove_job do |message|
|
394
475
|
msg = Sidekiq.load_json(message)
|
395
|
-
msg['retry_count'] -= 1
|
476
|
+
msg['retry_count'] -= 1 if msg['retry_count']
|
396
477
|
Sidekiq::Client.push(msg)
|
397
478
|
end
|
398
479
|
end
|
@@ -400,20 +481,15 @@ module Sidekiq
|
|
400
481
|
##
|
401
482
|
# Place job in the dead set
|
402
483
|
def kill
|
403
|
-
raise 'Kill not available on jobs which have not failed' unless item['failed_at']
|
404
484
|
remove_job do |message|
|
405
|
-
|
406
|
-
now = Time.now.to_f
|
407
|
-
Sidekiq.redis do |conn|
|
408
|
-
conn.multi do
|
409
|
-
conn.zadd('dead', now, message)
|
410
|
-
conn.zremrangebyscore('dead', '-inf', now - DeadSet.timeout)
|
411
|
-
conn.zremrangebyrank('dead', 0, - DeadSet.max_jobs)
|
412
|
-
end
|
413
|
-
end
|
485
|
+
DeadSet.new.kill(message)
|
414
486
|
end
|
415
487
|
end
|
416
488
|
|
489
|
+
def error?
|
490
|
+
!!item['error_class']
|
491
|
+
end
|
492
|
+
|
417
493
|
private
|
418
494
|
|
419
495
|
def remove_job
|
@@ -488,15 +564,15 @@ module Sidekiq
|
|
488
564
|
page = -1
|
489
565
|
page_size = 50
|
490
566
|
|
491
|
-
|
567
|
+
while true do
|
492
568
|
range_start = page * page_size + offset_size
|
493
|
-
range_end =
|
569
|
+
range_end = range_start + page_size - 1
|
494
570
|
elements = Sidekiq.redis do |conn|
|
495
571
|
conn.zrange name, range_start, range_end, with_scores: true
|
496
572
|
end
|
497
573
|
break if elements.empty?
|
498
574
|
page -= 1
|
499
|
-
elements.each do |element, score|
|
575
|
+
elements.reverse.each do |element, score|
|
500
576
|
yield SortedEntry.new(self, score, element)
|
501
577
|
end
|
502
578
|
offset_size = initial_size - @_size
|
@@ -519,6 +595,11 @@ module Sidekiq
|
|
519
595
|
end
|
520
596
|
end
|
521
597
|
|
598
|
+
##
|
599
|
+
# Find the job with the given JID within this sorted set.
|
600
|
+
#
|
601
|
+
# This is a slow, inefficient operation. Do not use under
|
602
|
+
# normal conditions. Sidekiq Pro contains a faster version.
|
522
603
|
def find_job(jid)
|
523
604
|
self.detect { |j| j.jid == jid }
|
524
605
|
end
|
@@ -553,13 +634,13 @@ module Sidekiq
|
|
553
634
|
# Allows enumeration of scheduled jobs within Sidekiq.
|
554
635
|
# Based on this, you can search/filter for jobs. Here's an
|
555
636
|
# example where I'm selecting all jobs of a certain type
|
556
|
-
# and deleting them from the
|
637
|
+
# and deleting them from the schedule queue.
|
557
638
|
#
|
558
639
|
# r = Sidekiq::ScheduledSet.new
|
559
|
-
# r.select do |
|
560
|
-
#
|
561
|
-
#
|
562
|
-
#
|
640
|
+
# r.select do |scheduled|
|
641
|
+
# scheduled.klass == 'Sidekiq::Extensions::DelayedClass' &&
|
642
|
+
# scheduled.args[0] == 'User' &&
|
643
|
+
# scheduled.args[1] == 'setup_new_subscriber'
|
563
644
|
# end.map(&:delete)
|
564
645
|
class ScheduledSet < JobSet
|
565
646
|
def initialize
|
@@ -589,6 +670,12 @@ module Sidekiq
|
|
589
670
|
each(&:retry)
|
590
671
|
end
|
591
672
|
end
|
673
|
+
|
674
|
+
def kill_all
|
675
|
+
while size > 0
|
676
|
+
each(&:kill)
|
677
|
+
end
|
678
|
+
end
|
592
679
|
end
|
593
680
|
|
594
681
|
##
|
@@ -599,6 +686,27 @@ module Sidekiq
|
|
599
686
|
super 'dead'
|
600
687
|
end
|
601
688
|
|
689
|
+
def kill(message, opts={})
|
690
|
+
now = Time.now.to_f
|
691
|
+
Sidekiq.redis do |conn|
|
692
|
+
conn.multi do
|
693
|
+
conn.zadd(name, now.to_s, message)
|
694
|
+
conn.zremrangebyscore(name, '-inf', now - self.class.timeout)
|
695
|
+
conn.zremrangebyrank(name, 0, - self.class.max_jobs)
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
699
|
+
if opts[:notify_failure] != false
|
700
|
+
job = Sidekiq.load_json(message)
|
701
|
+
r = RuntimeError.new("Job killed by API")
|
702
|
+
r.set_backtrace(caller)
|
703
|
+
Sidekiq.death_handlers.each do |handle|
|
704
|
+
handle.call(job, r)
|
705
|
+
end
|
706
|
+
end
|
707
|
+
true
|
708
|
+
end
|
709
|
+
|
602
710
|
def retry_all
|
603
711
|
while size > 0
|
604
712
|
each(&:retry)
|
@@ -621,20 +729,20 @@ module Sidekiq
|
|
621
729
|
#
|
622
730
|
# Yields a Sidekiq::Process.
|
623
731
|
#
|
624
|
-
|
625
732
|
class ProcessSet
|
626
733
|
include Enumerable
|
734
|
+
include RedisScanner
|
627
735
|
|
628
736
|
def initialize(clean_plz=true)
|
629
|
-
|
737
|
+
cleanup if clean_plz
|
630
738
|
end
|
631
739
|
|
632
740
|
# Cleans up dead processes recorded in Redis.
|
633
741
|
# Returns the number of processes cleaned.
|
634
|
-
def
|
742
|
+
def cleanup
|
635
743
|
count = 0
|
636
744
|
Sidekiq.redis do |conn|
|
637
|
-
procs = conn
|
745
|
+
procs = sscan(conn, 'processes').sort
|
638
746
|
heartbeats = conn.pipelined do
|
639
747
|
procs.each do |key|
|
640
748
|
conn.hget(key, 'info')
|
@@ -654,7 +762,7 @@ module Sidekiq
|
|
654
762
|
end
|
655
763
|
|
656
764
|
def each
|
657
|
-
procs = Sidekiq.redis { |conn| conn
|
765
|
+
procs = Sidekiq.redis { |conn| sscan(conn, 'processes') }.sort
|
658
766
|
|
659
767
|
Sidekiq.redis do |conn|
|
660
768
|
# We're making a tradeoff here between consuming more memory instead of
|
@@ -662,13 +770,18 @@ module Sidekiq
|
|
662
770
|
# you'll be happier this way
|
663
771
|
result = conn.pipelined do
|
664
772
|
procs.each do |key|
|
665
|
-
conn.hmget(key, 'info', 'busy', 'beat')
|
773
|
+
conn.hmget(key, 'info', 'busy', 'beat', 'quiet')
|
666
774
|
end
|
667
775
|
end
|
668
776
|
|
669
|
-
result.each do |info, busy, at_s|
|
777
|
+
result.each do |info, busy, at_s, quiet|
|
778
|
+
# If a process is stopped between when we query Redis for `procs` and
|
779
|
+
# when we query for `result`, we will have an item in `result` that is
|
780
|
+
# composed of `nil` values.
|
781
|
+
next if info.nil?
|
782
|
+
|
670
783
|
hash = Sidekiq.load_json(info)
|
671
|
-
yield Process.new(hash.merge('busy' => busy.to_i, 'beat' => at_s.to_f))
|
784
|
+
yield Process.new(hash.merge('busy' => busy.to_i, 'beat' => at_s.to_f, 'quiet' => quiet))
|
672
785
|
end
|
673
786
|
end
|
674
787
|
|
@@ -682,10 +795,23 @@ module Sidekiq
|
|
682
795
|
def size
|
683
796
|
Sidekiq.redis { |conn| conn.scard('processes') }
|
684
797
|
end
|
798
|
+
|
799
|
+
# Returns the identity of the current cluster leader or "" if no leader.
|
800
|
+
# This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
|
801
|
+
# or Sidekiq Pro.
|
802
|
+
def leader
|
803
|
+
@leader ||= begin
|
804
|
+
x = Sidekiq.redis {|c| c.get("dear-leader") }
|
805
|
+
# need a non-falsy value so we can memoize
|
806
|
+
x = "" unless x
|
807
|
+
x
|
808
|
+
end
|
809
|
+
end
|
685
810
|
end
|
686
811
|
|
687
812
|
#
|
688
|
-
# Sidekiq::Process
|
813
|
+
# Sidekiq::Process represents an active Sidekiq process talking with Redis.
|
814
|
+
# Each process has a set of attributes which look like this:
|
689
815
|
#
|
690
816
|
# {
|
691
817
|
# 'hostname' => 'app-1.example.com',
|
@@ -715,8 +841,12 @@ module Sidekiq
|
|
715
841
|
@attribs[key]
|
716
842
|
end
|
717
843
|
|
844
|
+
def identity
|
845
|
+
self['identity']
|
846
|
+
end
|
847
|
+
|
718
848
|
def quiet!
|
719
|
-
signal('
|
849
|
+
signal('TSTP')
|
720
850
|
end
|
721
851
|
|
722
852
|
def stop!
|
@@ -727,6 +857,10 @@ module Sidekiq
|
|
727
857
|
signal('TTIN')
|
728
858
|
end
|
729
859
|
|
860
|
+
def stopping?
|
861
|
+
self['quiet'] == 'true'
|
862
|
+
end
|
863
|
+
|
730
864
|
private
|
731
865
|
|
732
866
|
def signal(sig)
|
@@ -739,12 +873,10 @@ module Sidekiq
|
|
739
873
|
end
|
740
874
|
end
|
741
875
|
|
742
|
-
def identity
|
743
|
-
self['identity']
|
744
|
-
end
|
745
876
|
end
|
746
877
|
|
747
878
|
##
|
879
|
+
# A worker is a thread that is currently processing a job.
|
748
880
|
# Programmatic access to the current active worker set.
|
749
881
|
#
|
750
882
|
# WARNING WARNING WARNING
|
@@ -765,10 +897,11 @@ module Sidekiq
|
|
765
897
|
#
|
766
898
|
class Workers
|
767
899
|
include Enumerable
|
900
|
+
include RedisScanner
|
768
901
|
|
769
902
|
def each
|
770
903
|
Sidekiq.redis do |conn|
|
771
|
-
procs = conn
|
904
|
+
procs = sscan(conn, 'processes')
|
772
905
|
procs.sort.each do |key|
|
773
906
|
valid, workers = conn.pipelined do
|
774
907
|
conn.exists(key)
|
@@ -790,7 +923,7 @@ module Sidekiq
|
|
790
923
|
# which can easily get out of sync with crashy processes.
|
791
924
|
def size
|
792
925
|
Sidekiq.redis do |conn|
|
793
|
-
procs = conn
|
926
|
+
procs = sscan(conn, 'processes')
|
794
927
|
if procs.empty?
|
795
928
|
0
|
796
929
|
else
|