sidekiq 6.2.2 → 6.5.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/Changes.md +150 -1
- data/LICENSE +3 -3
- data/README.md +9 -4
- data/bin/sidekiq +3 -3
- data/bin/sidekiqload +70 -66
- data/bin/sidekiqmon +1 -1
- data/lib/generators/sidekiq/job_generator.rb +57 -0
- data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
- data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
- data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
- data/lib/sidekiq/api.rb +232 -94
- data/lib/sidekiq/cli.rb +60 -40
- data/lib/sidekiq/client.rb +46 -66
- data/lib/sidekiq/{util.rb → component.rb} +12 -42
- data/lib/sidekiq/delay.rb +3 -1
- data/lib/sidekiq/extensions/generic_proxy.rb +1 -1
- data/lib/sidekiq/fetch.rb +22 -19
- data/lib/sidekiq/job.rb +8 -3
- data/lib/sidekiq/job_logger.rb +15 -27
- data/lib/sidekiq/job_retry.rb +78 -55
- data/lib/sidekiq/job_util.rb +71 -0
- data/lib/sidekiq/launcher.rb +62 -54
- data/lib/sidekiq/logger.rb +8 -18
- data/lib/sidekiq/manager.rb +35 -34
- data/lib/sidekiq/metrics/deploy.rb +47 -0
- data/lib/sidekiq/metrics/query.rb +153 -0
- data/lib/sidekiq/metrics/shared.rb +94 -0
- data/lib/sidekiq/metrics/tracking.rb +134 -0
- data/lib/sidekiq/middleware/chain.rb +82 -38
- data/lib/sidekiq/middleware/current_attributes.rb +63 -0
- data/lib/sidekiq/middleware/i18n.rb +6 -4
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +1 -1
- data/lib/sidekiq/paginator.rb +8 -8
- data/lib/sidekiq/processor.rb +47 -41
- data/lib/sidekiq/rails.rb +22 -4
- data/lib/sidekiq/redis_client_adapter.rb +154 -0
- data/lib/sidekiq/redis_connection.rb +85 -54
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +54 -30
- data/lib/sidekiq/testing/inline.rb +4 -4
- data/lib/sidekiq/testing.rb +37 -36
- data/lib/sidekiq/transaction_aware_client.rb +45 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +3 -3
- data/lib/sidekiq/web/application.rb +25 -9
- data/lib/sidekiq/web/csrf_protection.rb +2 -2
- data/lib/sidekiq/web/helpers.rb +30 -18
- data/lib/sidekiq/web.rb +8 -4
- data/lib/sidekiq/worker.rb +136 -13
- data/lib/sidekiq.rb +114 -31
- data/sidekiq.gemspec +2 -2
- data/web/assets/javascripts/application.js +113 -60
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard.js +50 -67
- data/web/assets/javascripts/graph.js +16 -0
- data/web/assets/javascripts/metrics.js +262 -0
- data/web/assets/stylesheets/application-dark.css +19 -23
- data/web/assets/stylesheets/application-rtl.css +0 -4
- data/web/assets/stylesheets/application.css +55 -109
- data/web/locales/el.yml +43 -19
- data/web/locales/en.yml +8 -1
- data/web/locales/pt-br.yml +27 -9
- data/web/views/_footer.erb +1 -1
- data/web/views/_nav.erb +1 -1
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +4 -4
- data/web/views/dashboard.erb +9 -8
- data/web/views/layout.erb +1 -1
- data/web/views/metrics.erb +69 -0
- data/web/views/metrics_for_job.erb +87 -0
- data/web/views/queue.erb +14 -10
- data/web/views/queues.erb +1 -1
- metadata +27 -12
- data/lib/generators/sidekiq/worker_generator.rb +0 -57
- data/lib/sidekiq/exception_handler.rb +0 -27
data/lib/sidekiq/api.rb
CHANGED
@@ -3,9 +3,20 @@
|
|
3
3
|
require "sidekiq"
|
4
4
|
|
5
5
|
require "zlib"
|
6
|
+
require "set"
|
6
7
|
require "base64"
|
7
8
|
|
9
|
+
if ENV["SIDEKIQ_METRICS_BETA"]
|
10
|
+
require "sidekiq/metrics/deploy"
|
11
|
+
require "sidekiq/metrics/query"
|
12
|
+
end
|
13
|
+
|
8
14
|
module Sidekiq
|
15
|
+
# Retrieve runtime statistics from Redis regarding
|
16
|
+
# this Sidekiq cluster.
|
17
|
+
#
|
18
|
+
# stat = Sidekiq::Stats.new
|
19
|
+
# stat.processed
|
9
20
|
class Stats
|
10
21
|
def initialize
|
11
22
|
fetch_stats_fast!
|
@@ -52,16 +63,17 @@ module Sidekiq
|
|
52
63
|
end
|
53
64
|
|
54
65
|
# O(1) redis calls
|
66
|
+
# @api private
|
55
67
|
def fetch_stats_fast!
|
56
68
|
pipe1_res = Sidekiq.redis { |conn|
|
57
|
-
conn.pipelined do
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
69
|
+
conn.pipelined do |pipeline|
|
70
|
+
pipeline.get("stat:processed")
|
71
|
+
pipeline.get("stat:failed")
|
72
|
+
pipeline.zcard("schedule")
|
73
|
+
pipeline.zcard("retry")
|
74
|
+
pipeline.zcard("dead")
|
75
|
+
pipeline.scard("processes")
|
76
|
+
pipeline.lrange("queue:default", -1, -1)
|
65
77
|
end
|
66
78
|
}
|
67
79
|
|
@@ -91,6 +103,7 @@ module Sidekiq
|
|
91
103
|
end
|
92
104
|
|
93
105
|
# O(number of processes + number of queues) redis calls
|
106
|
+
# @api private
|
94
107
|
def fetch_stats_slow!
|
95
108
|
processes = Sidekiq.redis { |conn|
|
96
109
|
conn.sscan_each("processes").to_a
|
@@ -101,9 +114,9 @@ module Sidekiq
|
|
101
114
|
}
|
102
115
|
|
103
116
|
pipe2_res = Sidekiq.redis { |conn|
|
104
|
-
conn.pipelined do
|
105
|
-
processes.each { |key|
|
106
|
-
queues.each { |queue|
|
117
|
+
conn.pipelined do |pipeline|
|
118
|
+
processes.each { |key| pipeline.hget(key, "busy") }
|
119
|
+
queues.each { |queue| pipeline.llen("queue:#{queue}") }
|
107
120
|
end
|
108
121
|
}
|
109
122
|
|
@@ -113,13 +126,16 @@ module Sidekiq
|
|
113
126
|
|
114
127
|
@stats[:workers_size] = workers_size
|
115
128
|
@stats[:enqueued] = enqueued
|
129
|
+
@stats
|
116
130
|
end
|
117
131
|
|
132
|
+
# @api private
|
118
133
|
def fetch_stats!
|
119
134
|
fetch_stats_fast!
|
120
135
|
fetch_stats_slow!
|
121
136
|
end
|
122
137
|
|
138
|
+
# @api private
|
123
139
|
def reset(*stats)
|
124
140
|
all = %w[failed processed]
|
125
141
|
stats = stats.empty? ? all : all & stats.flatten.compact.map(&:to_s)
|
@@ -146,9 +162,9 @@ module Sidekiq
|
|
146
162
|
Sidekiq.redis do |conn|
|
147
163
|
queues = conn.sscan_each("queues").to_a
|
148
164
|
|
149
|
-
lengths = conn.pipelined {
|
165
|
+
lengths = conn.pipelined { |pipeline|
|
150
166
|
queues.each do |queue|
|
151
|
-
|
167
|
+
pipeline.llen("queue:#{queue}")
|
152
168
|
end
|
153
169
|
}
|
154
170
|
|
@@ -160,6 +176,8 @@ module Sidekiq
|
|
160
176
|
|
161
177
|
class History
|
162
178
|
def initialize(days_previous, start_date = nil)
|
179
|
+
# we only store five years of data in Redis
|
180
|
+
raise ArgumentError if days_previous < 1 || days_previous > (5 * 365)
|
163
181
|
@days_previous = days_previous
|
164
182
|
@start_date = start_date || Time.now.utc.to_date
|
165
183
|
end
|
@@ -188,7 +206,7 @@ module Sidekiq
|
|
188
206
|
stat_hash[dates[idx]] = value ? value.to_i : 0
|
189
207
|
end
|
190
208
|
end
|
191
|
-
rescue
|
209
|
+
rescue RedisConnection.adapter::CommandError
|
192
210
|
# mget will trigger a CROSSSLOT error when run against a Cluster
|
193
211
|
# TODO Someone want to add Cluster support?
|
194
212
|
end
|
@@ -199,9 +217,10 @@ module Sidekiq
|
|
199
217
|
end
|
200
218
|
|
201
219
|
##
|
202
|
-
#
|
220
|
+
# Represents a queue within Sidekiq.
|
203
221
|
# Allows enumeration of all jobs within the queue
|
204
|
-
# and deletion of jobs.
|
222
|
+
# and deletion of jobs. NB: this queue data is real-time
|
223
|
+
# and is changing within Redis moment by moment.
|
205
224
|
#
|
206
225
|
# queue = Sidekiq::Queue.new("mailer")
|
207
226
|
# queue.each do |job|
|
@@ -209,29 +228,34 @@ module Sidekiq
|
|
209
228
|
# job.args # => [1, 2, 3]
|
210
229
|
# job.delete if job.jid == 'abcdef1234567890'
|
211
230
|
# end
|
212
|
-
#
|
213
231
|
class Queue
|
214
232
|
include Enumerable
|
215
233
|
|
216
234
|
##
|
217
|
-
#
|
235
|
+
# Fetch all known queues within Redis.
|
218
236
|
#
|
237
|
+
# @return [Array<Sidekiq::Queue>]
|
219
238
|
def self.all
|
220
239
|
Sidekiq.redis { |c| c.sscan_each("queues").to_a }.sort.map { |q| Sidekiq::Queue.new(q) }
|
221
240
|
end
|
222
241
|
|
223
242
|
attr_reader :name
|
224
243
|
|
244
|
+
# @param name [String] the name of the queue
|
225
245
|
def initialize(name = "default")
|
226
246
|
@name = name.to_s
|
227
247
|
@rname = "queue:#{name}"
|
228
248
|
end
|
229
249
|
|
250
|
+
# The current size of the queue within Redis.
|
251
|
+
# This value is real-time and can change between calls.
|
252
|
+
#
|
253
|
+
# @return [Integer] the size
|
230
254
|
def size
|
231
255
|
Sidekiq.redis { |con| con.llen(@rname) }
|
232
256
|
end
|
233
257
|
|
234
|
-
#
|
258
|
+
# @return [Boolean] if the queue is currently paused
|
235
259
|
def paused?
|
236
260
|
false
|
237
261
|
end
|
@@ -240,7 +264,7 @@ module Sidekiq
|
|
240
264
|
# Calculates this queue's latency, the difference in seconds since the oldest
|
241
265
|
# job in the queue was enqueued.
|
242
266
|
#
|
243
|
-
# @return Float
|
267
|
+
# @return [Float] in seconds
|
244
268
|
def latency
|
245
269
|
entry = Sidekiq.redis { |conn|
|
246
270
|
conn.lrange(@rname, -1, -1)
|
@@ -276,34 +300,54 @@ module Sidekiq
|
|
276
300
|
##
|
277
301
|
# Find the job with the given JID within this queue.
|
278
302
|
#
|
279
|
-
# This is a slow, inefficient operation. Do not use under
|
303
|
+
# This is a *slow, inefficient* operation. Do not use under
|
280
304
|
# normal conditions.
|
305
|
+
#
|
306
|
+
# @param jid [String] the job_id to look for
|
307
|
+
# @return [Sidekiq::JobRecord]
|
308
|
+
# @return [nil] if not found
|
281
309
|
def find_job(jid)
|
282
310
|
detect { |j| j.jid == jid }
|
283
311
|
end
|
284
312
|
|
313
|
+
# delete all jobs within this queue
|
314
|
+
# @return [Boolean] true
|
285
315
|
def clear
|
286
316
|
Sidekiq.redis do |conn|
|
287
|
-
conn.multi do
|
288
|
-
|
289
|
-
|
317
|
+
conn.multi do |transaction|
|
318
|
+
transaction.unlink(@rname)
|
319
|
+
transaction.srem("queues", name)
|
290
320
|
end
|
291
321
|
end
|
322
|
+
true
|
292
323
|
end
|
293
324
|
alias_method :💣, :clear
|
325
|
+
|
326
|
+
# :nodoc:
|
327
|
+
# @api private
|
328
|
+
def as_json(options = nil)
|
329
|
+
{name: name} # 5336
|
330
|
+
end
|
294
331
|
end
|
295
332
|
|
296
333
|
##
|
297
|
-
#
|
298
|
-
# sorted set.
|
334
|
+
# Represents a pending job within a Sidekiq queue.
|
299
335
|
#
|
300
336
|
# The job should be considered immutable but may be
|
301
337
|
# removed from the queue via JobRecord#delete.
|
302
|
-
#
|
303
338
|
class JobRecord
|
339
|
+
# the parsed Hash of job data
|
340
|
+
# @!attribute [r] Item
|
304
341
|
attr_reader :item
|
342
|
+
# the underlying String in Redis
|
343
|
+
# @!attribute [r] Value
|
305
344
|
attr_reader :value
|
345
|
+
# the queue associated with this job
|
346
|
+
# @!attribute [r] Queue
|
347
|
+
attr_reader :queue
|
306
348
|
|
349
|
+
# :nodoc:
|
350
|
+
# @api private
|
307
351
|
def initialize(item, queue_name = nil)
|
308
352
|
@args = nil
|
309
353
|
@value = item
|
@@ -311,6 +355,8 @@ module Sidekiq
|
|
311
355
|
@queue = queue_name || @item["queue"]
|
312
356
|
end
|
313
357
|
|
358
|
+
# :nodoc:
|
359
|
+
# @api private
|
314
360
|
def parse(item)
|
315
361
|
Sidekiq.load_json(item)
|
316
362
|
rescue JSON::ParserError
|
@@ -322,6 +368,8 @@ module Sidekiq
|
|
322
368
|
{}
|
323
369
|
end
|
324
370
|
|
371
|
+
# This is the job class which Sidekiq will execute. If using ActiveJob,
|
372
|
+
# this class will be the ActiveJob adapter class rather than a specific job.
|
325
373
|
def klass
|
326
374
|
self["class"]
|
327
375
|
end
|
@@ -351,27 +399,31 @@ module Sidekiq
|
|
351
399
|
def display_args
|
352
400
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
353
401
|
@display_args ||= case klass
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
402
|
+
when /\ASidekiq::Extensions::Delayed/
|
403
|
+
safe_load(args[0], args) do |_, _, arg, kwarg|
|
404
|
+
if !kwarg || kwarg.empty?
|
405
|
+
arg
|
406
|
+
else
|
407
|
+
[arg, kwarg]
|
408
|
+
end
|
409
|
+
end
|
410
|
+
when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
411
|
+
job_args = self["wrapped"] ? args[0]["arguments"] : []
|
412
|
+
if (self["wrapped"] || args[0]) == "ActionMailer::DeliveryJob"
|
413
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
414
|
+
job_args.drop(3)
|
415
|
+
elsif (self["wrapped"] || args[0]) == "ActionMailer::MailDeliveryJob"
|
416
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
417
|
+
job_args.drop(3).first["args"]
|
418
|
+
else
|
419
|
+
job_args
|
420
|
+
end
|
421
|
+
else
|
422
|
+
if self["encrypt"]
|
423
|
+
# no point in showing 150+ bytes of random garbage
|
424
|
+
args[-1] = "[encrypted data]"
|
425
|
+
end
|
426
|
+
args
|
375
427
|
end
|
376
428
|
end
|
377
429
|
|
@@ -405,15 +457,12 @@ module Sidekiq
|
|
405
457
|
end
|
406
458
|
end
|
407
459
|
|
408
|
-
attr_reader :queue
|
409
|
-
|
410
460
|
def latency
|
411
461
|
now = Time.now.to_f
|
412
462
|
now - (@item["enqueued_at"] || @item["created_at"] || now)
|
413
463
|
end
|
414
464
|
|
415
|
-
|
416
|
-
# Remove this job from the queue.
|
465
|
+
# Remove this job from the queue
|
417
466
|
def delete
|
418
467
|
count = Sidekiq.redis { |conn|
|
419
468
|
conn.lrem("queue:#{@queue}", 1, @value)
|
@@ -421,6 +470,7 @@ module Sidekiq
|
|
421
470
|
count != 0
|
422
471
|
end
|
423
472
|
|
473
|
+
# Access arbitrary attributes within the job hash
|
424
474
|
def [](name)
|
425
475
|
# nil will happen if the JSON fails to parse.
|
426
476
|
# We don't guarantee Sidekiq will work with bad job JSON but we should
|
@@ -435,7 +485,8 @@ module Sidekiq
|
|
435
485
|
rescue => ex
|
436
486
|
# #1761 in dev mode, it's possible to have jobs enqueued which haven't been loaded into
|
437
487
|
# memory yet so the YAML can't be loaded.
|
438
|
-
|
488
|
+
# TODO is this still necessary? Zeitwerk reloader should handle?
|
489
|
+
Sidekiq.logger.warn "Unable to load YAML: #{ex.message}" unless Sidekiq.config[:environment] == "development"
|
439
490
|
default
|
440
491
|
end
|
441
492
|
|
@@ -457,20 +508,28 @@ module Sidekiq
|
|
457
508
|
end
|
458
509
|
end
|
459
510
|
|
511
|
+
# Represents a job within a Redis sorted set where the score
|
512
|
+
# represents a timestamp associated with the job. This timestamp
|
513
|
+
# could be the scheduled time for it to run (e.g. scheduled set),
|
514
|
+
# or the expiration date after which the entry should be deleted (e.g. dead set).
|
460
515
|
class SortedEntry < JobRecord
|
461
516
|
attr_reader :score
|
462
517
|
attr_reader :parent
|
463
518
|
|
519
|
+
# :nodoc:
|
520
|
+
# @api private
|
464
521
|
def initialize(parent, score, item)
|
465
522
|
super(item)
|
466
|
-
@score = score
|
523
|
+
@score = Float(score)
|
467
524
|
@parent = parent
|
468
525
|
end
|
469
526
|
|
527
|
+
# The timestamp associated with this entry
|
470
528
|
def at
|
471
529
|
Time.at(score).utc
|
472
530
|
end
|
473
531
|
|
532
|
+
# remove this entry from the sorted set
|
474
533
|
def delete
|
475
534
|
if @value
|
476
535
|
@parent.delete_by_value(@parent.name, @value)
|
@@ -479,12 +538,17 @@ module Sidekiq
|
|
479
538
|
end
|
480
539
|
end
|
481
540
|
|
541
|
+
# Change the scheduled time for this job.
|
542
|
+
#
|
543
|
+
# @param at [Time] the new timestamp for this job
|
482
544
|
def reschedule(at)
|
483
545
|
Sidekiq.redis do |conn|
|
484
546
|
conn.zincrby(@parent.name, at.to_f - @score, Sidekiq.dump_json(@item))
|
485
547
|
end
|
486
548
|
end
|
487
549
|
|
550
|
+
# Enqueue this job from the scheduled or dead set so it will
|
551
|
+
# be executed at some point in the near future.
|
488
552
|
def add_to_queue
|
489
553
|
remove_job do |message|
|
490
554
|
msg = Sidekiq.load_json(message)
|
@@ -492,6 +556,8 @@ module Sidekiq
|
|
492
556
|
end
|
493
557
|
end
|
494
558
|
|
559
|
+
# enqueue this job from the retry set so it will be executed
|
560
|
+
# at some point in the near future.
|
495
561
|
def retry
|
496
562
|
remove_job do |message|
|
497
563
|
msg = Sidekiq.load_json(message)
|
@@ -500,8 +566,7 @@ module Sidekiq
|
|
500
566
|
end
|
501
567
|
end
|
502
568
|
|
503
|
-
|
504
|
-
# Place job in the dead set
|
569
|
+
# Move this job from its current set into the Dead set.
|
505
570
|
def kill
|
506
571
|
remove_job do |message|
|
507
572
|
DeadSet.new.kill(message)
|
@@ -516,9 +581,9 @@ module Sidekiq
|
|
516
581
|
|
517
582
|
def remove_job
|
518
583
|
Sidekiq.redis do |conn|
|
519
|
-
results = conn.multi {
|
520
|
-
|
521
|
-
|
584
|
+
results = conn.multi { |transaction|
|
585
|
+
transaction.zrangebyscore(parent.name, score, score)
|
586
|
+
transaction.zremrangebyscore(parent.name, score, score)
|
522
587
|
}.first
|
523
588
|
|
524
589
|
if results.size == 1
|
@@ -539,9 +604,9 @@ module Sidekiq
|
|
539
604
|
yield msg if msg
|
540
605
|
|
541
606
|
# push the rest back onto the sorted set
|
542
|
-
conn.multi do
|
607
|
+
conn.multi do |transaction|
|
543
608
|
nonmatched.each do |message|
|
544
|
-
|
609
|
+
transaction.zadd(parent.name, score.to_f.to_s, message)
|
545
610
|
end
|
546
611
|
end
|
547
612
|
end
|
@@ -549,20 +614,32 @@ module Sidekiq
|
|
549
614
|
end
|
550
615
|
end
|
551
616
|
|
617
|
+
# Base class for all sorted sets within Sidekiq.
|
552
618
|
class SortedSet
|
553
619
|
include Enumerable
|
554
620
|
|
621
|
+
# Redis key of the set
|
622
|
+
# @!attribute [r] Name
|
555
623
|
attr_reader :name
|
556
624
|
|
625
|
+
# :nodoc:
|
626
|
+
# @api private
|
557
627
|
def initialize(name)
|
558
628
|
@name = name
|
559
629
|
@_size = size
|
560
630
|
end
|
561
631
|
|
632
|
+
# real-time size of the set, will change
|
562
633
|
def size
|
563
634
|
Sidekiq.redis { |c| c.zcard(name) }
|
564
635
|
end
|
565
636
|
|
637
|
+
# Scan through each element of the sorted set, yielding each to the supplied block.
|
638
|
+
# Please see Redis's <a href="https://redis.io/commands/scan/">SCAN documentation</a> for implementation details.
|
639
|
+
#
|
640
|
+
# @param match [String] a snippet or regexp to filter matches.
|
641
|
+
# @param count [Integer] number of elements to retrieve at a time, default 100
|
642
|
+
# @yieldparam [Sidekiq::SortedEntry] each entry
|
566
643
|
def scan(match, count = 100)
|
567
644
|
return to_enum(:scan, match, count) unless block_given?
|
568
645
|
|
@@ -574,18 +651,32 @@ module Sidekiq
|
|
574
651
|
end
|
575
652
|
end
|
576
653
|
|
654
|
+
# @return [Boolean] always true
|
577
655
|
def clear
|
578
656
|
Sidekiq.redis do |conn|
|
579
657
|
conn.unlink(name)
|
580
658
|
end
|
659
|
+
true
|
581
660
|
end
|
582
661
|
alias_method :💣, :clear
|
662
|
+
|
663
|
+
# :nodoc:
|
664
|
+
# @api private
|
665
|
+
def as_json(options = nil)
|
666
|
+
{name: name} # 5336
|
667
|
+
end
|
583
668
|
end
|
584
669
|
|
670
|
+
# Base class for all sorted sets which contain jobs, e.g. scheduled, retry and dead.
|
671
|
+
# Sidekiq Pro and Enterprise add additional sorted sets which do not contain job data,
|
672
|
+
# e.g. Batches.
|
585
673
|
class JobSet < SortedSet
|
586
|
-
|
674
|
+
# Add a job with the associated timestamp to this set.
|
675
|
+
# @param timestamp [Time] the score for the job
|
676
|
+
# @param job [Hash] the job data
|
677
|
+
def schedule(timestamp, job)
|
587
678
|
Sidekiq.redis do |conn|
|
588
|
-
conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(
|
679
|
+
conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(job))
|
589
680
|
end
|
590
681
|
end
|
591
682
|
|
@@ -599,7 +690,7 @@ module Sidekiq
|
|
599
690
|
range_start = page * page_size + offset_size
|
600
691
|
range_end = range_start + page_size - 1
|
601
692
|
elements = Sidekiq.redis { |conn|
|
602
|
-
conn.zrange name, range_start, range_end,
|
693
|
+
conn.zrange name, range_start, range_end, withscores: true
|
603
694
|
}
|
604
695
|
break if elements.empty?
|
605
696
|
page -= 1
|
@@ -613,6 +704,10 @@ module Sidekiq
|
|
613
704
|
##
|
614
705
|
# Fetch jobs that match a given time or Range. Job ID is an
|
615
706
|
# optional second argument.
|
707
|
+
#
|
708
|
+
# @param score [Time,Range] a specific timestamp or range
|
709
|
+
# @param jid [String, optional] find a specific JID within the score
|
710
|
+
# @return [Array<SortedEntry>] any results found, can be empty
|
616
711
|
def fetch(score, jid = nil)
|
617
712
|
begin_score, end_score =
|
618
713
|
if score.is_a?(Range)
|
@@ -622,7 +717,7 @@ module Sidekiq
|
|
622
717
|
end
|
623
718
|
|
624
719
|
elements = Sidekiq.redis { |conn|
|
625
|
-
conn.zrangebyscore(name, begin_score, end_score,
|
720
|
+
conn.zrangebyscore(name, begin_score, end_score, withscores: true)
|
626
721
|
}
|
627
722
|
|
628
723
|
elements.each_with_object([]) do |element, result|
|
@@ -634,7 +729,10 @@ module Sidekiq
|
|
634
729
|
|
635
730
|
##
|
636
731
|
# Find the job with the given JID within this sorted set.
|
637
|
-
# This is a
|
732
|
+
# *This is a slow O(n) operation*. Do not use for app logic.
|
733
|
+
#
|
734
|
+
# @param jid [String] the job identifier
|
735
|
+
# @return [SortedEntry] the record or nil
|
638
736
|
def find_job(jid)
|
639
737
|
Sidekiq.redis do |conn|
|
640
738
|
conn.zscan_each(name, match: "*#{jid}*", count: 100) do |entry, score|
|
@@ -646,6 +744,8 @@ module Sidekiq
|
|
646
744
|
nil
|
647
745
|
end
|
648
746
|
|
747
|
+
# :nodoc:
|
748
|
+
# @api private
|
649
749
|
def delete_by_value(name, value)
|
650
750
|
Sidekiq.redis do |conn|
|
651
751
|
ret = conn.zrem(name, value)
|
@@ -654,6 +754,8 @@ module Sidekiq
|
|
654
754
|
end
|
655
755
|
end
|
656
756
|
|
757
|
+
# :nodoc:
|
758
|
+
# @api private
|
657
759
|
def delete_by_jid(score, jid)
|
658
760
|
Sidekiq.redis do |conn|
|
659
761
|
elements = conn.zrangebyscore(name, score, score)
|
@@ -674,10 +776,10 @@ module Sidekiq
|
|
674
776
|
end
|
675
777
|
|
676
778
|
##
|
677
|
-
#
|
779
|
+
# The set of scheduled jobs within Sidekiq.
|
678
780
|
# Based on this, you can search/filter for jobs. Here's an
|
679
|
-
# example where I'm selecting
|
680
|
-
# and deleting them from the
|
781
|
+
# example where I'm selecting jobs based on some complex logic
|
782
|
+
# and deleting them from the scheduled set.
|
681
783
|
#
|
682
784
|
# r = Sidekiq::ScheduledSet.new
|
683
785
|
# r.select do |scheduled|
|
@@ -692,7 +794,7 @@ module Sidekiq
|
|
692
794
|
end
|
693
795
|
|
694
796
|
##
|
695
|
-
#
|
797
|
+
# The set of retries within Sidekiq.
|
696
798
|
# Based on this, you can search/filter for jobs. Here's an
|
697
799
|
# example where I'm selecting all jobs of a certain type
|
698
800
|
# and deleting them from the retry queue.
|
@@ -708,30 +810,36 @@ module Sidekiq
|
|
708
810
|
super "retry"
|
709
811
|
end
|
710
812
|
|
813
|
+
# Enqueues all jobs pending within the retry set.
|
711
814
|
def retry_all
|
712
815
|
each(&:retry) while size > 0
|
713
816
|
end
|
714
817
|
|
818
|
+
# Kills all jobs pending within the retry set.
|
715
819
|
def kill_all
|
716
820
|
each(&:kill) while size > 0
|
717
821
|
end
|
718
822
|
end
|
719
823
|
|
720
824
|
##
|
721
|
-
#
|
825
|
+
# The set of dead jobs within Sidekiq. Dead jobs have failed all of
|
826
|
+
# their retries and are helding in this set pending some sort of manual
|
827
|
+
# fix. They will be removed after 6 months (dead_timeout) if not.
|
722
828
|
#
|
723
829
|
class DeadSet < JobSet
|
724
830
|
def initialize
|
725
831
|
super "dead"
|
726
832
|
end
|
727
833
|
|
834
|
+
# Add the given job to the Dead set.
|
835
|
+
# @param message [String] the job data as JSON
|
728
836
|
def kill(message, opts = {})
|
729
837
|
now = Time.now.to_f
|
730
838
|
Sidekiq.redis do |conn|
|
731
|
-
conn.multi do
|
732
|
-
|
733
|
-
|
734
|
-
|
839
|
+
conn.multi do |transaction|
|
840
|
+
transaction.zadd(name, now.to_s, message)
|
841
|
+
transaction.zremrangebyscore(name, "-inf", now - self.class.timeout)
|
842
|
+
transaction.zremrangebyrank(name, 0, - self.class.max_jobs)
|
735
843
|
end
|
736
844
|
end
|
737
845
|
|
@@ -746,16 +854,21 @@ module Sidekiq
|
|
746
854
|
true
|
747
855
|
end
|
748
856
|
|
857
|
+
# Enqueue all dead jobs
|
749
858
|
def retry_all
|
750
859
|
each(&:retry) while size > 0
|
751
860
|
end
|
752
861
|
|
862
|
+
# The maximum size of the Dead set. Older entries will be trimmed
|
863
|
+
# to stay within this limit. Default value is 10,000.
|
753
864
|
def self.max_jobs
|
754
|
-
Sidekiq
|
865
|
+
Sidekiq[:dead_max_jobs]
|
755
866
|
end
|
756
867
|
|
868
|
+
# The time limit for entries within the Dead set. Older entries will be thrown away.
|
869
|
+
# Default value is six months.
|
757
870
|
def self.timeout
|
758
|
-
Sidekiq
|
871
|
+
Sidekiq[:dead_timeout_in_seconds]
|
759
872
|
end
|
760
873
|
end
|
761
874
|
|
@@ -764,24 +877,29 @@ module Sidekiq
|
|
764
877
|
# right now. Each process sends a heartbeat to Redis every 5 seconds
|
765
878
|
# so this set should be relatively accurate, barring network partitions.
|
766
879
|
#
|
767
|
-
#
|
880
|
+
# @yieldparam [Sidekiq::Process]
|
768
881
|
#
|
769
882
|
class ProcessSet
|
770
883
|
include Enumerable
|
771
884
|
|
885
|
+
# :nodoc:
|
886
|
+
# @api private
|
772
887
|
def initialize(clean_plz = true)
|
773
888
|
cleanup if clean_plz
|
774
889
|
end
|
775
890
|
|
776
891
|
# Cleans up dead processes recorded in Redis.
|
777
892
|
# Returns the number of processes cleaned.
|
893
|
+
# :nodoc:
|
894
|
+
# @api private
|
778
895
|
def cleanup
|
896
|
+
return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1", nx: true, ex: 60) }
|
779
897
|
count = 0
|
780
898
|
Sidekiq.redis do |conn|
|
781
899
|
procs = conn.sscan_each("processes").to_a.sort
|
782
|
-
heartbeats = conn.pipelined {
|
900
|
+
heartbeats = conn.pipelined { |pipeline|
|
783
901
|
procs.each do |key|
|
784
|
-
|
902
|
+
pipeline.hget(key, "info")
|
785
903
|
end
|
786
904
|
}
|
787
905
|
|
@@ -803,9 +921,9 @@ module Sidekiq
|
|
803
921
|
# We're making a tradeoff here between consuming more memory instead of
|
804
922
|
# making more roundtrips to Redis, but if you have hundreds or thousands of workers,
|
805
923
|
# you'll be happier this way
|
806
|
-
conn.pipelined do
|
924
|
+
conn.pipelined do |pipeline|
|
807
925
|
procs.each do |key|
|
808
|
-
|
926
|
+
pipeline.hmget(key, "info", "busy", "beat", "quiet", "rss", "rtt_us")
|
809
927
|
end
|
810
928
|
end
|
811
929
|
}
|
@@ -818,10 +936,10 @@ module Sidekiq
|
|
818
936
|
|
819
937
|
hash = Sidekiq.load_json(info)
|
820
938
|
yield Process.new(hash.merge("busy" => busy.to_i,
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
939
|
+
"beat" => at_s.to_f,
|
940
|
+
"quiet" => quiet,
|
941
|
+
"rss" => rss.to_i,
|
942
|
+
"rtt_us" => rtt.to_i))
|
825
943
|
end
|
826
944
|
end
|
827
945
|
|
@@ -829,6 +947,7 @@ module Sidekiq
|
|
829
947
|
# based on current heartbeat. #each does that and ensures the set only
|
830
948
|
# contains Sidekiq processes which have sent a heartbeat within the last
|
831
949
|
# 60 seconds.
|
950
|
+
# @return [Integer] current number of registered Sidekiq processes
|
832
951
|
def size
|
833
952
|
Sidekiq.redis { |conn| conn.scard("processes") }
|
834
953
|
end
|
@@ -836,10 +955,12 @@ module Sidekiq
|
|
836
955
|
# Total number of threads available to execute jobs.
|
837
956
|
# For Sidekiq Enterprise customers this number (in production) must be
|
838
957
|
# less than or equal to your licensed concurrency.
|
958
|
+
# @return [Integer] the sum of process concurrency
|
839
959
|
def total_concurrency
|
840
960
|
sum { |x| x["concurrency"].to_i }
|
841
961
|
end
|
842
962
|
|
963
|
+
# @return [Integer] total amount of RSS memory consumed by Sidekiq processes
|
843
964
|
def total_rss_in_kb
|
844
965
|
sum { |x| x["rss"].to_i }
|
845
966
|
end
|
@@ -848,6 +969,8 @@ module Sidekiq
|
|
848
969
|
# Returns the identity of the current cluster leader or "" if no leader.
|
849
970
|
# This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
|
850
971
|
# or Sidekiq Pro.
|
972
|
+
# @return [String] Identity of cluster leader
|
973
|
+
# @return [String] empty string if no leader
|
851
974
|
def leader
|
852
975
|
@leader ||= begin
|
853
976
|
x = Sidekiq.redis { |c| c.get("dear-leader") }
|
@@ -874,6 +997,8 @@ module Sidekiq
|
|
874
997
|
# 'identity' => <unique string identifying the process>,
|
875
998
|
# }
|
876
999
|
class Process
|
1000
|
+
# :nodoc:
|
1001
|
+
# @api private
|
877
1002
|
def initialize(hash)
|
878
1003
|
@attribs = hash
|
879
1004
|
end
|
@@ -898,18 +1023,31 @@ module Sidekiq
|
|
898
1023
|
self["queues"]
|
899
1024
|
end
|
900
1025
|
|
1026
|
+
# Signal this process to stop processing new jobs.
|
1027
|
+
# It will continue to execute jobs it has already fetched.
|
1028
|
+
# This method is *asynchronous* and it can take 5-10
|
1029
|
+
# seconds for the process to quiet.
|
901
1030
|
def quiet!
|
902
1031
|
signal("TSTP")
|
903
1032
|
end
|
904
1033
|
|
1034
|
+
# Signal this process to shutdown.
|
1035
|
+
# It will shutdown within its configured :timeout value, default 25 seconds.
|
1036
|
+
# This method is *asynchronous* and it can take 5-10
|
1037
|
+
# seconds for the process to start shutting down.
|
905
1038
|
def stop!
|
906
1039
|
signal("TERM")
|
907
1040
|
end
|
908
1041
|
|
1042
|
+
# Signal this process to log backtraces for all threads.
|
1043
|
+
# Useful if you have a frozen or deadlocked process which is
|
1044
|
+
# still sending a heartbeat.
|
1045
|
+
# This method is *asynchronous* and it can take 5-10 seconds.
|
909
1046
|
def dump_threads
|
910
1047
|
signal("TTIN")
|
911
1048
|
end
|
912
1049
|
|
1050
|
+
# @return [Boolean] true if this process is quiet or shutting down
|
913
1051
|
def stopping?
|
914
1052
|
self["quiet"] == "true"
|
915
1053
|
end
|
@@ -919,9 +1057,9 @@ module Sidekiq
|
|
919
1057
|
def signal(sig)
|
920
1058
|
key = "#{identity}-signals"
|
921
1059
|
Sidekiq.redis do |c|
|
922
|
-
c.multi do
|
923
|
-
|
924
|
-
|
1060
|
+
c.multi do |transaction|
|
1061
|
+
transaction.lpush(key, sig)
|
1062
|
+
transaction.expire(key, 60)
|
925
1063
|
end
|
926
1064
|
end
|
927
1065
|
end
|
@@ -955,9 +1093,9 @@ module Sidekiq
|
|
955
1093
|
Sidekiq.redis do |conn|
|
956
1094
|
procs = conn.sscan_each("processes").to_a
|
957
1095
|
procs.sort.each do |key|
|
958
|
-
valid, workers = conn.pipelined {
|
959
|
-
|
960
|
-
|
1096
|
+
valid, workers = conn.pipelined { |pipeline|
|
1097
|
+
pipeline.exists?(key)
|
1098
|
+
pipeline.hgetall("#{key}:work")
|
961
1099
|
}
|
962
1100
|
next unless valid
|
963
1101
|
workers.each_pair do |tid, json|
|
@@ -985,9 +1123,9 @@ module Sidekiq
|
|
985
1123
|
if procs.empty?
|
986
1124
|
0
|
987
1125
|
else
|
988
|
-
conn.pipelined {
|
1126
|
+
conn.pipelined { |pipeline|
|
989
1127
|
procs.each do |key|
|
990
|
-
|
1128
|
+
pipeline.hget(key, "busy")
|
991
1129
|
end
|
992
1130
|
}.sum(&:to_i)
|
993
1131
|
end
|