sidekiq 6.0.1 → 6.2.2
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 +147 -2
- data/LICENSE +1 -1
- data/README.md +4 -7
- data/bin/sidekiq +26 -2
- data/lib/generators/sidekiq/worker_generator.rb +1 -1
- data/lib/sidekiq/api.rb +151 -111
- data/lib/sidekiq/cli.rb +39 -10
- data/lib/sidekiq/client.rb +26 -15
- data/lib/sidekiq/extensions/action_mailer.rb +3 -2
- data/lib/sidekiq/extensions/active_record.rb +4 -3
- data/lib/sidekiq/extensions/class_methods.rb +5 -4
- data/lib/sidekiq/extensions/generic_proxy.rb +3 -1
- data/lib/sidekiq/fetch.rb +29 -21
- data/lib/sidekiq/job.rb +8 -0
- data/lib/sidekiq/job_logger.rb +2 -2
- data/lib/sidekiq/job_retry.rb +11 -12
- data/lib/sidekiq/launcher.rb +104 -24
- data/lib/sidekiq/logger.rb +12 -11
- data/lib/sidekiq/manager.rb +4 -4
- data/lib/sidekiq/middleware/chain.rb +6 -4
- data/lib/sidekiq/monitor.rb +2 -17
- data/lib/sidekiq/processor.rb +17 -39
- data/lib/sidekiq/rails.rb +16 -18
- data/lib/sidekiq/redis_connection.rb +21 -13
- data/lib/sidekiq/scheduled.rb +7 -1
- data/lib/sidekiq/sd_notify.rb +149 -0
- data/lib/sidekiq/systemd.rb +24 -0
- data/lib/sidekiq/testing.rb +2 -4
- data/lib/sidekiq/util.rb +28 -2
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +2 -2
- data/lib/sidekiq/web/application.rb +30 -19
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +35 -24
- data/lib/sidekiq/web/router.rb +6 -5
- data/lib/sidekiq/web.rb +37 -73
- data/lib/sidekiq/worker.rb +4 -7
- data/lib/sidekiq.rb +14 -8
- data/sidekiq.gemspec +12 -5
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +25 -27
- data/web/assets/stylesheets/application-dark.css +146 -124
- data/web/assets/stylesheets/application.css +35 -135
- data/web/locales/ar.yml +8 -2
- data/web/locales/de.yml +14 -2
- data/web/locales/en.yml +5 -0
- data/web/locales/es.yml +18 -2
- data/web/locales/fr.yml +10 -3
- data/web/locales/ja.yml +5 -0
- data/web/locales/lt.yml +83 -0
- data/web/locales/pl.yml +4 -4
- data/web/locales/ru.yml +4 -0
- data/web/locales/vi.yml +83 -0
- data/web/views/_job_info.erb +1 -1
- data/web/views/busy.erb +50 -19
- data/web/views/dashboard.erb +14 -6
- data/web/views/dead.erb +1 -1
- data/web/views/layout.erb +2 -1
- data/web/views/morgue.erb +6 -6
- data/web/views/queue.erb +1 -1
- data/web/views/queues.erb +10 -2
- data/web/views/retries.erb +7 -7
- data/web/views/retry.erb +1 -1
- data/web/views/scheduled.erb +1 -1
- metadata +26 -50
- data/.circleci/config.yml +0 -82
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -11
- data/.gitignore +0 -13
- data/.standard.yml +0 -20
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/5.0-Upgrade.md +0 -56
- data/6.0-Upgrade.md +0 -72
- data/COMM-LICENSE +0 -97
- data/Ent-2.0-Upgrade.md +0 -37
- data/Ent-Changes.md +0 -256
- data/Gemfile +0 -24
- data/Gemfile.lock +0 -196
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-3.0-Upgrade.md +0 -44
- data/Pro-4.0-Upgrade.md +0 -35
- data/Pro-5.0-Upgrade.md +0 -25
- data/Pro-Changes.md +0 -776
- data/Rakefile +0 -10
- data/code_of_conduct.md +0 -50
data/lib/sidekiq/api.rb
CHANGED
@@ -8,7 +8,7 @@ require "base64"
|
|
8
8
|
module Sidekiq
|
9
9
|
class Stats
|
10
10
|
def initialize
|
11
|
-
|
11
|
+
fetch_stats_fast!
|
12
12
|
end
|
13
13
|
|
14
14
|
def processed
|
@@ -51,7 +51,8 @@ module Sidekiq
|
|
51
51
|
Sidekiq::Stats::Queues.new.lengths
|
52
52
|
end
|
53
53
|
|
54
|
-
|
54
|
+
# O(1) redis calls
|
55
|
+
def fetch_stats_fast!
|
55
56
|
pipe1_res = Sidekiq.redis { |conn|
|
56
57
|
conn.pipelined do
|
57
58
|
conn.get("stat:processed")
|
@@ -64,6 +65,33 @@ module Sidekiq
|
|
64
65
|
end
|
65
66
|
}
|
66
67
|
|
68
|
+
default_queue_latency = if (entry = pipe1_res[6].first)
|
69
|
+
job = begin
|
70
|
+
Sidekiq.load_json(entry)
|
71
|
+
rescue
|
72
|
+
{}
|
73
|
+
end
|
74
|
+
now = Time.now.to_f
|
75
|
+
thence = job["enqueued_at"] || now
|
76
|
+
now - thence
|
77
|
+
else
|
78
|
+
0
|
79
|
+
end
|
80
|
+
|
81
|
+
@stats = {
|
82
|
+
processed: pipe1_res[0].to_i,
|
83
|
+
failed: pipe1_res[1].to_i,
|
84
|
+
scheduled_size: pipe1_res[2],
|
85
|
+
retry_size: pipe1_res[3],
|
86
|
+
dead_size: pipe1_res[4],
|
87
|
+
processes_size: pipe1_res[5],
|
88
|
+
|
89
|
+
default_queue_latency: default_queue_latency
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
# O(number of processes + number of queues) redis calls
|
94
|
+
def fetch_stats_slow!
|
67
95
|
processes = Sidekiq.redis { |conn|
|
68
96
|
conn.sscan_each("processes").to_a
|
69
97
|
}
|
@@ -80,33 +108,16 @@ module Sidekiq
|
|
80
108
|
}
|
81
109
|
|
82
110
|
s = processes.size
|
83
|
-
workers_size = pipe2_res[0...s].
|
84
|
-
enqueued = pipe2_res[s..-1].
|
111
|
+
workers_size = pipe2_res[0...s].sum(&:to_i)
|
112
|
+
enqueued = pipe2_res[s..-1].sum(&:to_i)
|
85
113
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
rescue
|
90
|
-
{}
|
91
|
-
end
|
92
|
-
now = Time.now.to_f
|
93
|
-
thence = job["enqueued_at"] || now
|
94
|
-
now - thence
|
95
|
-
else
|
96
|
-
0
|
97
|
-
end
|
98
|
-
@stats = {
|
99
|
-
processed: pipe1_res[0].to_i,
|
100
|
-
failed: pipe1_res[1].to_i,
|
101
|
-
scheduled_size: pipe1_res[2],
|
102
|
-
retry_size: pipe1_res[3],
|
103
|
-
dead_size: pipe1_res[4],
|
104
|
-
processes_size: pipe1_res[5],
|
114
|
+
@stats[:workers_size] = workers_size
|
115
|
+
@stats[:enqueued] = enqueued
|
116
|
+
end
|
105
117
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
}
|
118
|
+
def fetch_stats!
|
119
|
+
fetch_stats_fast!
|
120
|
+
fetch_stats_slow!
|
110
121
|
end
|
111
122
|
|
112
123
|
def reset(*stats)
|
@@ -126,7 +137,8 @@ module Sidekiq
|
|
126
137
|
private
|
127
138
|
|
128
139
|
def stat(s)
|
129
|
-
@stats[s]
|
140
|
+
fetch_stats_slow! if @stats[s].nil?
|
141
|
+
@stats[s] || raise(ArgumentError, "Unknown stat #{s}")
|
130
142
|
end
|
131
143
|
|
132
144
|
class Queues
|
@@ -140,13 +152,8 @@ module Sidekiq
|
|
140
152
|
end
|
141
153
|
}
|
142
154
|
|
143
|
-
|
144
|
-
array_of_arrays
|
145
|
-
memo[queue] = lengths[i]
|
146
|
-
i += 1
|
147
|
-
}.sort_by { |_, size| size }
|
148
|
-
|
149
|
-
Hash[array_of_arrays.reverse]
|
155
|
+
array_of_arrays = queues.zip(lengths).sort_by { |_, size| -size }
|
156
|
+
array_of_arrays.to_h
|
150
157
|
end
|
151
158
|
end
|
152
159
|
end
|
@@ -168,18 +175,12 @@ module Sidekiq
|
|
168
175
|
private
|
169
176
|
|
170
177
|
def date_stat_hash(stat)
|
171
|
-
i = 0
|
172
178
|
stat_hash = {}
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
datestr = date.strftime("%Y-%m-%d")
|
179
|
-
keys << "stat:#{stat}:#{datestr}"
|
180
|
-
dates << datestr
|
181
|
-
i += 1
|
182
|
-
end
|
179
|
+
dates = @start_date.downto(@start_date - @days_previous + 1).map { |date|
|
180
|
+
date.strftime("%Y-%m-%d")
|
181
|
+
}
|
182
|
+
|
183
|
+
keys = dates.map { |datestr| "stat:#{stat}:#{datestr}" }
|
183
184
|
|
184
185
|
begin
|
185
186
|
Sidekiq.redis do |conn|
|
@@ -266,7 +267,7 @@ module Sidekiq
|
|
266
267
|
break if entries.empty?
|
267
268
|
page += 1
|
268
269
|
entries.each do |entry|
|
269
|
-
yield
|
270
|
+
yield JobRecord.new(entry, @name)
|
270
271
|
end
|
271
272
|
deleted_size = initial_size - size
|
272
273
|
end
|
@@ -276,7 +277,7 @@ module Sidekiq
|
|
276
277
|
# Find the job with the given JID within this queue.
|
277
278
|
#
|
278
279
|
# This is a slow, inefficient operation. Do not use under
|
279
|
-
# normal conditions.
|
280
|
+
# normal conditions.
|
280
281
|
def find_job(jid)
|
281
282
|
detect { |j| j.jid == jid }
|
282
283
|
end
|
@@ -284,7 +285,7 @@ module Sidekiq
|
|
284
285
|
def clear
|
285
286
|
Sidekiq.redis do |conn|
|
286
287
|
conn.multi do
|
287
|
-
conn.
|
288
|
+
conn.unlink(@rname)
|
288
289
|
conn.srem("queues", name)
|
289
290
|
end
|
290
291
|
end
|
@@ -297,9 +298,9 @@ module Sidekiq
|
|
297
298
|
# sorted set.
|
298
299
|
#
|
299
300
|
# The job should be considered immutable but may be
|
300
|
-
# removed from the queue via
|
301
|
+
# removed from the queue via JobRecord#delete.
|
301
302
|
#
|
302
|
-
class
|
303
|
+
class JobRecord
|
303
304
|
attr_reader :item
|
304
305
|
attr_reader :value
|
305
306
|
|
@@ -327,21 +328,23 @@ module Sidekiq
|
|
327
328
|
|
328
329
|
def display_class
|
329
330
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
330
|
-
@klass ||=
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
331
|
+
@klass ||= self["display_class"] || begin
|
332
|
+
case klass
|
333
|
+
when /\ASidekiq::Extensions::Delayed/
|
334
|
+
safe_load(args[0], klass) do |target, method, _|
|
335
|
+
"#{target}.#{method}"
|
336
|
+
end
|
337
|
+
when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
338
|
+
job_class = @item["wrapped"] || args[0]
|
339
|
+
if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob"
|
340
|
+
# MailerClass#mailer_method
|
341
|
+
args[0]["arguments"][0..1].join("#")
|
342
|
+
else
|
343
|
+
job_class
|
344
|
+
end
|
345
|
+
else
|
346
|
+
klass
|
347
|
+
end
|
345
348
|
end
|
346
349
|
end
|
347
350
|
|
@@ -438,17 +441,23 @@ module Sidekiq
|
|
438
441
|
|
439
442
|
def uncompress_backtrace(backtrace)
|
440
443
|
if backtrace.is_a?(Array)
|
441
|
-
# Handle old jobs with
|
444
|
+
# Handle old jobs with raw Array backtrace format
|
442
445
|
backtrace
|
443
446
|
else
|
444
447
|
decoded = Base64.decode64(backtrace)
|
445
448
|
uncompressed = Zlib::Inflate.inflate(decoded)
|
446
|
-
|
449
|
+
begin
|
450
|
+
Sidekiq.load_json(uncompressed)
|
451
|
+
rescue
|
452
|
+
# Handle old jobs with marshalled backtrace format
|
453
|
+
# TODO Remove in 7.x
|
454
|
+
Marshal.load(uncompressed)
|
455
|
+
end
|
447
456
|
end
|
448
457
|
end
|
449
458
|
end
|
450
459
|
|
451
|
-
class SortedEntry <
|
460
|
+
class SortedEntry < JobRecord
|
452
461
|
attr_reader :score
|
453
462
|
attr_reader :parent
|
454
463
|
|
@@ -471,8 +480,9 @@ module Sidekiq
|
|
471
480
|
end
|
472
481
|
|
473
482
|
def reschedule(at)
|
474
|
-
|
475
|
-
|
483
|
+
Sidekiq.redis do |conn|
|
484
|
+
conn.zincrby(@parent.name, at.to_f - @score, Sidekiq.dump_json(@item))
|
485
|
+
end
|
476
486
|
end
|
477
487
|
|
478
488
|
def add_to_queue
|
@@ -516,7 +526,7 @@ module Sidekiq
|
|
516
526
|
else
|
517
527
|
# multiple jobs with the same score
|
518
528
|
# find the one with the right JID and push it
|
519
|
-
|
529
|
+
matched, nonmatched = results.partition { |message|
|
520
530
|
if message.index(jid)
|
521
531
|
msg = Sidekiq.load_json(message)
|
522
532
|
msg["jid"] == jid
|
@@ -525,12 +535,12 @@ module Sidekiq
|
|
525
535
|
end
|
526
536
|
}
|
527
537
|
|
528
|
-
msg =
|
538
|
+
msg = matched.first
|
529
539
|
yield msg if msg
|
530
540
|
|
531
541
|
# push the rest back onto the sorted set
|
532
542
|
conn.multi do
|
533
|
-
|
543
|
+
nonmatched.each do |message|
|
534
544
|
conn.zadd(parent.name, score.to_f.to_s, message)
|
535
545
|
end
|
536
546
|
end
|
@@ -554,7 +564,7 @@ module Sidekiq
|
|
554
564
|
end
|
555
565
|
|
556
566
|
def scan(match, count = 100)
|
557
|
-
return to_enum(:scan, match) unless block_given?
|
567
|
+
return to_enum(:scan, match, count) unless block_given?
|
558
568
|
|
559
569
|
match = "*#{match}*" unless match.include?("*")
|
560
570
|
Sidekiq.redis do |conn|
|
@@ -566,7 +576,7 @@ module Sidekiq
|
|
566
576
|
|
567
577
|
def clear
|
568
578
|
Sidekiq.redis do |conn|
|
569
|
-
conn.
|
579
|
+
conn.unlink(name)
|
570
580
|
end
|
571
581
|
end
|
572
582
|
alias_method :💣, :clear
|
@@ -648,11 +658,13 @@ module Sidekiq
|
|
648
658
|
Sidekiq.redis do |conn|
|
649
659
|
elements = conn.zrangebyscore(name, score, score)
|
650
660
|
elements.each do |element|
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
661
|
+
if element.index(jid)
|
662
|
+
message = Sidekiq.load_json(element)
|
663
|
+
if message["jid"] == jid
|
664
|
+
ret = conn.zrem(name, element)
|
665
|
+
@_size -= 1 if ret
|
666
|
+
break ret
|
667
|
+
end
|
656
668
|
end
|
657
669
|
end
|
658
670
|
end
|
@@ -776,40 +788,41 @@ module Sidekiq
|
|
776
788
|
# the hash named key has an expiry of 60 seconds.
|
777
789
|
# if it's not found, that means the process has not reported
|
778
790
|
# in to Redis and probably died.
|
779
|
-
to_prune =
|
780
|
-
|
781
|
-
|
782
|
-
end
|
791
|
+
to_prune = procs.select.with_index { |proc, i|
|
792
|
+
heartbeats[i].nil?
|
793
|
+
}
|
783
794
|
count = conn.srem("processes", to_prune) unless to_prune.empty?
|
784
795
|
end
|
785
796
|
count
|
786
797
|
end
|
787
798
|
|
788
799
|
def each
|
789
|
-
|
800
|
+
result = Sidekiq.redis { |conn|
|
801
|
+
procs = conn.sscan_each("processes").to_a.sort
|
790
802
|
|
791
|
-
Sidekiq.redis do |conn|
|
792
803
|
# We're making a tradeoff here between consuming more memory instead of
|
793
804
|
# making more roundtrips to Redis, but if you have hundreds or thousands of workers,
|
794
805
|
# you'll be happier this way
|
795
|
-
|
806
|
+
conn.pipelined do
|
796
807
|
procs.each do |key|
|
797
|
-
conn.hmget(key, "info", "busy", "beat", "quiet")
|
808
|
+
conn.hmget(key, "info", "busy", "beat", "quiet", "rss", "rtt_us")
|
798
809
|
end
|
799
|
-
|
810
|
+
end
|
811
|
+
}
|
800
812
|
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
813
|
+
result.each do |info, busy, at_s, quiet, rss, rtt|
|
814
|
+
# If a process is stopped between when we query Redis for `procs` and
|
815
|
+
# when we query for `result`, we will have an item in `result` that is
|
816
|
+
# composed of `nil` values.
|
817
|
+
next if info.nil?
|
806
818
|
|
807
|
-
|
808
|
-
|
809
|
-
|
819
|
+
hash = Sidekiq.load_json(info)
|
820
|
+
yield Process.new(hash.merge("busy" => busy.to_i,
|
821
|
+
"beat" => at_s.to_f,
|
822
|
+
"quiet" => quiet,
|
823
|
+
"rss" => rss.to_i,
|
824
|
+
"rtt_us" => rtt.to_i))
|
810
825
|
end
|
811
|
-
|
812
|
-
nil
|
813
826
|
end
|
814
827
|
|
815
828
|
# This method is not guaranteed accurate since it does not prune the set
|
@@ -820,6 +833,18 @@ module Sidekiq
|
|
820
833
|
Sidekiq.redis { |conn| conn.scard("processes") }
|
821
834
|
end
|
822
835
|
|
836
|
+
# Total number of threads available to execute jobs.
|
837
|
+
# For Sidekiq Enterprise customers this number (in production) must be
|
838
|
+
# less than or equal to your licensed concurrency.
|
839
|
+
def total_concurrency
|
840
|
+
sum { |x| x["concurrency"].to_i }
|
841
|
+
end
|
842
|
+
|
843
|
+
def total_rss_in_kb
|
844
|
+
sum { |x| x["rss"].to_i }
|
845
|
+
end
|
846
|
+
alias_method :total_rss, :total_rss_in_kb
|
847
|
+
|
823
848
|
# Returns the identity of the current cluster leader or "" if no leader.
|
824
849
|
# This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
|
825
850
|
# or Sidekiq Pro.
|
@@ -869,6 +894,10 @@ module Sidekiq
|
|
869
894
|
self["identity"]
|
870
895
|
end
|
871
896
|
|
897
|
+
def queues
|
898
|
+
self["queues"]
|
899
|
+
end
|
900
|
+
|
872
901
|
def quiet!
|
873
902
|
signal("TSTP")
|
874
903
|
end
|
@@ -899,8 +928,8 @@ module Sidekiq
|
|
899
928
|
end
|
900
929
|
|
901
930
|
##
|
902
|
-
#
|
903
|
-
#
|
931
|
+
# The WorkSet stores the work being done by this Sidekiq cluster.
|
932
|
+
# It tracks the process and thread working on each job.
|
904
933
|
#
|
905
934
|
# WARNING WARNING WARNING
|
906
935
|
#
|
@@ -908,33 +937,40 @@ module Sidekiq
|
|
908
937
|
# If you call #size => 5 and then expect #each to be
|
909
938
|
# called 5 times, you're going to have a bad time.
|
910
939
|
#
|
911
|
-
#
|
912
|
-
#
|
913
|
-
#
|
940
|
+
# works = Sidekiq::WorkSet.new
|
941
|
+
# works.size => 2
|
942
|
+
# works.each do |process_id, thread_id, work|
|
914
943
|
# # process_id is a unique identifier per Sidekiq process
|
915
944
|
# # thread_id is a unique identifier per thread
|
916
945
|
# # work is a Hash which looks like:
|
917
|
-
# # { 'queue' => name, 'run_at' => timestamp, 'payload' =>
|
946
|
+
# # { 'queue' => name, 'run_at' => timestamp, 'payload' => job_hash }
|
918
947
|
# # run_at is an epoch Integer.
|
919
948
|
# end
|
920
949
|
#
|
921
|
-
class
|
950
|
+
class WorkSet
|
922
951
|
include Enumerable
|
923
952
|
|
924
|
-
def each
|
953
|
+
def each(&block)
|
954
|
+
results = []
|
925
955
|
Sidekiq.redis do |conn|
|
926
956
|
procs = conn.sscan_each("processes").to_a
|
927
957
|
procs.sort.each do |key|
|
928
958
|
valid, workers = conn.pipelined {
|
929
|
-
conn.exists(key)
|
959
|
+
conn.exists?(key)
|
930
960
|
conn.hgetall("#{key}:workers")
|
931
961
|
}
|
932
962
|
next unless valid
|
933
963
|
workers.each_pair do |tid, json|
|
934
|
-
|
964
|
+
hsh = Sidekiq.load_json(json)
|
965
|
+
p = hsh["payload"]
|
966
|
+
# avoid breaking API, this is a side effect of the JSON optimization in #4316
|
967
|
+
hsh["payload"] = Sidekiq.load_json(p) if p.is_a?(String)
|
968
|
+
results << [key, tid, hsh]
|
935
969
|
end
|
936
970
|
end
|
937
971
|
end
|
972
|
+
|
973
|
+
results.sort_by { |(_, _, hsh)| hsh["run_at"] }.each(&block)
|
938
974
|
end
|
939
975
|
|
940
976
|
# Note that #size is only as accurate as Sidekiq's heartbeat,
|
@@ -953,9 +989,13 @@ module Sidekiq
|
|
953
989
|
procs.each do |key|
|
954
990
|
conn.hget(key, "busy")
|
955
991
|
end
|
956
|
-
}.
|
992
|
+
}.sum(&:to_i)
|
957
993
|
end
|
958
994
|
end
|
959
995
|
end
|
960
996
|
end
|
997
|
+
# Since "worker" is a nebulous term, we've deprecated the use of this class name.
|
998
|
+
# Is "worker" a process, a type of job, a thread? Undefined!
|
999
|
+
# WorkSet better describes the data.
|
1000
|
+
Workers = WorkSet
|
961
1001
|
end
|
data/lib/sidekiq/cli.rb
CHANGED
@@ -33,14 +33,18 @@ module Sidekiq
|
|
33
33
|
# Code within this method is not tested because it alters
|
34
34
|
# global process state irreversibly. PRs which improve the
|
35
35
|
# test coverage of Sidekiq::CLI are welcomed.
|
36
|
-
def run
|
37
|
-
|
36
|
+
def run(boot_app: true)
|
37
|
+
boot_application if boot_app
|
38
|
+
|
38
39
|
if environment == "development" && $stdout.tty? && Sidekiq.log_formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
|
39
40
|
print_banner
|
40
41
|
end
|
42
|
+
logger.info "Booted Rails #{::Rails.version} application in #{environment} environment" if rails_app?
|
41
43
|
|
42
44
|
self_read, self_write = IO.pipe
|
43
45
|
sigs = %w[INT TERM TTIN TSTP]
|
46
|
+
# USR1 and USR2 don't work on the JVM
|
47
|
+
sigs << "USR2" if Sidekiq.pro? && !jruby?
|
44
48
|
sigs.each do |sig|
|
45
49
|
trap sig do
|
46
50
|
self_write.puts(sig)
|
@@ -51,12 +55,25 @@ module Sidekiq
|
|
51
55
|
|
52
56
|
logger.info "Running in #{RUBY_DESCRIPTION}"
|
53
57
|
logger.info Sidekiq::LICENSE
|
54
|
-
logger.info "Upgrade to Sidekiq Pro for more features and support:
|
58
|
+
logger.info "Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org" unless defined?(::Sidekiq::Pro)
|
55
59
|
|
56
60
|
# touch the connection pool so it is created before we
|
57
61
|
# fire startup and start multithreading.
|
58
|
-
|
59
|
-
|
62
|
+
info = Sidekiq.redis_info
|
63
|
+
ver = info["redis_version"]
|
64
|
+
raise "You are connecting to Redis v#{ver}, Sidekiq requires Redis v4.0.0 or greater" if ver < "4"
|
65
|
+
|
66
|
+
maxmemory_policy = info["maxmemory_policy"]
|
67
|
+
if maxmemory_policy != "noeviction"
|
68
|
+
logger.warn <<~EOM
|
69
|
+
|
70
|
+
|
71
|
+
WARNING: Your Redis instance will evict Sidekiq data under heavy load.
|
72
|
+
The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
|
73
|
+
See: https://github.com/mperham/sidekiq/wiki/Using-Redis#memory
|
74
|
+
|
75
|
+
EOM
|
76
|
+
end
|
60
77
|
|
61
78
|
# Since the user can pass us a connection pool explicitly in the initializer, we
|
62
79
|
# need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
|
@@ -160,7 +177,7 @@ module Sidekiq
|
|
160
177
|
Sidekiq.logger.warn "<no backtrace available>"
|
161
178
|
end
|
162
179
|
end
|
163
|
-
}
|
180
|
+
}
|
164
181
|
}
|
165
182
|
UNHANDLED_SIGNAL_HANDLER = ->(cli) { Sidekiq.logger.info "No signal handler registered, ignoring" }
|
166
183
|
SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER
|
@@ -179,7 +196,11 @@ module Sidekiq
|
|
179
196
|
end
|
180
197
|
|
181
198
|
def set_environment(cli_env)
|
182
|
-
|
199
|
+
# See #984 for discussion.
|
200
|
+
# APP_ENV is now the preferred ENV term since it is not tech-specific.
|
201
|
+
# Both Sinatra 2.0+ and Sidekiq support this term.
|
202
|
+
# RAILS_ENV and RACK_ENV are there for legacy support.
|
203
|
+
@environment = cli_env || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
183
204
|
end
|
184
205
|
|
185
206
|
def symbolize_keys_deep!(hash)
|
@@ -221,8 +242,7 @@ module Sidekiq
|
|
221
242
|
opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
|
222
243
|
|
223
244
|
# set defaults
|
224
|
-
opts[:queues] = ["default"] if opts[:queues].nil?
|
225
|
-
opts[:strict] = true if opts[:strict].nil?
|
245
|
+
opts[:queues] = ["default"] if opts[:queues].nil?
|
226
246
|
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
|
227
247
|
|
228
248
|
# merge with defaults
|
@@ -233,7 +253,7 @@ module Sidekiq
|
|
233
253
|
Sidekiq.options
|
234
254
|
end
|
235
255
|
|
236
|
-
def
|
256
|
+
def boot_application
|
237
257
|
ENV["RACK_ENV"] = ENV["RAILS_ENV"] = environment
|
238
258
|
|
239
259
|
if File.directory?(options[:require])
|
@@ -361,6 +381,8 @@ module Sidekiq
|
|
361
381
|
end
|
362
382
|
|
363
383
|
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
384
|
+
opts.delete(:strict)
|
385
|
+
|
364
386
|
parse_queues(opts, opts.delete(:queues) || [])
|
365
387
|
|
366
388
|
opts
|
@@ -372,9 +394,16 @@ module Sidekiq
|
|
372
394
|
|
373
395
|
def parse_queue(opts, queue, weight = nil)
|
374
396
|
opts[:queues] ||= []
|
397
|
+
opts[:strict] = true if opts[:strict].nil?
|
375
398
|
raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
|
376
399
|
[weight.to_i, 1].max.times { opts[:queues] << queue }
|
377
400
|
opts[:strict] = false if weight.to_i > 0
|
378
401
|
end
|
402
|
+
|
403
|
+
def rails_app?
|
404
|
+
defined?(::Rails) && ::Rails.respond_to?(:application)
|
405
|
+
end
|
379
406
|
end
|
380
407
|
end
|
408
|
+
|
409
|
+
require "sidekiq/systemd"
|
data/lib/sidekiq/client.rb
CHANGED
@@ -19,7 +19,7 @@ module Sidekiq
|
|
19
19
|
#
|
20
20
|
def middleware(&block)
|
21
21
|
@chain ||= Sidekiq.client_middleware
|
22
|
-
if
|
22
|
+
if block
|
23
23
|
@chain = @chain.dup
|
24
24
|
yield @chain
|
25
25
|
end
|
@@ -90,17 +90,18 @@ module Sidekiq
|
|
90
90
|
# Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less
|
91
91
|
# than the number given if the middleware stopped processing for one or more jobs.
|
92
92
|
def push_bulk(items)
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
args = items["args"]
|
94
|
+
raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless args.is_a?(Array) && args.all?(Array)
|
95
|
+
return [] if args.empty? # no jobs to push
|
96
96
|
|
97
97
|
at = items.delete("at")
|
98
98
|
raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all?(Numeric))
|
99
|
+
raise ArgumentError, "Job 'at' Array must have same size as 'args' Array" if at.is_a?(Array) && at.size != args.size
|
99
100
|
|
100
101
|
normed = normalize_item(items)
|
101
|
-
payloads =
|
102
|
-
|
103
|
-
copy =
|
102
|
+
payloads = args.map.with_index { |job_args, index|
|
103
|
+
copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12), "enqueued_at" => Time.now.to_f)
|
104
|
+
copy["at"] = (at.is_a?(Array) ? at[index] : at) if at
|
104
105
|
|
105
106
|
result = process_single(items["class"], copy)
|
106
107
|
result || nil
|
@@ -193,7 +194,7 @@ module Sidekiq
|
|
193
194
|
end
|
194
195
|
|
195
196
|
def atomic_push(conn, payloads)
|
196
|
-
if payloads.first
|
197
|
+
if payloads.first.key?("at")
|
197
198
|
conn.zadd("schedule", payloads.map { |hash|
|
198
199
|
at = hash.delete("at").to_s
|
199
200
|
[at, Sidekiq.dump_json(hash)]
|
@@ -218,21 +219,31 @@ module Sidekiq
|
|
218
219
|
end
|
219
220
|
end
|
220
221
|
|
222
|
+
def validate(item)
|
223
|
+
raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: `#{item}`") unless item.is_a?(Hash) && item.key?("class") && item.key?("args")
|
224
|
+
raise(ArgumentError, "Job args must be an Array: `#{item}`") unless item["args"].is_a?(Array)
|
225
|
+
raise(ArgumentError, "Job class must be either a Class or String representation of the class name: `#{item}`") unless item["class"].is_a?(Class) || item["class"].is_a?(String)
|
226
|
+
raise(ArgumentError, "Job 'at' must be a Numeric timestamp: `#{item}`") if item.key?("at") && !item["at"].is_a?(Numeric)
|
227
|
+
raise(ArgumentError, "Job tags must be an Array: `#{item}`") if item["tags"] && !item["tags"].is_a?(Array)
|
228
|
+
end
|
229
|
+
|
221
230
|
def normalize_item(item)
|
222
|
-
|
223
|
-
raise(ArgumentError, "Job args must be an Array") unless item["args"].is_a?(Array)
|
224
|
-
raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item["class"].is_a?(Class) || item["class"].is_a?(String)
|
225
|
-
raise(ArgumentError, "Job 'at' must be a Numeric timestamp") if item.key?("at") && !item["at"].is_a?(Numeric)
|
226
|
-
raise(ArgumentError, "Job tags must be an Array") if item["tags"] && !item["tags"].is_a?(Array)
|
231
|
+
validate(item)
|
227
232
|
# raise(ArgumentError, "Arguments must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices") unless JSON.load(JSON.dump(item['args'])) == item['args']
|
228
233
|
|
229
|
-
|
230
|
-
|
234
|
+
# merge in the default sidekiq_options for the item's class and/or wrapped element
|
235
|
+
# this allows ActiveJobs to control sidekiq_options too.
|
236
|
+
defaults = normalized_hash(item["class"])
|
237
|
+
defaults = defaults.merge(item["wrapped"].get_sidekiq_options) if item["wrapped"].respond_to?("get_sidekiq_options")
|
238
|
+
item = defaults.merge(item)
|
239
|
+
|
240
|
+
raise(ArgumentError, "Job must include a valid queue name") if item["queue"].nil? || item["queue"] == ""
|
231
241
|
|
232
242
|
item["class"] = item["class"].to_s
|
233
243
|
item["queue"] = item["queue"].to_s
|
234
244
|
item["jid"] ||= SecureRandom.hex(12)
|
235
245
|
item["created_at"] ||= Time.now.to_f
|
246
|
+
|
236
247
|
item
|
237
248
|
end
|
238
249
|
|