sidekiq 6.4.1 → 6.5.6
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 +81 -1
- data/README.md +1 -1
- data/bin/sidekiqload +16 -10
- data/lib/sidekiq/api.rb +189 -58
- data/lib/sidekiq/cli.rb +39 -37
- data/lib/sidekiq/client.rb +26 -27
- data/lib/sidekiq/component.rb +65 -0
- data/lib/sidekiq/delay.rb +1 -1
- data/lib/sidekiq/extensions/generic_proxy.rb +1 -1
- data/lib/sidekiq/fetch.rb +18 -16
- data/lib/sidekiq/job_retry.rb +73 -52
- data/lib/sidekiq/job_util.rb +15 -9
- data/lib/sidekiq/launcher.rb +37 -33
- data/lib/sidekiq/logger.rb +5 -19
- data/lib/sidekiq/manager.rb +28 -25
- 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 +10 -4
- 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 +2 -2
- data/lib/sidekiq/processor.rb +47 -41
- data/lib/sidekiq/rails.rb +15 -8
- data/lib/sidekiq/redis_client_adapter.rb +154 -0
- data/lib/sidekiq/redis_connection.rb +80 -49
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +12 -17
- 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 +18 -5
- data/lib/sidekiq/web/csrf_protection.rb +2 -2
- data/lib/sidekiq/web/helpers.rb +28 -5
- data/lib/sidekiq/web.rb +5 -1
- data/lib/sidekiq/worker.rb +18 -13
- data/lib/sidekiq.rb +106 -31
- data/sidekiq.gemspec +2 -2
- data/web/assets/javascripts/application.js +58 -26
- 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 +0 -17
- data/web/assets/javascripts/graph.js +16 -0
- data/web/assets/javascripts/metrics.js +262 -0
- data/web/assets/stylesheets/application.css +45 -3
- data/web/locales/el.yml +43 -19
- data/web/locales/en.yml +7 -0
- data/web/locales/pt-br.yml +27 -9
- data/web/views/_nav.erb +1 -1
- data/web/views/_summary.erb +1 -1
- data/web/views/busy.erb +4 -4
- data/web/views/dashboard.erb +1 -0
- data/web/views/metrics.erb +69 -0
- data/web/views/metrics_for_job.erb +87 -0
- data/web/views/queue.erb +5 -1
- metadata +27 -8
- data/lib/sidekiq/exception_handler.rb +0 -27
- data/lib/sidekiq/util.rb +0 -108
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,6 +63,7 @@ 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
69
|
conn.pipelined do |pipeline|
|
@@ -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
|
@@ -116,11 +129,13 @@ module Sidekiq
|
|
116
129
|
@stats
|
117
130
|
end
|
118
131
|
|
132
|
+
# @api private
|
119
133
|
def fetch_stats!
|
120
134
|
fetch_stats_fast!
|
121
135
|
fetch_stats_slow!
|
122
136
|
end
|
123
137
|
|
138
|
+
# @api private
|
124
139
|
def reset(*stats)
|
125
140
|
all = %w[failed processed]
|
126
141
|
stats = stats.empty? ? all : all & stats.flatten.compact.map(&:to_s)
|
@@ -191,7 +206,7 @@ module Sidekiq
|
|
191
206
|
stat_hash[dates[idx]] = value ? value.to_i : 0
|
192
207
|
end
|
193
208
|
end
|
194
|
-
rescue
|
209
|
+
rescue RedisConnection.adapter::CommandError
|
195
210
|
# mget will trigger a CROSSSLOT error when run against a Cluster
|
196
211
|
# TODO Someone want to add Cluster support?
|
197
212
|
end
|
@@ -202,9 +217,10 @@ module Sidekiq
|
|
202
217
|
end
|
203
218
|
|
204
219
|
##
|
205
|
-
#
|
220
|
+
# Represents a queue within Sidekiq.
|
206
221
|
# Allows enumeration of all jobs within the queue
|
207
|
-
# 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.
|
208
224
|
#
|
209
225
|
# queue = Sidekiq::Queue.new("mailer")
|
210
226
|
# queue.each do |job|
|
@@ -212,29 +228,34 @@ module Sidekiq
|
|
212
228
|
# job.args # => [1, 2, 3]
|
213
229
|
# job.delete if job.jid == 'abcdef1234567890'
|
214
230
|
# end
|
215
|
-
#
|
216
231
|
class Queue
|
217
232
|
include Enumerable
|
218
233
|
|
219
234
|
##
|
220
|
-
#
|
235
|
+
# Fetch all known queues within Redis.
|
221
236
|
#
|
237
|
+
# @return [Array<Sidekiq::Queue>]
|
222
238
|
def self.all
|
223
239
|
Sidekiq.redis { |c| c.sscan_each("queues").to_a }.sort.map { |q| Sidekiq::Queue.new(q) }
|
224
240
|
end
|
225
241
|
|
226
242
|
attr_reader :name
|
227
243
|
|
244
|
+
# @param name [String] the name of the queue
|
228
245
|
def initialize(name = "default")
|
229
246
|
@name = name.to_s
|
230
247
|
@rname = "queue:#{name}"
|
231
248
|
end
|
232
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
|
233
254
|
def size
|
234
255
|
Sidekiq.redis { |con| con.llen(@rname) }
|
235
256
|
end
|
236
257
|
|
237
|
-
#
|
258
|
+
# @return [Boolean] if the queue is currently paused
|
238
259
|
def paused?
|
239
260
|
false
|
240
261
|
end
|
@@ -243,7 +264,7 @@ module Sidekiq
|
|
243
264
|
# Calculates this queue's latency, the difference in seconds since the oldest
|
244
265
|
# job in the queue was enqueued.
|
245
266
|
#
|
246
|
-
# @return Float
|
267
|
+
# @return [Float] in seconds
|
247
268
|
def latency
|
248
269
|
entry = Sidekiq.redis { |conn|
|
249
270
|
conn.lrange(@rname, -1, -1)
|
@@ -279,34 +300,54 @@ module Sidekiq
|
|
279
300
|
##
|
280
301
|
# Find the job with the given JID within this queue.
|
281
302
|
#
|
282
|
-
# This is a slow, inefficient operation. Do not use under
|
303
|
+
# This is a *slow, inefficient* operation. Do not use under
|
283
304
|
# normal conditions.
|
305
|
+
#
|
306
|
+
# @param jid [String] the job_id to look for
|
307
|
+
# @return [Sidekiq::JobRecord]
|
308
|
+
# @return [nil] if not found
|
284
309
|
def find_job(jid)
|
285
310
|
detect { |j| j.jid == jid }
|
286
311
|
end
|
287
312
|
|
313
|
+
# delete all jobs within this queue
|
314
|
+
# @return [Boolean] true
|
288
315
|
def clear
|
289
316
|
Sidekiq.redis do |conn|
|
290
317
|
conn.multi do |transaction|
|
291
318
|
transaction.unlink(@rname)
|
292
|
-
transaction.srem("queues", name)
|
319
|
+
transaction.srem("queues", [name])
|
293
320
|
end
|
294
321
|
end
|
322
|
+
true
|
295
323
|
end
|
296
324
|
alias_method :💣, :clear
|
325
|
+
|
326
|
+
# :nodoc:
|
327
|
+
# @api private
|
328
|
+
def as_json(options = nil)
|
329
|
+
{name: name} # 5336
|
330
|
+
end
|
297
331
|
end
|
298
332
|
|
299
333
|
##
|
300
|
-
#
|
301
|
-
# sorted set.
|
334
|
+
# Represents a pending job within a Sidekiq queue.
|
302
335
|
#
|
303
336
|
# The job should be considered immutable but may be
|
304
337
|
# removed from the queue via JobRecord#delete.
|
305
|
-
#
|
306
338
|
class JobRecord
|
339
|
+
# the parsed Hash of job data
|
340
|
+
# @!attribute [r] Item
|
307
341
|
attr_reader :item
|
342
|
+
# the underlying String in Redis
|
343
|
+
# @!attribute [r] Value
|
308
344
|
attr_reader :value
|
345
|
+
# the queue associated with this job
|
346
|
+
# @!attribute [r] Queue
|
347
|
+
attr_reader :queue
|
309
348
|
|
349
|
+
# :nodoc:
|
350
|
+
# @api private
|
310
351
|
def initialize(item, queue_name = nil)
|
311
352
|
@args = nil
|
312
353
|
@value = item
|
@@ -314,6 +355,8 @@ module Sidekiq
|
|
314
355
|
@queue = queue_name || @item["queue"]
|
315
356
|
end
|
316
357
|
|
358
|
+
# :nodoc:
|
359
|
+
# @api private
|
317
360
|
def parse(item)
|
318
361
|
Sidekiq.load_json(item)
|
319
362
|
rescue JSON::ParserError
|
@@ -325,6 +368,8 @@ module Sidekiq
|
|
325
368
|
{}
|
326
369
|
end
|
327
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.
|
328
373
|
def klass
|
329
374
|
self["class"]
|
330
375
|
end
|
@@ -354,31 +399,31 @@ module Sidekiq
|
|
354
399
|
def display_args
|
355
400
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
356
401
|
@display_args ||= case klass
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
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
|
382
427
|
end
|
383
428
|
end
|
384
429
|
|
@@ -412,15 +457,12 @@ module Sidekiq
|
|
412
457
|
end
|
413
458
|
end
|
414
459
|
|
415
|
-
attr_reader :queue
|
416
|
-
|
417
460
|
def latency
|
418
461
|
now = Time.now.to_f
|
419
462
|
now - (@item["enqueued_at"] || @item["created_at"] || now)
|
420
463
|
end
|
421
464
|
|
422
|
-
|
423
|
-
# Remove this job from the queue.
|
465
|
+
# Remove this job from the queue
|
424
466
|
def delete
|
425
467
|
count = Sidekiq.redis { |conn|
|
426
468
|
conn.lrem("queue:#{@queue}", 1, @value)
|
@@ -428,6 +470,7 @@ module Sidekiq
|
|
428
470
|
count != 0
|
429
471
|
end
|
430
472
|
|
473
|
+
# Access arbitrary attributes within the job hash
|
431
474
|
def [](name)
|
432
475
|
# nil will happen if the JSON fails to parse.
|
433
476
|
# We don't guarantee Sidekiq will work with bad job JSON but we should
|
@@ -442,6 +485,7 @@ module Sidekiq
|
|
442
485
|
rescue => ex
|
443
486
|
# #1761 in dev mode, it's possible to have jobs enqueued which haven't been loaded into
|
444
487
|
# memory yet so the YAML can't be loaded.
|
488
|
+
# TODO is this still necessary? Zeitwerk reloader should handle?
|
445
489
|
Sidekiq.logger.warn "Unable to load YAML: #{ex.message}" unless Sidekiq.options[:environment] == "development"
|
446
490
|
default
|
447
491
|
end
|
@@ -464,20 +508,28 @@ module Sidekiq
|
|
464
508
|
end
|
465
509
|
end
|
466
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).
|
467
515
|
class SortedEntry < JobRecord
|
468
516
|
attr_reader :score
|
469
517
|
attr_reader :parent
|
470
518
|
|
519
|
+
# :nodoc:
|
520
|
+
# @api private
|
471
521
|
def initialize(parent, score, item)
|
472
522
|
super(item)
|
473
|
-
@score = score
|
523
|
+
@score = Float(score)
|
474
524
|
@parent = parent
|
475
525
|
end
|
476
526
|
|
527
|
+
# The timestamp associated with this entry
|
477
528
|
def at
|
478
529
|
Time.at(score).utc
|
479
530
|
end
|
480
531
|
|
532
|
+
# remove this entry from the sorted set
|
481
533
|
def delete
|
482
534
|
if @value
|
483
535
|
@parent.delete_by_value(@parent.name, @value)
|
@@ -486,12 +538,17 @@ module Sidekiq
|
|
486
538
|
end
|
487
539
|
end
|
488
540
|
|
541
|
+
# Change the scheduled time for this job.
|
542
|
+
#
|
543
|
+
# @param at [Time] the new timestamp for this job
|
489
544
|
def reschedule(at)
|
490
545
|
Sidekiq.redis do |conn|
|
491
546
|
conn.zincrby(@parent.name, at.to_f - @score, Sidekiq.dump_json(@item))
|
492
547
|
end
|
493
548
|
end
|
494
549
|
|
550
|
+
# Enqueue this job from the scheduled or dead set so it will
|
551
|
+
# be executed at some point in the near future.
|
495
552
|
def add_to_queue
|
496
553
|
remove_job do |message|
|
497
554
|
msg = Sidekiq.load_json(message)
|
@@ -499,6 +556,8 @@ module Sidekiq
|
|
499
556
|
end
|
500
557
|
end
|
501
558
|
|
559
|
+
# enqueue this job from the retry set so it will be executed
|
560
|
+
# at some point in the near future.
|
502
561
|
def retry
|
503
562
|
remove_job do |message|
|
504
563
|
msg = Sidekiq.load_json(message)
|
@@ -507,8 +566,7 @@ module Sidekiq
|
|
507
566
|
end
|
508
567
|
end
|
509
568
|
|
510
|
-
|
511
|
-
# Place job in the dead set
|
569
|
+
# Move this job from its current set into the Dead set.
|
512
570
|
def kill
|
513
571
|
remove_job do |message|
|
514
572
|
DeadSet.new.kill(message)
|
@@ -556,20 +614,32 @@ module Sidekiq
|
|
556
614
|
end
|
557
615
|
end
|
558
616
|
|
617
|
+
# Base class for all sorted sets within Sidekiq.
|
559
618
|
class SortedSet
|
560
619
|
include Enumerable
|
561
620
|
|
621
|
+
# Redis key of the set
|
622
|
+
# @!attribute [r] Name
|
562
623
|
attr_reader :name
|
563
624
|
|
625
|
+
# :nodoc:
|
626
|
+
# @api private
|
564
627
|
def initialize(name)
|
565
628
|
@name = name
|
566
629
|
@_size = size
|
567
630
|
end
|
568
631
|
|
632
|
+
# real-time size of the set, will change
|
569
633
|
def size
|
570
634
|
Sidekiq.redis { |c| c.zcard(name) }
|
571
635
|
end
|
572
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
|
573
643
|
def scan(match, count = 100)
|
574
644
|
return to_enum(:scan, match, count) unless block_given?
|
575
645
|
|
@@ -581,18 +651,32 @@ module Sidekiq
|
|
581
651
|
end
|
582
652
|
end
|
583
653
|
|
654
|
+
# @return [Boolean] always true
|
584
655
|
def clear
|
585
656
|
Sidekiq.redis do |conn|
|
586
657
|
conn.unlink(name)
|
587
658
|
end
|
659
|
+
true
|
588
660
|
end
|
589
661
|
alias_method :💣, :clear
|
662
|
+
|
663
|
+
# :nodoc:
|
664
|
+
# @api private
|
665
|
+
def as_json(options = nil)
|
666
|
+
{name: name} # 5336
|
667
|
+
end
|
590
668
|
end
|
591
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.
|
592
673
|
class JobSet < SortedSet
|
593
|
-
|
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)
|
594
678
|
Sidekiq.redis do |conn|
|
595
|
-
conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(
|
679
|
+
conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(job))
|
596
680
|
end
|
597
681
|
end
|
598
682
|
|
@@ -606,7 +690,7 @@ module Sidekiq
|
|
606
690
|
range_start = page * page_size + offset_size
|
607
691
|
range_end = range_start + page_size - 1
|
608
692
|
elements = Sidekiq.redis { |conn|
|
609
|
-
conn.zrange name, range_start, range_end,
|
693
|
+
conn.zrange name, range_start, range_end, withscores: true
|
610
694
|
}
|
611
695
|
break if elements.empty?
|
612
696
|
page -= 1
|
@@ -620,6 +704,10 @@ module Sidekiq
|
|
620
704
|
##
|
621
705
|
# Fetch jobs that match a given time or Range. Job ID is an
|
622
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
|
623
711
|
def fetch(score, jid = nil)
|
624
712
|
begin_score, end_score =
|
625
713
|
if score.is_a?(Range)
|
@@ -629,7 +717,7 @@ module Sidekiq
|
|
629
717
|
end
|
630
718
|
|
631
719
|
elements = Sidekiq.redis { |conn|
|
632
|
-
conn.zrangebyscore(name, begin_score, end_score,
|
720
|
+
conn.zrangebyscore(name, begin_score, end_score, withscores: true)
|
633
721
|
}
|
634
722
|
|
635
723
|
elements.each_with_object([]) do |element, result|
|
@@ -641,7 +729,10 @@ module Sidekiq
|
|
641
729
|
|
642
730
|
##
|
643
731
|
# Find the job with the given JID within this sorted set.
|
644
|
-
# 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
|
645
736
|
def find_job(jid)
|
646
737
|
Sidekiq.redis do |conn|
|
647
738
|
conn.zscan_each(name, match: "*#{jid}*", count: 100) do |entry, score|
|
@@ -653,6 +744,8 @@ module Sidekiq
|
|
653
744
|
nil
|
654
745
|
end
|
655
746
|
|
747
|
+
# :nodoc:
|
748
|
+
# @api private
|
656
749
|
def delete_by_value(name, value)
|
657
750
|
Sidekiq.redis do |conn|
|
658
751
|
ret = conn.zrem(name, value)
|
@@ -661,6 +754,8 @@ module Sidekiq
|
|
661
754
|
end
|
662
755
|
end
|
663
756
|
|
757
|
+
# :nodoc:
|
758
|
+
# @api private
|
664
759
|
def delete_by_jid(score, jid)
|
665
760
|
Sidekiq.redis do |conn|
|
666
761
|
elements = conn.zrangebyscore(name, score, score)
|
@@ -681,10 +776,10 @@ module Sidekiq
|
|
681
776
|
end
|
682
777
|
|
683
778
|
##
|
684
|
-
#
|
779
|
+
# The set of scheduled jobs within Sidekiq.
|
685
780
|
# Based on this, you can search/filter for jobs. Here's an
|
686
|
-
# example where I'm selecting
|
687
|
-
# 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.
|
688
783
|
#
|
689
784
|
# r = Sidekiq::ScheduledSet.new
|
690
785
|
# r.select do |scheduled|
|
@@ -699,7 +794,7 @@ module Sidekiq
|
|
699
794
|
end
|
700
795
|
|
701
796
|
##
|
702
|
-
#
|
797
|
+
# The set of retries within Sidekiq.
|
703
798
|
# Based on this, you can search/filter for jobs. Here's an
|
704
799
|
# example where I'm selecting all jobs of a certain type
|
705
800
|
# and deleting them from the retry queue.
|
@@ -715,23 +810,29 @@ module Sidekiq
|
|
715
810
|
super "retry"
|
716
811
|
end
|
717
812
|
|
813
|
+
# Enqueues all jobs pending within the retry set.
|
718
814
|
def retry_all
|
719
815
|
each(&:retry) while size > 0
|
720
816
|
end
|
721
817
|
|
818
|
+
# Kills all jobs pending within the retry set.
|
722
819
|
def kill_all
|
723
820
|
each(&:kill) while size > 0
|
724
821
|
end
|
725
822
|
end
|
726
823
|
|
727
824
|
##
|
728
|
-
#
|
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.
|
729
828
|
#
|
730
829
|
class DeadSet < JobSet
|
731
830
|
def initialize
|
732
831
|
super "dead"
|
733
832
|
end
|
734
833
|
|
834
|
+
# Add the given job to the Dead set.
|
835
|
+
# @param message [String] the job data as JSON
|
735
836
|
def kill(message, opts = {})
|
736
837
|
now = Time.now.to_f
|
737
838
|
Sidekiq.redis do |conn|
|
@@ -753,16 +854,21 @@ module Sidekiq
|
|
753
854
|
true
|
754
855
|
end
|
755
856
|
|
857
|
+
# Enqueue all dead jobs
|
756
858
|
def retry_all
|
757
859
|
each(&:retry) while size > 0
|
758
860
|
end
|
759
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.
|
760
864
|
def self.max_jobs
|
761
|
-
Sidekiq
|
865
|
+
Sidekiq[:dead_max_jobs]
|
762
866
|
end
|
763
867
|
|
868
|
+
# The time limit for entries within the Dead set. Older entries will be thrown away.
|
869
|
+
# Default value is six months.
|
764
870
|
def self.timeout
|
765
|
-
Sidekiq
|
871
|
+
Sidekiq[:dead_timeout_in_seconds]
|
766
872
|
end
|
767
873
|
end
|
768
874
|
|
@@ -771,18 +877,23 @@ module Sidekiq
|
|
771
877
|
# right now. Each process sends a heartbeat to Redis every 5 seconds
|
772
878
|
# so this set should be relatively accurate, barring network partitions.
|
773
879
|
#
|
774
|
-
#
|
880
|
+
# @yieldparam [Sidekiq::Process]
|
775
881
|
#
|
776
882
|
class ProcessSet
|
777
883
|
include Enumerable
|
778
884
|
|
885
|
+
# :nodoc:
|
886
|
+
# @api private
|
779
887
|
def initialize(clean_plz = true)
|
780
888
|
cleanup if clean_plz
|
781
889
|
end
|
782
890
|
|
783
891
|
# Cleans up dead processes recorded in Redis.
|
784
892
|
# Returns the number of processes cleaned.
|
893
|
+
# :nodoc:
|
894
|
+
# @api private
|
785
895
|
def cleanup
|
896
|
+
return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1", nx: true, ex: 60) }
|
786
897
|
count = 0
|
787
898
|
Sidekiq.redis do |conn|
|
788
899
|
procs = conn.sscan_each("processes").to_a.sort
|
@@ -836,6 +947,7 @@ module Sidekiq
|
|
836
947
|
# based on current heartbeat. #each does that and ensures the set only
|
837
948
|
# contains Sidekiq processes which have sent a heartbeat within the last
|
838
949
|
# 60 seconds.
|
950
|
+
# @return [Integer] current number of registered Sidekiq processes
|
839
951
|
def size
|
840
952
|
Sidekiq.redis { |conn| conn.scard("processes") }
|
841
953
|
end
|
@@ -843,10 +955,12 @@ module Sidekiq
|
|
843
955
|
# Total number of threads available to execute jobs.
|
844
956
|
# For Sidekiq Enterprise customers this number (in production) must be
|
845
957
|
# less than or equal to your licensed concurrency.
|
958
|
+
# @return [Integer] the sum of process concurrency
|
846
959
|
def total_concurrency
|
847
960
|
sum { |x| x["concurrency"].to_i }
|
848
961
|
end
|
849
962
|
|
963
|
+
# @return [Integer] total amount of RSS memory consumed by Sidekiq processes
|
850
964
|
def total_rss_in_kb
|
851
965
|
sum { |x| x["rss"].to_i }
|
852
966
|
end
|
@@ -855,6 +969,8 @@ module Sidekiq
|
|
855
969
|
# Returns the identity of the current cluster leader or "" if no leader.
|
856
970
|
# This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
|
857
971
|
# or Sidekiq Pro.
|
972
|
+
# @return [String] Identity of cluster leader
|
973
|
+
# @return [String] empty string if no leader
|
858
974
|
def leader
|
859
975
|
@leader ||= begin
|
860
976
|
x = Sidekiq.redis { |c| c.get("dear-leader") }
|
@@ -881,6 +997,8 @@ module Sidekiq
|
|
881
997
|
# 'identity' => <unique string identifying the process>,
|
882
998
|
# }
|
883
999
|
class Process
|
1000
|
+
# :nodoc:
|
1001
|
+
# @api private
|
884
1002
|
def initialize(hash)
|
885
1003
|
@attribs = hash
|
886
1004
|
end
|
@@ -905,18 +1023,31 @@ module Sidekiq
|
|
905
1023
|
self["queues"]
|
906
1024
|
end
|
907
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.
|
908
1030
|
def quiet!
|
909
1031
|
signal("TSTP")
|
910
1032
|
end
|
911
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.
|
912
1038
|
def stop!
|
913
1039
|
signal("TERM")
|
914
1040
|
end
|
915
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.
|
916
1046
|
def dump_threads
|
917
1047
|
signal("TTIN")
|
918
1048
|
end
|
919
1049
|
|
1050
|
+
# @return [Boolean] true if this process is quiet or shutting down
|
920
1051
|
def stopping?
|
921
1052
|
self["quiet"] == "true"
|
922
1053
|
end
|
@@ -964,7 +1095,7 @@ module Sidekiq
|
|
964
1095
|
procs.sort.each do |key|
|
965
1096
|
valid, workers = conn.pipelined { |pipeline|
|
966
1097
|
pipeline.exists?(key)
|
967
|
-
pipeline.hgetall("#{key}:
|
1098
|
+
pipeline.hgetall("#{key}:work")
|
968
1099
|
}
|
969
1100
|
next unless valid
|
970
1101
|
workers.each_pair do |tid, json|
|