sidekiq 7.1.4 → 8.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changes.md +333 -0
- data/README.md +16 -13
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiqload +31 -22
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +121 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/generators/sidekiq/templates/job.rb.erb +1 -1
- data/lib/sidekiq/api.rb +260 -67
- data/lib/sidekiq/capsule.rb +17 -8
- data/lib/sidekiq/cli.rb +19 -20
- data/lib/sidekiq/client.rb +48 -15
- data/lib/sidekiq/component.rb +64 -3
- data/lib/sidekiq/config.rb +60 -18
- data/lib/sidekiq/deploy.rb +4 -2
- data/lib/sidekiq/embedded.rb +4 -1
- data/lib/sidekiq/fetch.rb +2 -1
- data/lib/sidekiq/iterable_job.rb +56 -0
- data/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +322 -0
- data/lib/sidekiq/job.rb +16 -5
- data/lib/sidekiq/job_logger.rb +15 -12
- data/lib/sidekiq/job_retry.rb +41 -13
- data/lib/sidekiq/job_util.rb +7 -1
- data/lib/sidekiq/launcher.rb +23 -11
- data/lib/sidekiq/loader.rb +57 -0
- data/lib/sidekiq/logger.rb +25 -69
- data/lib/sidekiq/manager.rb +0 -1
- data/lib/sidekiq/metrics/query.rb +76 -45
- data/lib/sidekiq/metrics/shared.rb +23 -9
- data/lib/sidekiq/metrics/tracking.rb +32 -15
- data/lib/sidekiq/middleware/current_attributes.rb +39 -14
- data/lib/sidekiq/middleware/i18n.rb +2 -0
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +6 -9
- data/lib/sidekiq/paginator.rb +16 -3
- data/lib/sidekiq/processor.rb +37 -20
- data/lib/sidekiq/profiler.rb +73 -0
- data/lib/sidekiq/rails.rb +47 -57
- data/lib/sidekiq/redis_client_adapter.rb +25 -8
- data/lib/sidekiq/redis_connection.rb +49 -9
- data/lib/sidekiq/ring_buffer.rb +3 -0
- data/lib/sidekiq/scheduled.rb +2 -2
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/testing.rb +34 -15
- data/lib/sidekiq/transaction_aware_client.rb +20 -5
- data/lib/sidekiq/version.rb +6 -2
- data/lib/sidekiq/web/action.rb +149 -64
- data/lib/sidekiq/web/application.rb +367 -297
- data/lib/sidekiq/web/config.rb +120 -0
- data/lib/sidekiq/web/csrf_protection.rb +8 -5
- data/lib/sidekiq/web/helpers.rb +146 -64
- data/lib/sidekiq/web/router.rb +61 -74
- data/lib/sidekiq/web.rb +53 -106
- data/lib/sidekiq.rb +11 -4
- data/sidekiq.gemspec +6 -5
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status.png +0 -0
- data/web/assets/javascripts/application.js +66 -24
- data/web/assets/javascripts/base-charts.js +30 -16
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/dashboard-charts.js +37 -11
- data/web/assets/javascripts/dashboard.js +15 -11
- data/web/assets/javascripts/metrics.js +50 -34
- data/web/assets/stylesheets/style.css +776 -0
- data/web/locales/ar.yml +2 -0
- data/web/locales/cs.yml +2 -0
- data/web/locales/da.yml +2 -0
- data/web/locales/de.yml +2 -0
- data/web/locales/el.yml +2 -0
- data/web/locales/en.yml +12 -1
- data/web/locales/es.yml +25 -2
- data/web/locales/fa.yml +2 -0
- data/web/locales/fr.yml +2 -1
- data/web/locales/gd.yml +2 -1
- data/web/locales/he.yml +2 -0
- data/web/locales/hi.yml +2 -0
- data/web/locales/it.yml +41 -1
- data/web/locales/ja.yml +2 -1
- data/web/locales/ko.yml +2 -0
- data/web/locales/lt.yml +2 -0
- data/web/locales/nb.yml +2 -0
- data/web/locales/nl.yml +2 -0
- data/web/locales/pl.yml +2 -0
- data/web/locales/{pt-br.yml → pt-BR.yml} +4 -3
- data/web/locales/pt.yml +2 -0
- data/web/locales/ru.yml +2 -0
- data/web/locales/sv.yml +2 -0
- data/web/locales/ta.yml +2 -0
- data/web/locales/tr.yml +102 -0
- data/web/locales/uk.yml +29 -4
- data/web/locales/ur.yml +2 -0
- data/web/locales/vi.yml +2 -0
- data/web/locales/{zh-cn.yml → zh-CN.yml} +86 -74
- data/web/locales/{zh-tw.yml → zh-TW.yml} +3 -2
- data/web/views/_footer.erb +31 -22
- data/web/views/_job_info.erb +91 -89
- data/web/views/_metrics_period_select.erb +13 -10
- data/web/views/_nav.erb +14 -21
- data/web/views/_paging.erb +22 -21
- data/web/views/_poll_link.erb +2 -2
- data/web/views/_summary.erb +23 -23
- data/web/views/busy.erb +123 -125
- data/web/views/dashboard.erb +71 -82
- data/web/views/dead.erb +31 -27
- data/web/views/filtering.erb +6 -0
- data/web/views/layout.erb +13 -29
- data/web/views/metrics.erb +70 -68
- data/web/views/metrics_for_job.erb +30 -40
- data/web/views/morgue.erb +65 -70
- data/web/views/profiles.erb +43 -0
- data/web/views/queue.erb +54 -52
- data/web/views/queues.erb +43 -37
- data/web/views/retries.erb +70 -75
- data/web/views/retry.erb +32 -27
- data/web/views/scheduled.erb +63 -55
- data/web/views/scheduled_job_info.erb +3 -3
- metadata +49 -27
- data/web/assets/stylesheets/application-dark.css +0 -147
- data/web/assets/stylesheets/application-rtl.css +0 -153
- data/web/assets/stylesheets/application.css +0 -724
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/views/_status.erb +0 -4
data/lib/sidekiq/api.rb
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "sidekiq"
|
|
4
|
-
|
|
5
3
|
require "zlib"
|
|
6
|
-
require "set"
|
|
7
|
-
require "base64"
|
|
8
4
|
|
|
5
|
+
require "sidekiq"
|
|
9
6
|
require "sidekiq/metrics/query"
|
|
10
7
|
|
|
11
8
|
#
|
|
@@ -102,11 +99,22 @@ module Sidekiq
|
|
|
102
99
|
rescue
|
|
103
100
|
{}
|
|
104
101
|
end
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
|
|
103
|
+
enqueued_at = job["enqueued_at"]
|
|
104
|
+
if enqueued_at
|
|
105
|
+
if enqueued_at.is_a?(Float)
|
|
106
|
+
# old format
|
|
107
|
+
now = Time.now.to_f
|
|
108
|
+
now - enqueued_at
|
|
109
|
+
else
|
|
110
|
+
now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
111
|
+
(now - enqueued_at) / 1000.0
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
0.0
|
|
115
|
+
end
|
|
108
116
|
else
|
|
109
|
-
0
|
|
117
|
+
0.0
|
|
110
118
|
end
|
|
111
119
|
|
|
112
120
|
@stats = {
|
|
@@ -266,11 +274,22 @@ module Sidekiq
|
|
|
266
274
|
entry = Sidekiq.redis { |conn|
|
|
267
275
|
conn.lindex(@rname, -1)
|
|
268
276
|
}
|
|
269
|
-
return 0 unless entry
|
|
277
|
+
return 0.0 unless entry
|
|
278
|
+
|
|
270
279
|
job = Sidekiq.load_json(entry)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
280
|
+
enqueued_at = job["enqueued_at"]
|
|
281
|
+
if enqueued_at
|
|
282
|
+
if enqueued_at.is_a?(Float)
|
|
283
|
+
# old format
|
|
284
|
+
now = Time.now.to_f
|
|
285
|
+
now - enqueued_at
|
|
286
|
+
else
|
|
287
|
+
now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
288
|
+
(now - enqueued_at) / 1000.0
|
|
289
|
+
end
|
|
290
|
+
else
|
|
291
|
+
0.0
|
|
292
|
+
end
|
|
274
293
|
end
|
|
275
294
|
|
|
276
295
|
def each
|
|
@@ -374,7 +393,7 @@ module Sidekiq
|
|
|
374
393
|
def display_class
|
|
375
394
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
|
376
395
|
@klass ||= self["display_class"] || begin
|
|
377
|
-
if klass == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
|
396
|
+
if klass == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper" || klass == "Sidekiq::ActiveJob::Wrapper"
|
|
378
397
|
job_class = @item["wrapped"] || args[0]
|
|
379
398
|
if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob"
|
|
380
399
|
# MailerClass#mailer_method
|
|
@@ -390,7 +409,7 @@ module Sidekiq
|
|
|
390
409
|
|
|
391
410
|
def display_args
|
|
392
411
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
|
393
|
-
@display_args ||= if klass == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
|
412
|
+
@display_args ||= if klass == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper" || klass == "Sidekiq::ActiveJob::Wrapper"
|
|
394
413
|
job_args = self["wrapped"] ? deserialize_argument(args[0]["arguments"]) : []
|
|
395
414
|
if (self["wrapped"] || args[0]) == "ActionMailer::DeliveryJob"
|
|
396
415
|
# remove MailerClass, mailer_method and 'deliver_now'
|
|
@@ -422,12 +441,26 @@ module Sidekiq
|
|
|
422
441
|
self["bid"]
|
|
423
442
|
end
|
|
424
443
|
|
|
444
|
+
def failed_at
|
|
445
|
+
if self["failed_at"]
|
|
446
|
+
time_from_timestamp(self["failed_at"])
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def retried_at
|
|
451
|
+
if self["retried_at"]
|
|
452
|
+
time_from_timestamp(self["retried_at"])
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
425
456
|
def enqueued_at
|
|
426
|
-
|
|
457
|
+
if self["enqueued_at"]
|
|
458
|
+
time_from_timestamp(self["enqueued_at"])
|
|
459
|
+
end
|
|
427
460
|
end
|
|
428
461
|
|
|
429
462
|
def created_at
|
|
430
|
-
|
|
463
|
+
time_from_timestamp(self["created_at"] || self["enqueued_at"] || 0)
|
|
431
464
|
end
|
|
432
465
|
|
|
433
466
|
def tags
|
|
@@ -445,8 +478,17 @@ module Sidekiq
|
|
|
445
478
|
end
|
|
446
479
|
|
|
447
480
|
def latency
|
|
448
|
-
|
|
449
|
-
|
|
481
|
+
timestamp = @item["enqueued_at"] || @item["created_at"]
|
|
482
|
+
if timestamp
|
|
483
|
+
if timestamp.is_a?(Float)
|
|
484
|
+
# old format
|
|
485
|
+
Time.now.to_f - timestamp
|
|
486
|
+
else
|
|
487
|
+
(::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) - timestamp) / 1000.0
|
|
488
|
+
end
|
|
489
|
+
else
|
|
490
|
+
0.0
|
|
491
|
+
end
|
|
450
492
|
end
|
|
451
493
|
|
|
452
494
|
# Remove this job from the queue
|
|
@@ -491,10 +533,19 @@ module Sidekiq
|
|
|
491
533
|
end
|
|
492
534
|
|
|
493
535
|
def uncompress_backtrace(backtrace)
|
|
494
|
-
|
|
495
|
-
uncompressed = Zlib::Inflate.inflate(
|
|
536
|
+
strict_base64_decoded = backtrace.unpack1("m")
|
|
537
|
+
uncompressed = Zlib::Inflate.inflate(strict_base64_decoded)
|
|
496
538
|
Sidekiq.load_json(uncompressed)
|
|
497
539
|
end
|
|
540
|
+
|
|
541
|
+
def time_from_timestamp(timestamp)
|
|
542
|
+
if timestamp.is_a?(Float)
|
|
543
|
+
# old format, timestamps were stored as fractional seconds since the epoch
|
|
544
|
+
Time.at(timestamp).utc
|
|
545
|
+
else
|
|
546
|
+
Time.at(timestamp / 1000, timestamp % 1000, :millisecond)
|
|
547
|
+
end
|
|
548
|
+
end
|
|
498
549
|
end
|
|
499
550
|
|
|
500
551
|
# Represents a job within a Redis sorted set where the score
|
|
@@ -669,6 +720,41 @@ module Sidekiq
|
|
|
669
720
|
end
|
|
670
721
|
end
|
|
671
722
|
|
|
723
|
+
def pop_each
|
|
724
|
+
Sidekiq.redis do |c|
|
|
725
|
+
size.times do
|
|
726
|
+
data, score = c.zpopmin(name, 1)&.first
|
|
727
|
+
break unless data
|
|
728
|
+
yield data, score
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
def retry_all
|
|
734
|
+
c = Sidekiq::Client.new
|
|
735
|
+
pop_each do |msg, _|
|
|
736
|
+
job = Sidekiq.load_json(msg)
|
|
737
|
+
# Manual retries should not count against the retry limit.
|
|
738
|
+
job["retry_count"] -= 1 if job["retry_count"]
|
|
739
|
+
c.push(job)
|
|
740
|
+
end
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
# Move all jobs from this Set to the Dead Set.
|
|
744
|
+
# See DeadSet#kill
|
|
745
|
+
def kill_all(notify_failure: false, ex: nil)
|
|
746
|
+
ds = DeadSet.new
|
|
747
|
+
opts = {notify_failure: notify_failure, ex: ex, trim: false}
|
|
748
|
+
|
|
749
|
+
begin
|
|
750
|
+
pop_each do |msg, _|
|
|
751
|
+
ds.kill(msg, opts)
|
|
752
|
+
end
|
|
753
|
+
ensure
|
|
754
|
+
ds.trim
|
|
755
|
+
end
|
|
756
|
+
end
|
|
757
|
+
|
|
672
758
|
def each
|
|
673
759
|
initial_size = @_size
|
|
674
760
|
offset_size = 0
|
|
@@ -679,7 +765,7 @@ module Sidekiq
|
|
|
679
765
|
range_start = page * page_size + offset_size
|
|
680
766
|
range_end = range_start + page_size - 1
|
|
681
767
|
elements = Sidekiq.redis { |conn|
|
|
682
|
-
conn.zrange name, range_start, range_end, withscores
|
|
768
|
+
conn.zrange name, range_start, range_end, "withscores"
|
|
683
769
|
}
|
|
684
770
|
break if elements.empty?
|
|
685
771
|
page -= 1
|
|
@@ -706,7 +792,7 @@ module Sidekiq
|
|
|
706
792
|
end
|
|
707
793
|
|
|
708
794
|
elements = Sidekiq.redis { |conn|
|
|
709
|
-
conn.zrange(name, begin_score, end_score, "BYSCORE", withscores
|
|
795
|
+
conn.zrange(name, begin_score, end_score, "BYSCORE", "withscores")
|
|
710
796
|
}
|
|
711
797
|
|
|
712
798
|
elements.each_with_object([]) do |element, result|
|
|
@@ -766,39 +852,21 @@ module Sidekiq
|
|
|
766
852
|
|
|
767
853
|
##
|
|
768
854
|
# The set of scheduled jobs within Sidekiq.
|
|
769
|
-
# Based on this, you can search/filter for jobs. Here's an
|
|
770
|
-
# example where I'm selecting jobs based on some complex logic
|
|
771
|
-
# and deleting them from the scheduled set.
|
|
772
|
-
#
|
|
773
855
|
# See the API wiki page for usage notes and examples.
|
|
774
856
|
#
|
|
775
857
|
class ScheduledSet < JobSet
|
|
776
858
|
def initialize
|
|
777
|
-
super
|
|
859
|
+
super("schedule")
|
|
778
860
|
end
|
|
779
861
|
end
|
|
780
862
|
|
|
781
863
|
##
|
|
782
864
|
# The set of retries within Sidekiq.
|
|
783
|
-
# Based on this, you can search/filter for jobs. Here's an
|
|
784
|
-
# example where I'm selecting all jobs of a certain type
|
|
785
|
-
# and deleting them from the retry queue.
|
|
786
|
-
#
|
|
787
865
|
# See the API wiki page for usage notes and examples.
|
|
788
866
|
#
|
|
789
867
|
class RetrySet < JobSet
|
|
790
868
|
def initialize
|
|
791
|
-
super
|
|
792
|
-
end
|
|
793
|
-
|
|
794
|
-
# Enqueues all jobs pending within the retry set.
|
|
795
|
-
def retry_all
|
|
796
|
-
each(&:retry) while size > 0
|
|
797
|
-
end
|
|
798
|
-
|
|
799
|
-
# Kills all jobs pending within the retry set.
|
|
800
|
-
def kill_all
|
|
801
|
-
each(&:kill) while size > 0
|
|
869
|
+
super("retry")
|
|
802
870
|
end
|
|
803
871
|
end
|
|
804
872
|
|
|
@@ -809,36 +877,48 @@ module Sidekiq
|
|
|
809
877
|
#
|
|
810
878
|
class DeadSet < JobSet
|
|
811
879
|
def initialize
|
|
812
|
-
super
|
|
880
|
+
super("dead")
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
# Trim dead jobs which are over our storage limits
|
|
884
|
+
def trim
|
|
885
|
+
hash = Sidekiq.default_configuration
|
|
886
|
+
now = Time.now.to_f
|
|
887
|
+
Sidekiq.redis do |conn|
|
|
888
|
+
conn.multi do |transaction|
|
|
889
|
+
transaction.zremrangebyscore(name, "-inf", now - hash[:dead_timeout_in_seconds])
|
|
890
|
+
transaction.zremrangebyrank(name, 0, - hash[:dead_max_jobs])
|
|
891
|
+
end
|
|
892
|
+
end
|
|
813
893
|
end
|
|
814
894
|
|
|
815
895
|
# Add the given job to the Dead set.
|
|
816
896
|
# @param message [String] the job data as JSON
|
|
897
|
+
# @option opts [Boolean] :notify_failure (true) Whether death handlers should be called
|
|
898
|
+
# @option opts [Boolean] :trim (true) Whether Sidekiq should trim the structure to keep it within configuration
|
|
899
|
+
# @option opts [Exception] :ex (RuntimeError) An exception to pass to the death handlers
|
|
817
900
|
def kill(message, opts = {})
|
|
818
901
|
now = Time.now.to_f
|
|
819
902
|
Sidekiq.redis do |conn|
|
|
820
|
-
conn.
|
|
821
|
-
transaction.zadd(name, now.to_s, message)
|
|
822
|
-
transaction.zremrangebyscore(name, "-inf", now - Sidekiq::Config::DEFAULTS[:dead_timeout_in_seconds])
|
|
823
|
-
transaction.zremrangebyrank(name, 0, - Sidekiq::Config::DEFAULTS[:dead_max_jobs])
|
|
824
|
-
end
|
|
903
|
+
conn.zadd(name, now.to_s, message)
|
|
825
904
|
end
|
|
826
905
|
|
|
906
|
+
trim if opts[:trim] != false
|
|
907
|
+
|
|
827
908
|
if opts[:notify_failure] != false
|
|
828
909
|
job = Sidekiq.load_json(message)
|
|
829
|
-
|
|
830
|
-
|
|
910
|
+
if opts[:ex]
|
|
911
|
+
ex = opts[:ex]
|
|
912
|
+
else
|
|
913
|
+
ex = RuntimeError.new("Job killed by API")
|
|
914
|
+
ex.set_backtrace(caller)
|
|
915
|
+
end
|
|
831
916
|
Sidekiq.default_configuration.death_handlers.each do |handle|
|
|
832
|
-
handle.call(job,
|
|
917
|
+
handle.call(job, ex)
|
|
833
918
|
end
|
|
834
919
|
end
|
|
835
920
|
true
|
|
836
921
|
end
|
|
837
|
-
|
|
838
|
-
# Enqueue all dead jobs
|
|
839
|
-
def retry_all
|
|
840
|
-
each(&:retry) while size > 0
|
|
841
|
-
end
|
|
842
922
|
end
|
|
843
923
|
|
|
844
924
|
##
|
|
@@ -881,7 +961,7 @@ module Sidekiq
|
|
|
881
961
|
# @api private
|
|
882
962
|
def cleanup
|
|
883
963
|
# dont run cleanup more than once per minute
|
|
884
|
-
return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1",
|
|
964
|
+
return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1", "NX", "EX", "60") }
|
|
885
965
|
|
|
886
966
|
count = 0
|
|
887
967
|
Sidekiq.redis do |conn|
|
|
@@ -979,9 +1059,9 @@ module Sidekiq
|
|
|
979
1059
|
# 'started_at' => <process start time>,
|
|
980
1060
|
# 'pid' => 12345,
|
|
981
1061
|
# 'tag' => 'myapp'
|
|
982
|
-
# 'concurrency' =>
|
|
983
|
-
# '
|
|
984
|
-
# 'busy' =>
|
|
1062
|
+
# 'concurrency' => 5,
|
|
1063
|
+
# 'capsules' => {"default" => {"mode" => "weighted", "concurrency" => 5, "weights" => {"default" => 2, "low" => 1}}},
|
|
1064
|
+
# 'busy' => 3,
|
|
985
1065
|
# 'beat' => <last heartbeat>,
|
|
986
1066
|
# 'identity' => <unique string identifying the process>,
|
|
987
1067
|
# 'embedded' => true,
|
|
@@ -1009,12 +1089,25 @@ module Sidekiq
|
|
|
1009
1089
|
self["identity"]
|
|
1010
1090
|
end
|
|
1011
1091
|
|
|
1092
|
+
# deprecated, use capsules below
|
|
1012
1093
|
def queues
|
|
1013
|
-
|
|
1094
|
+
capsules.values.flat_map { |x| x["weights"].keys }.uniq
|
|
1014
1095
|
end
|
|
1015
1096
|
|
|
1097
|
+
# deprecated, use capsules below
|
|
1016
1098
|
def weights
|
|
1017
|
-
|
|
1099
|
+
hash = {}
|
|
1100
|
+
capsules.values.each do |cap|
|
|
1101
|
+
# Note: will lose data if two capsules are processing the same named queue
|
|
1102
|
+
cap["weights"].each_pair do |queue, weight|
|
|
1103
|
+
hash[queue] = weight
|
|
1104
|
+
end
|
|
1105
|
+
end
|
|
1106
|
+
hash
|
|
1107
|
+
end
|
|
1108
|
+
|
|
1109
|
+
def capsules
|
|
1110
|
+
self["capsules"]
|
|
1018
1111
|
end
|
|
1019
1112
|
|
|
1020
1113
|
def version
|
|
@@ -1086,9 +1179,8 @@ module Sidekiq
|
|
|
1086
1179
|
# works.each do |process_id, thread_id, work|
|
|
1087
1180
|
# # process_id is a unique identifier per Sidekiq process
|
|
1088
1181
|
# # thread_id is a unique identifier per thread
|
|
1089
|
-
# # work is a
|
|
1090
|
-
# #
|
|
1091
|
-
# # run_at is an epoch Integer.
|
|
1182
|
+
# # work is a `Sidekiq::Work` instance that has the following accessor methods.
|
|
1183
|
+
# # [work.queue, work.run_at, work.payload]
|
|
1092
1184
|
# end
|
|
1093
1185
|
#
|
|
1094
1186
|
class WorkSet
|
|
@@ -1110,11 +1202,11 @@ module Sidekiq
|
|
|
1110
1202
|
|
|
1111
1203
|
procs.zip(all_works).each do |key, workers|
|
|
1112
1204
|
workers.each_pair do |tid, json|
|
|
1113
|
-
results << [key, tid, Sidekiq.load_json(json)] unless json.empty?
|
|
1205
|
+
results << [key, tid, Sidekiq::Work.new(key, tid, Sidekiq.load_json(json))] unless json.empty?
|
|
1114
1206
|
end
|
|
1115
1207
|
end
|
|
1116
1208
|
|
|
1117
|
-
results.sort_by { |(_, _,
|
|
1209
|
+
results.sort_by { |(_, _, work)| work.run_at }.each(&block)
|
|
1118
1210
|
end
|
|
1119
1211
|
|
|
1120
1212
|
# Note that #size is only as accurate as Sidekiq's heartbeat,
|
|
@@ -1137,9 +1229,110 @@ module Sidekiq
|
|
|
1137
1229
|
end
|
|
1138
1230
|
end
|
|
1139
1231
|
end
|
|
1232
|
+
|
|
1233
|
+
##
|
|
1234
|
+
# Find the work which represents a job with the given JID.
|
|
1235
|
+
# *This is a slow O(n) operation*. Do not use for app logic.
|
|
1236
|
+
#
|
|
1237
|
+
# @param jid [String] the job identifier
|
|
1238
|
+
# @return [Sidekiq::Work] the work or nil
|
|
1239
|
+
def find_work(jid)
|
|
1240
|
+
each do |_process_id, _thread_id, work|
|
|
1241
|
+
job = work.job
|
|
1242
|
+
return work if job.jid == jid
|
|
1243
|
+
end
|
|
1244
|
+
nil
|
|
1245
|
+
end
|
|
1246
|
+
alias_method :find_work_by_jid, :find_work
|
|
1247
|
+
end
|
|
1248
|
+
|
|
1249
|
+
# Sidekiq::Work represents a job which is currently executing.
|
|
1250
|
+
class Work
|
|
1251
|
+
attr_reader :process_id
|
|
1252
|
+
attr_reader :thread_id
|
|
1253
|
+
|
|
1254
|
+
def initialize(pid, tid, hsh)
|
|
1255
|
+
@process_id = pid
|
|
1256
|
+
@thread_id = tid
|
|
1257
|
+
@hsh = hsh
|
|
1258
|
+
@job = nil
|
|
1259
|
+
end
|
|
1260
|
+
|
|
1261
|
+
def queue
|
|
1262
|
+
@hsh["queue"]
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
def run_at
|
|
1266
|
+
Time.at(@hsh["run_at"])
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1269
|
+
def job
|
|
1270
|
+
@job ||= Sidekiq::JobRecord.new(@hsh["payload"])
|
|
1271
|
+
end
|
|
1272
|
+
|
|
1273
|
+
def payload
|
|
1274
|
+
@hsh["payload"]
|
|
1275
|
+
end
|
|
1140
1276
|
end
|
|
1277
|
+
|
|
1141
1278
|
# Since "worker" is a nebulous term, we've deprecated the use of this class name.
|
|
1142
1279
|
# Is "worker" a process, a type of job, a thread? Undefined!
|
|
1143
1280
|
# WorkSet better describes the data.
|
|
1144
1281
|
Workers = WorkSet
|
|
1282
|
+
|
|
1283
|
+
class ProfileSet
|
|
1284
|
+
include Enumerable
|
|
1285
|
+
|
|
1286
|
+
# This is a point in time/snapshot API, you'll need to instantiate a new instance
|
|
1287
|
+
# if you want to fetch newer records.
|
|
1288
|
+
def initialize
|
|
1289
|
+
@records = Sidekiq.redis do |c|
|
|
1290
|
+
# This throws away expired profiles
|
|
1291
|
+
c.zremrangebyscore("profiles", "-inf", Time.now.to_f.to_s)
|
|
1292
|
+
# retreive records, newest to oldest
|
|
1293
|
+
c.zrange("profiles", "+inf", 0, "byscore", "rev")
|
|
1294
|
+
end
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1297
|
+
def size
|
|
1298
|
+
@records.size
|
|
1299
|
+
end
|
|
1300
|
+
|
|
1301
|
+
def each(&block)
|
|
1302
|
+
fetch_keys = %w[started_at jid type token size elapsed].freeze
|
|
1303
|
+
arrays = Sidekiq.redis do |c|
|
|
1304
|
+
c.pipelined do |p|
|
|
1305
|
+
@records.each do |key|
|
|
1306
|
+
p.hmget(key, *fetch_keys)
|
|
1307
|
+
end
|
|
1308
|
+
end
|
|
1309
|
+
end
|
|
1310
|
+
|
|
1311
|
+
arrays.compact.map { |arr| ProfileRecord.new(arr) }.each(&block)
|
|
1312
|
+
end
|
|
1313
|
+
end
|
|
1314
|
+
|
|
1315
|
+
class ProfileRecord
|
|
1316
|
+
attr_reader :started_at, :jid, :type, :token, :size, :elapsed
|
|
1317
|
+
|
|
1318
|
+
def initialize(arr)
|
|
1319
|
+
# Must be same order as fetch_keys above
|
|
1320
|
+
@started_at = Time.at(Integer(arr[0]))
|
|
1321
|
+
@jid = arr[1]
|
|
1322
|
+
@type = arr[2]
|
|
1323
|
+
@token = arr[3]
|
|
1324
|
+
@size = Integer(arr[4])
|
|
1325
|
+
@elapsed = Float(arr[5])
|
|
1326
|
+
end
|
|
1327
|
+
|
|
1328
|
+
def key
|
|
1329
|
+
"#{token}-#{jid}"
|
|
1330
|
+
end
|
|
1331
|
+
|
|
1332
|
+
def data
|
|
1333
|
+
Sidekiq.redis { |c| c.hget(key, "data") }
|
|
1334
|
+
end
|
|
1335
|
+
end
|
|
1145
1336
|
end
|
|
1337
|
+
|
|
1338
|
+
Sidekiq.loader.run_load_hooks(:api)
|
data/lib/sidekiq/capsule.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "sidekiq/component"
|
|
2
4
|
|
|
3
5
|
module Sidekiq
|
|
@@ -9,14 +11,15 @@ module Sidekiq
|
|
|
9
11
|
# This capsule will pull jobs from the "single" queue and process
|
|
10
12
|
# the jobs with one thread, meaning the jobs will be processed serially.
|
|
11
13
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
14
|
+
# Sidekiq.configure_server do |config|
|
|
15
|
+
# config.capsule("single-threaded") do |cap|
|
|
16
|
+
# cap.concurrency = 1
|
|
17
|
+
# cap.queues = %w(single)
|
|
18
|
+
# end
|
|
16
19
|
# end
|
|
17
|
-
# end
|
|
18
20
|
class Capsule
|
|
19
21
|
include Sidekiq::Component
|
|
22
|
+
extend Forwardable
|
|
20
23
|
|
|
21
24
|
attr_reader :name
|
|
22
25
|
attr_reader :queues
|
|
@@ -24,6 +27,8 @@ module Sidekiq
|
|
|
24
27
|
attr_reader :mode
|
|
25
28
|
attr_reader :weights
|
|
26
29
|
|
|
30
|
+
def_delegators :@config, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig, :thread_priority
|
|
31
|
+
|
|
27
32
|
def initialize(name, config)
|
|
28
33
|
@name = name
|
|
29
34
|
@config = config
|
|
@@ -33,11 +38,15 @@ module Sidekiq
|
|
|
33
38
|
@mode = :strict
|
|
34
39
|
end
|
|
35
40
|
|
|
41
|
+
def to_h
|
|
42
|
+
{concurrency: concurrency, mode: mode, weights: weights}
|
|
43
|
+
end
|
|
44
|
+
|
|
36
45
|
def fetcher
|
|
37
46
|
@fetcher ||= begin
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
instance = (config[:fetch_class] || Sidekiq::BasicFetch).new(self)
|
|
48
|
+
instance.setup(config[:fetch_setup]) if instance.respond_to?(:setup)
|
|
49
|
+
instance
|
|
41
50
|
end
|
|
42
51
|
end
|
|
43
52
|
|
data/lib/sidekiq/cli.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
$stdout.sync = true
|
|
4
4
|
|
|
5
5
|
require "yaml"
|
|
6
|
-
require "singleton"
|
|
7
6
|
require "optparse"
|
|
8
7
|
require "erb"
|
|
9
8
|
require "fileutils"
|
|
@@ -17,7 +16,6 @@ require "sidekiq/launcher"
|
|
|
17
16
|
module Sidekiq # :nodoc:
|
|
18
17
|
class CLI
|
|
19
18
|
include Sidekiq::Component
|
|
20
|
-
include Singleton unless $TESTING
|
|
21
19
|
|
|
22
20
|
attr_accessor :launcher
|
|
23
21
|
attr_accessor :environment
|
|
@@ -31,6 +29,10 @@ module Sidekiq # :nodoc:
|
|
|
31
29
|
validate!
|
|
32
30
|
end
|
|
33
31
|
|
|
32
|
+
def self.instance
|
|
33
|
+
@instance ||= new
|
|
34
|
+
end
|
|
35
|
+
|
|
34
36
|
def jruby?
|
|
35
37
|
defined?(::JRUBY_VERSION)
|
|
36
38
|
end
|
|
@@ -38,7 +40,7 @@ module Sidekiq # :nodoc:
|
|
|
38
40
|
# Code within this method is not tested because it alters
|
|
39
41
|
# global process state irreversibly. PRs which improve the
|
|
40
42
|
# test coverage of Sidekiq::CLI are welcomed.
|
|
41
|
-
def run(boot_app: true)
|
|
43
|
+
def run(boot_app: true, warmup: true)
|
|
42
44
|
boot_application if boot_app
|
|
43
45
|
|
|
44
46
|
if environment == "development" && $stdout.tty? && @config.logger.formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
|
|
@@ -74,7 +76,7 @@ module Sidekiq # :nodoc:
|
|
|
74
76
|
# fire startup and start multithreading.
|
|
75
77
|
info = @config.redis_info
|
|
76
78
|
ver = Gem::Version.new(info["redis_version"])
|
|
77
|
-
raise "You are
|
|
79
|
+
raise "You are connected to Redis #{ver}, Sidekiq requires Redis 7.0.0 or greater" if ver < Gem::Version.new("7.0.0")
|
|
78
80
|
|
|
79
81
|
maxmemory_policy = info["maxmemory_policy"]
|
|
80
82
|
if maxmemory_policy != "noeviction" && maxmemory_policy != ""
|
|
@@ -101,6 +103,8 @@ module Sidekiq # :nodoc:
|
|
|
101
103
|
# Touch middleware so it isn't lazy loaded by multiple threads, #3043
|
|
102
104
|
@config.server_middleware
|
|
103
105
|
|
|
106
|
+
::Process.warmup if warmup && ::Process.respond_to?(:warmup) && ENV["RUBY_DISABLE_WARMUP"] != "1"
|
|
107
|
+
|
|
104
108
|
# Before this point, the process is initializing with just the main thread.
|
|
105
109
|
# Starting here the process will now have multiple threads running.
|
|
106
110
|
fire_event(:startup, reverse: false, reraise: true)
|
|
@@ -296,29 +300,18 @@ module Sidekiq # :nodoc:
|
|
|
296
300
|
|
|
297
301
|
if File.directory?(@config[:require])
|
|
298
302
|
require "rails"
|
|
299
|
-
if ::Rails::VERSION::MAJOR <
|
|
300
|
-
warn "Sidekiq #{Sidekiq::VERSION} only supports Rails
|
|
303
|
+
if ::Rails::VERSION::MAJOR < 7
|
|
304
|
+
warn "Sidekiq #{Sidekiq::VERSION} only supports Rails 7+"
|
|
301
305
|
end
|
|
302
306
|
require "sidekiq/rails"
|
|
303
307
|
require File.expand_path("#{@config[:require]}/config/environment.rb")
|
|
304
|
-
@config[:tag] ||= default_tag
|
|
308
|
+
@config[:tag] ||= default_tag(::Rails.root)
|
|
305
309
|
else
|
|
306
310
|
require @config[:require]
|
|
311
|
+
@config[:tag] ||= default_tag
|
|
307
312
|
end
|
|
308
313
|
end
|
|
309
314
|
|
|
310
|
-
def default_tag
|
|
311
|
-
dir = ::Rails.root
|
|
312
|
-
name = File.basename(dir)
|
|
313
|
-
prevdir = File.dirname(dir) # Capistrano release directory?
|
|
314
|
-
if name.to_i != 0 && prevdir
|
|
315
|
-
if File.basename(prevdir) == "releases"
|
|
316
|
-
return File.basename(File.dirname(prevdir))
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
name
|
|
320
|
-
end
|
|
321
|
-
|
|
322
315
|
def validate!
|
|
323
316
|
if !File.exist?(@config[:require]) ||
|
|
324
317
|
(File.directory?(@config[:require]) && !File.exist?("#{@config[:require]}/config/application.rb"))
|
|
@@ -393,7 +386,12 @@ module Sidekiq # :nodoc:
|
|
|
393
386
|
end
|
|
394
387
|
|
|
395
388
|
def initialize_logger
|
|
396
|
-
|
|
389
|
+
if @config[:verbose] || ENV["DEBUG_INVOCATION"] == "1"
|
|
390
|
+
# DEBUG_INVOCATION is a systemd-ism triggered by
|
|
391
|
+
# RestartMode=debug. We turn on debugging when the
|
|
392
|
+
# sidekiq process crashes and is restarted with this flag.
|
|
393
|
+
@config.logger.level = ::Logger::DEBUG
|
|
394
|
+
end
|
|
397
395
|
end
|
|
398
396
|
|
|
399
397
|
def parse_config(path)
|
|
@@ -421,3 +419,4 @@ end
|
|
|
421
419
|
|
|
422
420
|
require "sidekiq/systemd"
|
|
423
421
|
require "sidekiq/metrics/tracking"
|
|
422
|
+
require "sidekiq/job/interrupt_handler"
|