sidekiq 6.4.1 → 6.5.9
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 +101 -1
- data/README.md +1 -1
- data/bin/sidekiqload +18 -12
- data/lib/sidekiq/api.rb +222 -71
- data/lib/sidekiq/cli.rb +51 -37
- data/lib/sidekiq/client.rb +27 -28
- 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 +18 -12
- data/lib/sidekiq/middleware/i18n.rb +6 -4
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +2 -2
- data/lib/sidekiq/paginator.rb +11 -3
- 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 +53 -24
- 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 +21 -5
- data/lib/sidekiq/web/csrf_protection.rb +2 -2
- data/lib/sidekiq/web/helpers.rb +20 -7
- data/lib/sidekiq/web.rb +5 -1
- data/lib/sidekiq/worker.rb +24 -16
- 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/ja.yml +7 -0
- data/web/locales/pt-br.yml +27 -9
- data/web/locales/zh-cn.yml +36 -11
- data/web/locales/zh-tw.yml +32 -7
- data/web/views/_nav.erb +1 -1
- data/web/views/_summary.erb +1 -1
- data/web/views/busy.erb +9 -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 +34 -9
- data/lib/sidekiq/exception_handler.rb +0 -27
- data/lib/sidekiq/util.rb +0 -108
data/lib/sidekiq/api.rb
CHANGED
@@ -3,9 +3,31 @@
|
|
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
|
+
|
14
|
+
#
|
15
|
+
# Sidekiq's Data API provides a Ruby object model on top
|
16
|
+
# of Sidekiq's runtime data in Redis. This API should never
|
17
|
+
# be used within application code for business logic.
|
18
|
+
#
|
19
|
+
# The Sidekiq server process never uses this API: all data
|
20
|
+
# manipulation is done directly for performance reasons to
|
21
|
+
# ensure we are using Redis as efficiently as possible at
|
22
|
+
# every callsite.
|
23
|
+
#
|
24
|
+
|
8
25
|
module Sidekiq
|
26
|
+
# Retrieve runtime statistics from Redis regarding
|
27
|
+
# this Sidekiq cluster.
|
28
|
+
#
|
29
|
+
# stat = Sidekiq::Stats.new
|
30
|
+
# stat.processed
|
9
31
|
class Stats
|
10
32
|
def initialize
|
11
33
|
fetch_stats_fast!
|
@@ -52,6 +74,7 @@ module Sidekiq
|
|
52
74
|
end
|
53
75
|
|
54
76
|
# O(1) redis calls
|
77
|
+
# @api private
|
55
78
|
def fetch_stats_fast!
|
56
79
|
pipe1_res = Sidekiq.redis { |conn|
|
57
80
|
conn.pipelined do |pipeline|
|
@@ -91,6 +114,7 @@ module Sidekiq
|
|
91
114
|
end
|
92
115
|
|
93
116
|
# O(number of processes + number of queues) redis calls
|
117
|
+
# @api private
|
94
118
|
def fetch_stats_slow!
|
95
119
|
processes = Sidekiq.redis { |conn|
|
96
120
|
conn.sscan_each("processes").to_a
|
@@ -116,11 +140,13 @@ module Sidekiq
|
|
116
140
|
@stats
|
117
141
|
end
|
118
142
|
|
143
|
+
# @api private
|
119
144
|
def fetch_stats!
|
120
145
|
fetch_stats_fast!
|
121
146
|
fetch_stats_slow!
|
122
147
|
end
|
123
148
|
|
149
|
+
# @api private
|
124
150
|
def reset(*stats)
|
125
151
|
all = %w[failed processed]
|
126
152
|
stats = stats.empty? ? all : all & stats.flatten.compact.map(&:to_s)
|
@@ -191,7 +217,7 @@ module Sidekiq
|
|
191
217
|
stat_hash[dates[idx]] = value ? value.to_i : 0
|
192
218
|
end
|
193
219
|
end
|
194
|
-
rescue
|
220
|
+
rescue RedisConnection.adapter::CommandError
|
195
221
|
# mget will trigger a CROSSSLOT error when run against a Cluster
|
196
222
|
# TODO Someone want to add Cluster support?
|
197
223
|
end
|
@@ -202,9 +228,10 @@ module Sidekiq
|
|
202
228
|
end
|
203
229
|
|
204
230
|
##
|
205
|
-
#
|
231
|
+
# Represents a queue within Sidekiq.
|
206
232
|
# Allows enumeration of all jobs within the queue
|
207
|
-
# and deletion of jobs.
|
233
|
+
# and deletion of jobs. NB: this queue data is real-time
|
234
|
+
# and is changing within Redis moment by moment.
|
208
235
|
#
|
209
236
|
# queue = Sidekiq::Queue.new("mailer")
|
210
237
|
# queue.each do |job|
|
@@ -212,29 +239,34 @@ module Sidekiq
|
|
212
239
|
# job.args # => [1, 2, 3]
|
213
240
|
# job.delete if job.jid == 'abcdef1234567890'
|
214
241
|
# end
|
215
|
-
#
|
216
242
|
class Queue
|
217
243
|
include Enumerable
|
218
244
|
|
219
245
|
##
|
220
|
-
#
|
246
|
+
# Fetch all known queues within Redis.
|
221
247
|
#
|
248
|
+
# @return [Array<Sidekiq::Queue>]
|
222
249
|
def self.all
|
223
250
|
Sidekiq.redis { |c| c.sscan_each("queues").to_a }.sort.map { |q| Sidekiq::Queue.new(q) }
|
224
251
|
end
|
225
252
|
|
226
253
|
attr_reader :name
|
227
254
|
|
255
|
+
# @param name [String] the name of the queue
|
228
256
|
def initialize(name = "default")
|
229
257
|
@name = name.to_s
|
230
258
|
@rname = "queue:#{name}"
|
231
259
|
end
|
232
260
|
|
261
|
+
# The current size of the queue within Redis.
|
262
|
+
# This value is real-time and can change between calls.
|
263
|
+
#
|
264
|
+
# @return [Integer] the size
|
233
265
|
def size
|
234
266
|
Sidekiq.redis { |con| con.llen(@rname) }
|
235
267
|
end
|
236
268
|
|
237
|
-
#
|
269
|
+
# @return [Boolean] if the queue is currently paused
|
238
270
|
def paused?
|
239
271
|
false
|
240
272
|
end
|
@@ -243,7 +275,7 @@ module Sidekiq
|
|
243
275
|
# Calculates this queue's latency, the difference in seconds since the oldest
|
244
276
|
# job in the queue was enqueued.
|
245
277
|
#
|
246
|
-
# @return Float
|
278
|
+
# @return [Float] in seconds
|
247
279
|
def latency
|
248
280
|
entry = Sidekiq.redis { |conn|
|
249
281
|
conn.lrange(@rname, -1, -1)
|
@@ -279,34 +311,54 @@ module Sidekiq
|
|
279
311
|
##
|
280
312
|
# Find the job with the given JID within this queue.
|
281
313
|
#
|
282
|
-
# This is a slow, inefficient operation. Do not use under
|
314
|
+
# This is a *slow, inefficient* operation. Do not use under
|
283
315
|
# normal conditions.
|
316
|
+
#
|
317
|
+
# @param jid [String] the job_id to look for
|
318
|
+
# @return [Sidekiq::JobRecord]
|
319
|
+
# @return [nil] if not found
|
284
320
|
def find_job(jid)
|
285
321
|
detect { |j| j.jid == jid }
|
286
322
|
end
|
287
323
|
|
324
|
+
# delete all jobs within this queue
|
325
|
+
# @return [Boolean] true
|
288
326
|
def clear
|
289
327
|
Sidekiq.redis do |conn|
|
290
328
|
conn.multi do |transaction|
|
291
329
|
transaction.unlink(@rname)
|
292
|
-
transaction.srem("queues", name)
|
330
|
+
transaction.srem("queues", [name])
|
293
331
|
end
|
294
332
|
end
|
333
|
+
true
|
295
334
|
end
|
296
335
|
alias_method :💣, :clear
|
336
|
+
|
337
|
+
# :nodoc:
|
338
|
+
# @api private
|
339
|
+
def as_json(options = nil)
|
340
|
+
{name: name} # 5336
|
341
|
+
end
|
297
342
|
end
|
298
343
|
|
299
344
|
##
|
300
|
-
#
|
301
|
-
# sorted set.
|
345
|
+
# Represents a pending job within a Sidekiq queue.
|
302
346
|
#
|
303
347
|
# The job should be considered immutable but may be
|
304
348
|
# removed from the queue via JobRecord#delete.
|
305
|
-
#
|
306
349
|
class JobRecord
|
350
|
+
# the parsed Hash of job data
|
351
|
+
# @!attribute [r] Item
|
307
352
|
attr_reader :item
|
353
|
+
# the underlying String in Redis
|
354
|
+
# @!attribute [r] Value
|
308
355
|
attr_reader :value
|
356
|
+
# the queue associated with this job
|
357
|
+
# @!attribute [r] Queue
|
358
|
+
attr_reader :queue
|
309
359
|
|
360
|
+
# :nodoc:
|
361
|
+
# @api private
|
310
362
|
def initialize(item, queue_name = nil)
|
311
363
|
@args = nil
|
312
364
|
@value = item
|
@@ -314,6 +366,8 @@ module Sidekiq
|
|
314
366
|
@queue = queue_name || @item["queue"]
|
315
367
|
end
|
316
368
|
|
369
|
+
# :nodoc:
|
370
|
+
# @api private
|
317
371
|
def parse(item)
|
318
372
|
Sidekiq.load_json(item)
|
319
373
|
rescue JSON::ParserError
|
@@ -325,6 +379,8 @@ module Sidekiq
|
|
325
379
|
{}
|
326
380
|
end
|
327
381
|
|
382
|
+
# This is the job class which Sidekiq will execute. If using ActiveJob,
|
383
|
+
# this class will be the ActiveJob adapter class rather than a specific job.
|
328
384
|
def klass
|
329
385
|
self["class"]
|
330
386
|
end
|
@@ -354,31 +410,31 @@ module Sidekiq
|
|
354
410
|
def display_args
|
355
411
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
356
412
|
@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
|
-
|
413
|
+
when /\ASidekiq::Extensions::Delayed/
|
414
|
+
safe_load(args[0], args) do |_, _, arg, kwarg|
|
415
|
+
if !kwarg || kwarg.empty?
|
416
|
+
arg
|
417
|
+
else
|
418
|
+
[arg, kwarg]
|
419
|
+
end
|
420
|
+
end
|
421
|
+
when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
422
|
+
job_args = self["wrapped"] ? args[0]["arguments"] : []
|
423
|
+
if (self["wrapped"] || args[0]) == "ActionMailer::DeliveryJob"
|
424
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
425
|
+
job_args.drop(3)
|
426
|
+
elsif (self["wrapped"] || args[0]) == "ActionMailer::MailDeliveryJob"
|
427
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
428
|
+
job_args.drop(3).first["args"]
|
429
|
+
else
|
430
|
+
job_args
|
431
|
+
end
|
432
|
+
else
|
433
|
+
if self["encrypt"]
|
434
|
+
# no point in showing 150+ bytes of random garbage
|
435
|
+
args[-1] = "[encrypted data]"
|
436
|
+
end
|
437
|
+
args
|
382
438
|
end
|
383
439
|
end
|
384
440
|
|
@@ -412,15 +468,12 @@ module Sidekiq
|
|
412
468
|
end
|
413
469
|
end
|
414
470
|
|
415
|
-
attr_reader :queue
|
416
|
-
|
417
471
|
def latency
|
418
472
|
now = Time.now.to_f
|
419
473
|
now - (@item["enqueued_at"] || @item["created_at"] || now)
|
420
474
|
end
|
421
475
|
|
422
|
-
|
423
|
-
# Remove this job from the queue.
|
476
|
+
# Remove this job from the queue
|
424
477
|
def delete
|
425
478
|
count = Sidekiq.redis { |conn|
|
426
479
|
conn.lrem("queue:#{@queue}", 1, @value)
|
@@ -428,6 +481,7 @@ module Sidekiq
|
|
428
481
|
count != 0
|
429
482
|
end
|
430
483
|
|
484
|
+
# Access arbitrary attributes within the job hash
|
431
485
|
def [](name)
|
432
486
|
# nil will happen if the JSON fails to parse.
|
433
487
|
# We don't guarantee Sidekiq will work with bad job JSON but we should
|
@@ -442,6 +496,7 @@ module Sidekiq
|
|
442
496
|
rescue => ex
|
443
497
|
# #1761 in dev mode, it's possible to have jobs enqueued which haven't been loaded into
|
444
498
|
# memory yet so the YAML can't be loaded.
|
499
|
+
# TODO is this still necessary? Zeitwerk reloader should handle?
|
445
500
|
Sidekiq.logger.warn "Unable to load YAML: #{ex.message}" unless Sidekiq.options[:environment] == "development"
|
446
501
|
default
|
447
502
|
end
|
@@ -464,20 +519,28 @@ module Sidekiq
|
|
464
519
|
end
|
465
520
|
end
|
466
521
|
|
522
|
+
# Represents a job within a Redis sorted set where the score
|
523
|
+
# represents a timestamp associated with the job. This timestamp
|
524
|
+
# could be the scheduled time for it to run (e.g. scheduled set),
|
525
|
+
# or the expiration date after which the entry should be deleted (e.g. dead set).
|
467
526
|
class SortedEntry < JobRecord
|
468
527
|
attr_reader :score
|
469
528
|
attr_reader :parent
|
470
529
|
|
530
|
+
# :nodoc:
|
531
|
+
# @api private
|
471
532
|
def initialize(parent, score, item)
|
472
533
|
super(item)
|
473
|
-
@score = score
|
534
|
+
@score = Float(score)
|
474
535
|
@parent = parent
|
475
536
|
end
|
476
537
|
|
538
|
+
# The timestamp associated with this entry
|
477
539
|
def at
|
478
540
|
Time.at(score).utc
|
479
541
|
end
|
480
542
|
|
543
|
+
# remove this entry from the sorted set
|
481
544
|
def delete
|
482
545
|
if @value
|
483
546
|
@parent.delete_by_value(@parent.name, @value)
|
@@ -486,12 +549,17 @@ module Sidekiq
|
|
486
549
|
end
|
487
550
|
end
|
488
551
|
|
552
|
+
# Change the scheduled time for this job.
|
553
|
+
#
|
554
|
+
# @param at [Time] the new timestamp for this job
|
489
555
|
def reschedule(at)
|
490
556
|
Sidekiq.redis do |conn|
|
491
557
|
conn.zincrby(@parent.name, at.to_f - @score, Sidekiq.dump_json(@item))
|
492
558
|
end
|
493
559
|
end
|
494
560
|
|
561
|
+
# Enqueue this job from the scheduled or dead set so it will
|
562
|
+
# be executed at some point in the near future.
|
495
563
|
def add_to_queue
|
496
564
|
remove_job do |message|
|
497
565
|
msg = Sidekiq.load_json(message)
|
@@ -499,6 +567,8 @@ module Sidekiq
|
|
499
567
|
end
|
500
568
|
end
|
501
569
|
|
570
|
+
# enqueue this job from the retry set so it will be executed
|
571
|
+
# at some point in the near future.
|
502
572
|
def retry
|
503
573
|
remove_job do |message|
|
504
574
|
msg = Sidekiq.load_json(message)
|
@@ -507,8 +577,7 @@ module Sidekiq
|
|
507
577
|
end
|
508
578
|
end
|
509
579
|
|
510
|
-
|
511
|
-
# Place job in the dead set
|
580
|
+
# Move this job from its current set into the Dead set.
|
512
581
|
def kill
|
513
582
|
remove_job do |message|
|
514
583
|
DeadSet.new.kill(message)
|
@@ -556,20 +625,32 @@ module Sidekiq
|
|
556
625
|
end
|
557
626
|
end
|
558
627
|
|
628
|
+
# Base class for all sorted sets within Sidekiq.
|
559
629
|
class SortedSet
|
560
630
|
include Enumerable
|
561
631
|
|
632
|
+
# Redis key of the set
|
633
|
+
# @!attribute [r] Name
|
562
634
|
attr_reader :name
|
563
635
|
|
636
|
+
# :nodoc:
|
637
|
+
# @api private
|
564
638
|
def initialize(name)
|
565
639
|
@name = name
|
566
640
|
@_size = size
|
567
641
|
end
|
568
642
|
|
643
|
+
# real-time size of the set, will change
|
569
644
|
def size
|
570
645
|
Sidekiq.redis { |c| c.zcard(name) }
|
571
646
|
end
|
572
647
|
|
648
|
+
# Scan through each element of the sorted set, yielding each to the supplied block.
|
649
|
+
# Please see Redis's <a href="https://redis.io/commands/scan/">SCAN documentation</a> for implementation details.
|
650
|
+
#
|
651
|
+
# @param match [String] a snippet or regexp to filter matches.
|
652
|
+
# @param count [Integer] number of elements to retrieve at a time, default 100
|
653
|
+
# @yieldparam [Sidekiq::SortedEntry] each entry
|
573
654
|
def scan(match, count = 100)
|
574
655
|
return to_enum(:scan, match, count) unless block_given?
|
575
656
|
|
@@ -581,18 +662,32 @@ module Sidekiq
|
|
581
662
|
end
|
582
663
|
end
|
583
664
|
|
665
|
+
# @return [Boolean] always true
|
584
666
|
def clear
|
585
667
|
Sidekiq.redis do |conn|
|
586
668
|
conn.unlink(name)
|
587
669
|
end
|
670
|
+
true
|
588
671
|
end
|
589
672
|
alias_method :💣, :clear
|
673
|
+
|
674
|
+
# :nodoc:
|
675
|
+
# @api private
|
676
|
+
def as_json(options = nil)
|
677
|
+
{name: name} # 5336
|
678
|
+
end
|
590
679
|
end
|
591
680
|
|
681
|
+
# Base class for all sorted sets which contain jobs, e.g. scheduled, retry and dead.
|
682
|
+
# Sidekiq Pro and Enterprise add additional sorted sets which do not contain job data,
|
683
|
+
# e.g. Batches.
|
592
684
|
class JobSet < SortedSet
|
593
|
-
|
685
|
+
# Add a job with the associated timestamp to this set.
|
686
|
+
# @param timestamp [Time] the score for the job
|
687
|
+
# @param job [Hash] the job data
|
688
|
+
def schedule(timestamp, job)
|
594
689
|
Sidekiq.redis do |conn|
|
595
|
-
conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(
|
690
|
+
conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(job))
|
596
691
|
end
|
597
692
|
end
|
598
693
|
|
@@ -606,7 +701,7 @@ module Sidekiq
|
|
606
701
|
range_start = page * page_size + offset_size
|
607
702
|
range_end = range_start + page_size - 1
|
608
703
|
elements = Sidekiq.redis { |conn|
|
609
|
-
conn.zrange name, range_start, range_end,
|
704
|
+
conn.zrange name, range_start, range_end, withscores: true
|
610
705
|
}
|
611
706
|
break if elements.empty?
|
612
707
|
page -= 1
|
@@ -620,6 +715,10 @@ module Sidekiq
|
|
620
715
|
##
|
621
716
|
# Fetch jobs that match a given time or Range. Job ID is an
|
622
717
|
# optional second argument.
|
718
|
+
#
|
719
|
+
# @param score [Time,Range] a specific timestamp or range
|
720
|
+
# @param jid [String, optional] find a specific JID within the score
|
721
|
+
# @return [Array<SortedEntry>] any results found, can be empty
|
623
722
|
def fetch(score, jid = nil)
|
624
723
|
begin_score, end_score =
|
625
724
|
if score.is_a?(Range)
|
@@ -629,7 +728,7 @@ module Sidekiq
|
|
629
728
|
end
|
630
729
|
|
631
730
|
elements = Sidekiq.redis { |conn|
|
632
|
-
conn.zrangebyscore(name, begin_score, end_score,
|
731
|
+
conn.zrangebyscore(name, begin_score, end_score, withscores: true)
|
633
732
|
}
|
634
733
|
|
635
734
|
elements.each_with_object([]) do |element, result|
|
@@ -641,7 +740,10 @@ module Sidekiq
|
|
641
740
|
|
642
741
|
##
|
643
742
|
# Find the job with the given JID within this sorted set.
|
644
|
-
# This is a
|
743
|
+
# *This is a slow O(n) operation*. Do not use for app logic.
|
744
|
+
#
|
745
|
+
# @param jid [String] the job identifier
|
746
|
+
# @return [SortedEntry] the record or nil
|
645
747
|
def find_job(jid)
|
646
748
|
Sidekiq.redis do |conn|
|
647
749
|
conn.zscan_each(name, match: "*#{jid}*", count: 100) do |entry, score|
|
@@ -653,6 +755,8 @@ module Sidekiq
|
|
653
755
|
nil
|
654
756
|
end
|
655
757
|
|
758
|
+
# :nodoc:
|
759
|
+
# @api private
|
656
760
|
def delete_by_value(name, value)
|
657
761
|
Sidekiq.redis do |conn|
|
658
762
|
ret = conn.zrem(name, value)
|
@@ -661,6 +765,8 @@ module Sidekiq
|
|
661
765
|
end
|
662
766
|
end
|
663
767
|
|
768
|
+
# :nodoc:
|
769
|
+
# @api private
|
664
770
|
def delete_by_jid(score, jid)
|
665
771
|
Sidekiq.redis do |conn|
|
666
772
|
elements = conn.zrangebyscore(name, score, score)
|
@@ -681,10 +787,10 @@ module Sidekiq
|
|
681
787
|
end
|
682
788
|
|
683
789
|
##
|
684
|
-
#
|
790
|
+
# The set of scheduled jobs within Sidekiq.
|
685
791
|
# Based on this, you can search/filter for jobs. Here's an
|
686
|
-
# example where I'm selecting
|
687
|
-
# and deleting them from the
|
792
|
+
# example where I'm selecting jobs based on some complex logic
|
793
|
+
# and deleting them from the scheduled set.
|
688
794
|
#
|
689
795
|
# r = Sidekiq::ScheduledSet.new
|
690
796
|
# r.select do |scheduled|
|
@@ -699,7 +805,7 @@ module Sidekiq
|
|
699
805
|
end
|
700
806
|
|
701
807
|
##
|
702
|
-
#
|
808
|
+
# The set of retries within Sidekiq.
|
703
809
|
# Based on this, you can search/filter for jobs. Here's an
|
704
810
|
# example where I'm selecting all jobs of a certain type
|
705
811
|
# and deleting them from the retry queue.
|
@@ -715,23 +821,29 @@ module Sidekiq
|
|
715
821
|
super "retry"
|
716
822
|
end
|
717
823
|
|
824
|
+
# Enqueues all jobs pending within the retry set.
|
718
825
|
def retry_all
|
719
826
|
each(&:retry) while size > 0
|
720
827
|
end
|
721
828
|
|
829
|
+
# Kills all jobs pending within the retry set.
|
722
830
|
def kill_all
|
723
831
|
each(&:kill) while size > 0
|
724
832
|
end
|
725
833
|
end
|
726
834
|
|
727
835
|
##
|
728
|
-
#
|
836
|
+
# The set of dead jobs within Sidekiq. Dead jobs have failed all of
|
837
|
+
# their retries and are helding in this set pending some sort of manual
|
838
|
+
# fix. They will be removed after 6 months (dead_timeout) if not.
|
729
839
|
#
|
730
840
|
class DeadSet < JobSet
|
731
841
|
def initialize
|
732
842
|
super "dead"
|
733
843
|
end
|
734
844
|
|
845
|
+
# Add the given job to the Dead set.
|
846
|
+
# @param message [String] the job data as JSON
|
735
847
|
def kill(message, opts = {})
|
736
848
|
now = Time.now.to_f
|
737
849
|
Sidekiq.redis do |conn|
|
@@ -753,16 +865,21 @@ module Sidekiq
|
|
753
865
|
true
|
754
866
|
end
|
755
867
|
|
868
|
+
# Enqueue all dead jobs
|
756
869
|
def retry_all
|
757
870
|
each(&:retry) while size > 0
|
758
871
|
end
|
759
872
|
|
873
|
+
# The maximum size of the Dead set. Older entries will be trimmed
|
874
|
+
# to stay within this limit. Default value is 10,000.
|
760
875
|
def self.max_jobs
|
761
|
-
Sidekiq
|
876
|
+
Sidekiq[:dead_max_jobs]
|
762
877
|
end
|
763
878
|
|
879
|
+
# The time limit for entries within the Dead set. Older entries will be thrown away.
|
880
|
+
# Default value is six months.
|
764
881
|
def self.timeout
|
765
|
-
Sidekiq
|
882
|
+
Sidekiq[:dead_timeout_in_seconds]
|
766
883
|
end
|
767
884
|
end
|
768
885
|
|
@@ -771,21 +888,28 @@ module Sidekiq
|
|
771
888
|
# right now. Each process sends a heartbeat to Redis every 5 seconds
|
772
889
|
# so this set should be relatively accurate, barring network partitions.
|
773
890
|
#
|
774
|
-
#
|
891
|
+
# @yieldparam [Sidekiq::Process]
|
775
892
|
#
|
776
893
|
class ProcessSet
|
777
894
|
include Enumerable
|
778
895
|
|
896
|
+
# :nodoc:
|
897
|
+
# @api private
|
779
898
|
def initialize(clean_plz = true)
|
780
899
|
cleanup if clean_plz
|
781
900
|
end
|
782
901
|
|
783
902
|
# Cleans up dead processes recorded in Redis.
|
784
903
|
# Returns the number of processes cleaned.
|
904
|
+
# :nodoc:
|
905
|
+
# @api private
|
785
906
|
def cleanup
|
907
|
+
# dont run cleanup more than once per minute
|
908
|
+
return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1", nx: true, ex: 60) }
|
909
|
+
|
786
910
|
count = 0
|
787
911
|
Sidekiq.redis do |conn|
|
788
|
-
procs = conn.sscan_each("processes").to_a
|
912
|
+
procs = conn.sscan_each("processes").to_a
|
789
913
|
heartbeats = conn.pipelined { |pipeline|
|
790
914
|
procs.each do |key|
|
791
915
|
pipeline.hget(key, "info")
|
@@ -836,6 +960,7 @@ module Sidekiq
|
|
836
960
|
# based on current heartbeat. #each does that and ensures the set only
|
837
961
|
# contains Sidekiq processes which have sent a heartbeat within the last
|
838
962
|
# 60 seconds.
|
963
|
+
# @return [Integer] current number of registered Sidekiq processes
|
839
964
|
def size
|
840
965
|
Sidekiq.redis { |conn| conn.scard("processes") }
|
841
966
|
end
|
@@ -843,10 +968,12 @@ module Sidekiq
|
|
843
968
|
# Total number of threads available to execute jobs.
|
844
969
|
# For Sidekiq Enterprise customers this number (in production) must be
|
845
970
|
# less than or equal to your licensed concurrency.
|
971
|
+
# @return [Integer] the sum of process concurrency
|
846
972
|
def total_concurrency
|
847
973
|
sum { |x| x["concurrency"].to_i }
|
848
974
|
end
|
849
975
|
|
976
|
+
# @return [Integer] total amount of RSS memory consumed by Sidekiq processes
|
850
977
|
def total_rss_in_kb
|
851
978
|
sum { |x| x["rss"].to_i }
|
852
979
|
end
|
@@ -855,6 +982,8 @@ module Sidekiq
|
|
855
982
|
# Returns the identity of the current cluster leader or "" if no leader.
|
856
983
|
# This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
|
857
984
|
# or Sidekiq Pro.
|
985
|
+
# @return [String] Identity of cluster leader
|
986
|
+
# @return [String] empty string if no leader
|
858
987
|
def leader
|
859
988
|
@leader ||= begin
|
860
989
|
x = Sidekiq.redis { |c| c.get("dear-leader") }
|
@@ -881,6 +1010,8 @@ module Sidekiq
|
|
881
1010
|
# 'identity' => <unique string identifying the process>,
|
882
1011
|
# }
|
883
1012
|
class Process
|
1013
|
+
# :nodoc:
|
1014
|
+
# @api private
|
884
1015
|
def initialize(hash)
|
885
1016
|
@attribs = hash
|
886
1017
|
end
|
@@ -905,18 +1036,31 @@ module Sidekiq
|
|
905
1036
|
self["queues"]
|
906
1037
|
end
|
907
1038
|
|
1039
|
+
# Signal this process to stop processing new jobs.
|
1040
|
+
# It will continue to execute jobs it has already fetched.
|
1041
|
+
# This method is *asynchronous* and it can take 5-10
|
1042
|
+
# seconds for the process to quiet.
|
908
1043
|
def quiet!
|
909
1044
|
signal("TSTP")
|
910
1045
|
end
|
911
1046
|
|
1047
|
+
# Signal this process to shutdown.
|
1048
|
+
# It will shutdown within its configured :timeout value, default 25 seconds.
|
1049
|
+
# This method is *asynchronous* and it can take 5-10
|
1050
|
+
# seconds for the process to start shutting down.
|
912
1051
|
def stop!
|
913
1052
|
signal("TERM")
|
914
1053
|
end
|
915
1054
|
|
1055
|
+
# Signal this process to log backtraces for all threads.
|
1056
|
+
# Useful if you have a frozen or deadlocked process which is
|
1057
|
+
# still sending a heartbeat.
|
1058
|
+
# This method is *asynchronous* and it can take 5-10 seconds.
|
916
1059
|
def dump_threads
|
917
1060
|
signal("TTIN")
|
918
1061
|
end
|
919
1062
|
|
1063
|
+
# @return [Boolean] true if this process is quiet or shutting down
|
920
1064
|
def stopping?
|
921
1065
|
self["quiet"] == "true"
|
922
1066
|
end
|
@@ -959,24 +1103,31 @@ module Sidekiq
|
|
959
1103
|
|
960
1104
|
def each(&block)
|
961
1105
|
results = []
|
1106
|
+
procs = nil
|
1107
|
+
all_works = nil
|
1108
|
+
|
962
1109
|
Sidekiq.redis do |conn|
|
963
|
-
procs = conn.sscan_each("processes").to_a
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
pipeline.hgetall("#{key}:
|
968
|
-
}
|
969
|
-
next unless valid
|
970
|
-
workers.each_pair do |tid, json|
|
971
|
-
hsh = Sidekiq.load_json(json)
|
972
|
-
p = hsh["payload"]
|
973
|
-
# avoid breaking API, this is a side effect of the JSON optimization in #4316
|
974
|
-
hsh["payload"] = Sidekiq.load_json(p) if p.is_a?(String)
|
975
|
-
results << [key, tid, hsh]
|
1110
|
+
procs = conn.sscan_each("processes").to_a.sort
|
1111
|
+
|
1112
|
+
all_works = conn.pipelined do |pipeline|
|
1113
|
+
procs.each do |key|
|
1114
|
+
pipeline.hgetall("#{key}:work")
|
976
1115
|
end
|
977
1116
|
end
|
978
1117
|
end
|
979
1118
|
|
1119
|
+
procs.zip(all_works).each do |key, workers|
|
1120
|
+
workers.each_pair do |tid, json|
|
1121
|
+
next if json.empty?
|
1122
|
+
|
1123
|
+
hsh = Sidekiq.load_json(json)
|
1124
|
+
p = hsh["payload"]
|
1125
|
+
# avoid breaking API, this is a side effect of the JSON optimization in #4316
|
1126
|
+
hsh["payload"] = Sidekiq.load_json(p) if p.is_a?(String)
|
1127
|
+
results << [key, tid, hsh]
|
1128
|
+
end
|
1129
|
+
end
|
1130
|
+
|
980
1131
|
results.sort_by { |(_, _, hsh)| hsh["run_at"] }.each(&block)
|
981
1132
|
end
|
982
1133
|
|