sidekiq 6.0.7 → 6.4.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 +189 -2
- data/LICENSE +3 -3
- data/README.md +11 -10
- data/bin/sidekiq +8 -3
- data/bin/sidekiqload +57 -65
- data/bin/sidekiqmon +1 -1
- data/lib/generators/sidekiq/job_generator.rb +57 -0
- data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
- data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
- data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
- data/lib/sidekiq/api.rb +164 -116
- data/lib/sidekiq/cli.rb +49 -15
- data/lib/sidekiq/client.rb +51 -70
- data/lib/sidekiq/delay.rb +2 -0
- 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 +4 -2
- data/lib/sidekiq/fetch.rb +32 -23
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +16 -28
- data/lib/sidekiq/job_retry.rb +32 -33
- data/lib/sidekiq/job_util.rb +67 -0
- data/lib/sidekiq/launcher.rb +113 -54
- data/lib/sidekiq/logger.rb +11 -20
- data/lib/sidekiq/manager.rb +16 -18
- data/lib/sidekiq/middleware/chain.rb +10 -8
- data/lib/sidekiq/middleware/current_attributes.rb +57 -0
- data/lib/sidekiq/middleware/i18n.rb +4 -4
- data/lib/sidekiq/monitor.rb +1 -1
- data/lib/sidekiq/paginator.rb +8 -8
- data/lib/sidekiq/processor.rb +31 -31
- data/lib/sidekiq/rails.rb +36 -20
- data/lib/sidekiq/redis_connection.rb +16 -15
- data/lib/sidekiq/scheduled.rb +51 -16
- data/lib/sidekiq/sd_notify.rb +1 -1
- data/lib/sidekiq/testing/inline.rb +4 -4
- data/lib/sidekiq/testing.rb +38 -39
- data/lib/sidekiq/util.rb +41 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +2 -2
- data/lib/sidekiq/web/application.rb +21 -12
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +39 -33
- data/lib/sidekiq/web/router.rb +5 -2
- data/lib/sidekiq/web.rb +36 -72
- data/lib/sidekiq/worker.rb +135 -16
- data/lib/sidekiq.rb +33 -17
- data/sidekiq.gemspec +11 -4
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +113 -65
- data/web/assets/javascripts/dashboard.js +51 -51
- data/web/assets/stylesheets/application-dark.css +64 -43
- data/web/assets/stylesheets/application-rtl.css +0 -4
- data/web/assets/stylesheets/application.css +42 -239
- data/web/locales/ar.yml +8 -2
- data/web/locales/en.yml +4 -1
- data/web/locales/es.yml +18 -2
- data/web/locales/fr.yml +8 -1
- data/web/locales/ja.yml +3 -0
- data/web/locales/lt.yml +1 -1
- data/web/locales/pl.yml +4 -4
- data/web/locales/ru.yml +4 -0
- data/web/views/_footer.erb +1 -1
- data/web/views/_job_info.erb +1 -1
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +51 -20
- data/web/views/dashboard.erb +22 -14
- 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 +11 -11
- data/web/views/queues.erb +4 -4
- data/web/views/retries.erb +7 -7
- data/web/views/retry.erb +1 -1
- data/web/views/scheduled.erb +1 -1
- metadata +24 -49
- data/.circleci/config.yml +0 -60
- 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 -208
- 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 -782
- data/Rakefile +0 -10
- data/code_of_conduct.md +0 -50
- data/lib/generators/sidekiq/worker_generator.rb +0 -57
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,50 +51,33 @@ 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
|
-
conn.pipelined do
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
57
|
+
conn.pipelined do |pipeline|
|
58
|
+
pipeline.get("stat:processed")
|
59
|
+
pipeline.get("stat:failed")
|
60
|
+
pipeline.zcard("schedule")
|
61
|
+
pipeline.zcard("retry")
|
62
|
+
pipeline.zcard("dead")
|
63
|
+
pipeline.scard("processes")
|
64
|
+
pipeline.lrange("queue:default", -1, -1)
|
64
65
|
end
|
65
66
|
}
|
66
67
|
|
67
|
-
processes = Sidekiq.redis { |conn|
|
68
|
-
conn.sscan_each("processes").to_a
|
69
|
-
}
|
70
|
-
|
71
|
-
queues = Sidekiq.redis { |conn|
|
72
|
-
conn.sscan_each("queues").to_a
|
73
|
-
}
|
74
|
-
|
75
|
-
pipe2_res = Sidekiq.redis { |conn|
|
76
|
-
conn.pipelined do
|
77
|
-
processes.each { |key| conn.hget(key, "busy") }
|
78
|
-
queues.each { |queue| conn.llen("queue:#{queue}") }
|
79
|
-
end
|
80
|
-
}
|
81
|
-
|
82
|
-
s = processes.size
|
83
|
-
workers_size = pipe2_res[0...s].sum(&:to_i)
|
84
|
-
enqueued = pipe2_res[s..-1].sum(&:to_i)
|
85
|
-
|
86
68
|
default_queue_latency = if (entry = pipe1_res[6].first)
|
87
69
|
job = begin
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
70
|
+
Sidekiq.load_json(entry)
|
71
|
+
rescue
|
72
|
+
{}
|
73
|
+
end
|
92
74
|
now = Time.now.to_f
|
93
75
|
thence = job["enqueued_at"] || now
|
94
76
|
now - thence
|
95
77
|
else
|
96
78
|
0
|
97
79
|
end
|
80
|
+
|
98
81
|
@stats = {
|
99
82
|
processed: pipe1_res[0].to_i,
|
100
83
|
failed: pipe1_res[1].to_i,
|
@@ -103,10 +86,39 @@ module Sidekiq
|
|
103
86
|
dead_size: pipe1_res[4],
|
104
87
|
processes_size: pipe1_res[5],
|
105
88
|
|
106
|
-
default_queue_latency: default_queue_latency
|
107
|
-
|
108
|
-
|
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!
|
95
|
+
processes = Sidekiq.redis { |conn|
|
96
|
+
conn.sscan_each("processes").to_a
|
97
|
+
}
|
98
|
+
|
99
|
+
queues = Sidekiq.redis { |conn|
|
100
|
+
conn.sscan_each("queues").to_a
|
101
|
+
}
|
102
|
+
|
103
|
+
pipe2_res = Sidekiq.redis { |conn|
|
104
|
+
conn.pipelined do |pipeline|
|
105
|
+
processes.each { |key| pipeline.hget(key, "busy") }
|
106
|
+
queues.each { |queue| pipeline.llen("queue:#{queue}") }
|
107
|
+
end
|
109
108
|
}
|
109
|
+
|
110
|
+
s = processes.size
|
111
|
+
workers_size = pipe2_res[0...s].sum(&:to_i)
|
112
|
+
enqueued = pipe2_res[s..-1].sum(&:to_i)
|
113
|
+
|
114
|
+
@stats[:workers_size] = workers_size
|
115
|
+
@stats[:enqueued] = enqueued
|
116
|
+
@stats
|
117
|
+
end
|
118
|
+
|
119
|
+
def fetch_stats!
|
120
|
+
fetch_stats_fast!
|
121
|
+
fetch_stats_slow!
|
110
122
|
end
|
111
123
|
|
112
124
|
def reset(*stats)
|
@@ -126,7 +138,8 @@ module Sidekiq
|
|
126
138
|
private
|
127
139
|
|
128
140
|
def stat(s)
|
129
|
-
@stats[s]
|
141
|
+
fetch_stats_slow! if @stats[s].nil?
|
142
|
+
@stats[s] || raise(ArgumentError, "Unknown stat #{s}")
|
130
143
|
end
|
131
144
|
|
132
145
|
class Queues
|
@@ -134,20 +147,22 @@ module Sidekiq
|
|
134
147
|
Sidekiq.redis do |conn|
|
135
148
|
queues = conn.sscan_each("queues").to_a
|
136
149
|
|
137
|
-
lengths = conn.pipelined {
|
150
|
+
lengths = conn.pipelined { |pipeline|
|
138
151
|
queues.each do |queue|
|
139
|
-
|
152
|
+
pipeline.llen("queue:#{queue}")
|
140
153
|
end
|
141
154
|
}
|
142
155
|
|
143
156
|
array_of_arrays = queues.zip(lengths).sort_by { |_, size| -size }
|
144
|
-
|
157
|
+
array_of_arrays.to_h
|
145
158
|
end
|
146
159
|
end
|
147
160
|
end
|
148
161
|
|
149
162
|
class History
|
150
163
|
def initialize(days_previous, start_date = nil)
|
164
|
+
# we only store five years of data in Redis
|
165
|
+
raise ArgumentError if days_previous < 1 || days_previous > (5 * 365)
|
151
166
|
@days_previous = days_previous
|
152
167
|
@start_date = start_date || Time.now.utc.to_date
|
153
168
|
end
|
@@ -255,7 +270,7 @@ module Sidekiq
|
|
255
270
|
break if entries.empty?
|
256
271
|
page += 1
|
257
272
|
entries.each do |entry|
|
258
|
-
yield
|
273
|
+
yield JobRecord.new(entry, @name)
|
259
274
|
end
|
260
275
|
deleted_size = initial_size - size
|
261
276
|
end
|
@@ -265,16 +280,16 @@ module Sidekiq
|
|
265
280
|
# Find the job with the given JID within this queue.
|
266
281
|
#
|
267
282
|
# This is a slow, inefficient operation. Do not use under
|
268
|
-
# normal conditions.
|
283
|
+
# normal conditions.
|
269
284
|
def find_job(jid)
|
270
285
|
detect { |j| j.jid == jid }
|
271
286
|
end
|
272
287
|
|
273
288
|
def clear
|
274
289
|
Sidekiq.redis do |conn|
|
275
|
-
conn.multi do
|
276
|
-
|
277
|
-
|
290
|
+
conn.multi do |transaction|
|
291
|
+
transaction.unlink(@rname)
|
292
|
+
transaction.srem("queues", name)
|
278
293
|
end
|
279
294
|
end
|
280
295
|
end
|
@@ -286,9 +301,9 @@ module Sidekiq
|
|
286
301
|
# sorted set.
|
287
302
|
#
|
288
303
|
# The job should be considered immutable but may be
|
289
|
-
# removed from the queue via
|
304
|
+
# removed from the queue via JobRecord#delete.
|
290
305
|
#
|
291
|
-
class
|
306
|
+
class JobRecord
|
292
307
|
attr_reader :item
|
293
308
|
attr_reader :value
|
294
309
|
|
@@ -316,48 +331,54 @@ module Sidekiq
|
|
316
331
|
|
317
332
|
def display_class
|
318
333
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
319
|
-
@klass ||=
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
+
@klass ||= self["display_class"] || begin
|
335
|
+
case klass
|
336
|
+
when /\ASidekiq::Extensions::Delayed/
|
337
|
+
safe_load(args[0], klass) do |target, method, _|
|
338
|
+
"#{target}.#{method}"
|
339
|
+
end
|
340
|
+
when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
341
|
+
job_class = @item["wrapped"] || args[0]
|
342
|
+
if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob"
|
343
|
+
# MailerClass#mailer_method
|
344
|
+
args[0]["arguments"][0..1].join("#")
|
345
|
+
else
|
346
|
+
job_class
|
347
|
+
end
|
348
|
+
else
|
349
|
+
klass
|
350
|
+
end
|
334
351
|
end
|
335
352
|
end
|
336
353
|
|
337
354
|
def display_args
|
338
355
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
339
356
|
@display_args ||= case klass
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
357
|
+
when /\ASidekiq::Extensions::Delayed/
|
358
|
+
safe_load(args[0], args) do |_, _, arg, kwarg|
|
359
|
+
if !kwarg || kwarg.empty?
|
360
|
+
arg
|
361
|
+
else
|
362
|
+
[arg, kwarg]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
366
|
+
job_args = self["wrapped"] ? args[0]["arguments"] : []
|
367
|
+
if (self["wrapped"] || args[0]) == "ActionMailer::DeliveryJob"
|
368
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
369
|
+
job_args.drop(3)
|
370
|
+
elsif (self["wrapped"] || args[0]) == "ActionMailer::MailDeliveryJob"
|
371
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
372
|
+
job_args.drop(3).first["args"]
|
373
|
+
else
|
374
|
+
job_args
|
375
|
+
end
|
376
|
+
else
|
377
|
+
if self["encrypt"]
|
378
|
+
# no point in showing 150+ bytes of random garbage
|
379
|
+
args[-1] = "[encrypted data]"
|
380
|
+
end
|
381
|
+
args
|
361
382
|
end
|
362
383
|
end
|
363
384
|
|
@@ -443,7 +464,7 @@ module Sidekiq
|
|
443
464
|
end
|
444
465
|
end
|
445
466
|
|
446
|
-
class SortedEntry <
|
467
|
+
class SortedEntry < JobRecord
|
447
468
|
attr_reader :score
|
448
469
|
attr_reader :parent
|
449
470
|
|
@@ -502,9 +523,9 @@ module Sidekiq
|
|
502
523
|
|
503
524
|
def remove_job
|
504
525
|
Sidekiq.redis do |conn|
|
505
|
-
results = conn.multi {
|
506
|
-
|
507
|
-
|
526
|
+
results = conn.multi { |transaction|
|
527
|
+
transaction.zrangebyscore(parent.name, score, score)
|
528
|
+
transaction.zremrangebyscore(parent.name, score, score)
|
508
529
|
}.first
|
509
530
|
|
510
531
|
if results.size == 1
|
@@ -525,9 +546,9 @@ module Sidekiq
|
|
525
546
|
yield msg if msg
|
526
547
|
|
527
548
|
# push the rest back onto the sorted set
|
528
|
-
conn.multi do
|
549
|
+
conn.multi do |transaction|
|
529
550
|
nonmatched.each do |message|
|
530
|
-
|
551
|
+
transaction.zadd(parent.name, score.to_f.to_s, message)
|
531
552
|
end
|
532
553
|
end
|
533
554
|
end
|
@@ -714,10 +735,10 @@ module Sidekiq
|
|
714
735
|
def kill(message, opts = {})
|
715
736
|
now = Time.now.to_f
|
716
737
|
Sidekiq.redis do |conn|
|
717
|
-
conn.multi do
|
718
|
-
|
719
|
-
|
720
|
-
|
738
|
+
conn.multi do |transaction|
|
739
|
+
transaction.zadd(name, now.to_s, message)
|
740
|
+
transaction.zremrangebyscore(name, "-inf", now - self.class.timeout)
|
741
|
+
transaction.zremrangebyrank(name, 0, - self.class.max_jobs)
|
721
742
|
end
|
722
743
|
end
|
723
744
|
|
@@ -765,9 +786,9 @@ module Sidekiq
|
|
765
786
|
count = 0
|
766
787
|
Sidekiq.redis do |conn|
|
767
788
|
procs = conn.sscan_each("processes").to_a.sort
|
768
|
-
heartbeats = conn.pipelined {
|
789
|
+
heartbeats = conn.pipelined { |pipeline|
|
769
790
|
procs.each do |key|
|
770
|
-
|
791
|
+
pipeline.hget(key, "info")
|
771
792
|
end
|
772
793
|
}
|
773
794
|
|
@@ -789,21 +810,25 @@ module Sidekiq
|
|
789
810
|
# We're making a tradeoff here between consuming more memory instead of
|
790
811
|
# making more roundtrips to Redis, but if you have hundreds or thousands of workers,
|
791
812
|
# you'll be happier this way
|
792
|
-
conn.pipelined do
|
813
|
+
conn.pipelined do |pipeline|
|
793
814
|
procs.each do |key|
|
794
|
-
|
815
|
+
pipeline.hmget(key, "info", "busy", "beat", "quiet", "rss", "rtt_us")
|
795
816
|
end
|
796
817
|
end
|
797
818
|
}
|
798
819
|
|
799
|
-
result.each do |info, busy, at_s, quiet|
|
820
|
+
result.each do |info, busy, at_s, quiet, rss, rtt|
|
800
821
|
# If a process is stopped between when we query Redis for `procs` and
|
801
822
|
# when we query for `result`, we will have an item in `result` that is
|
802
823
|
# composed of `nil` values.
|
803
824
|
next if info.nil?
|
804
825
|
|
805
826
|
hash = Sidekiq.load_json(info)
|
806
|
-
yield Process.new(hash.merge("busy" => busy.to_i,
|
827
|
+
yield Process.new(hash.merge("busy" => busy.to_i,
|
828
|
+
"beat" => at_s.to_f,
|
829
|
+
"quiet" => quiet,
|
830
|
+
"rss" => rss.to_i,
|
831
|
+
"rtt_us" => rtt.to_i))
|
807
832
|
end
|
808
833
|
end
|
809
834
|
|
@@ -815,6 +840,18 @@ module Sidekiq
|
|
815
840
|
Sidekiq.redis { |conn| conn.scard("processes") }
|
816
841
|
end
|
817
842
|
|
843
|
+
# Total number of threads available to execute jobs.
|
844
|
+
# For Sidekiq Enterprise customers this number (in production) must be
|
845
|
+
# less than or equal to your licensed concurrency.
|
846
|
+
def total_concurrency
|
847
|
+
sum { |x| x["concurrency"].to_i }
|
848
|
+
end
|
849
|
+
|
850
|
+
def total_rss_in_kb
|
851
|
+
sum { |x| x["rss"].to_i }
|
852
|
+
end
|
853
|
+
alias_method :total_rss, :total_rss_in_kb
|
854
|
+
|
818
855
|
# Returns the identity of the current cluster leader or "" if no leader.
|
819
856
|
# This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
|
820
857
|
# or Sidekiq Pro.
|
@@ -864,6 +901,10 @@ module Sidekiq
|
|
864
901
|
self["identity"]
|
865
902
|
end
|
866
903
|
|
904
|
+
def queues
|
905
|
+
self["queues"]
|
906
|
+
end
|
907
|
+
|
867
908
|
def quiet!
|
868
909
|
signal("TSTP")
|
869
910
|
end
|
@@ -885,17 +926,17 @@ module Sidekiq
|
|
885
926
|
def signal(sig)
|
886
927
|
key = "#{identity}-signals"
|
887
928
|
Sidekiq.redis do |c|
|
888
|
-
c.multi do
|
889
|
-
|
890
|
-
|
929
|
+
c.multi do |transaction|
|
930
|
+
transaction.lpush(key, sig)
|
931
|
+
transaction.expire(key, 60)
|
891
932
|
end
|
892
933
|
end
|
893
934
|
end
|
894
935
|
end
|
895
936
|
|
896
937
|
##
|
897
|
-
#
|
898
|
-
#
|
938
|
+
# The WorkSet stores the work being done by this Sidekiq cluster.
|
939
|
+
# It tracks the process and thread working on each job.
|
899
940
|
#
|
900
941
|
# WARNING WARNING WARNING
|
901
942
|
#
|
@@ -903,26 +944,27 @@ module Sidekiq
|
|
903
944
|
# If you call #size => 5 and then expect #each to be
|
904
945
|
# called 5 times, you're going to have a bad time.
|
905
946
|
#
|
906
|
-
#
|
907
|
-
#
|
908
|
-
#
|
947
|
+
# works = Sidekiq::WorkSet.new
|
948
|
+
# works.size => 2
|
949
|
+
# works.each do |process_id, thread_id, work|
|
909
950
|
# # process_id is a unique identifier per Sidekiq process
|
910
951
|
# # thread_id is a unique identifier per thread
|
911
952
|
# # work is a Hash which looks like:
|
912
|
-
# # { 'queue' => name, 'run_at' => timestamp, 'payload' =>
|
953
|
+
# # { 'queue' => name, 'run_at' => timestamp, 'payload' => job_hash }
|
913
954
|
# # run_at is an epoch Integer.
|
914
955
|
# end
|
915
956
|
#
|
916
|
-
class
|
957
|
+
class WorkSet
|
917
958
|
include Enumerable
|
918
959
|
|
919
|
-
def each
|
960
|
+
def each(&block)
|
961
|
+
results = []
|
920
962
|
Sidekiq.redis do |conn|
|
921
963
|
procs = conn.sscan_each("processes").to_a
|
922
964
|
procs.sort.each do |key|
|
923
|
-
valid, workers = conn.pipelined {
|
924
|
-
|
925
|
-
|
965
|
+
valid, workers = conn.pipelined { |pipeline|
|
966
|
+
pipeline.exists?(key)
|
967
|
+
pipeline.hgetall("#{key}:work")
|
926
968
|
}
|
927
969
|
next unless valid
|
928
970
|
workers.each_pair do |tid, json|
|
@@ -930,10 +972,12 @@ module Sidekiq
|
|
930
972
|
p = hsh["payload"]
|
931
973
|
# avoid breaking API, this is a side effect of the JSON optimization in #4316
|
932
974
|
hsh["payload"] = Sidekiq.load_json(p) if p.is_a?(String)
|
933
|
-
|
975
|
+
results << [key, tid, hsh]
|
934
976
|
end
|
935
977
|
end
|
936
978
|
end
|
979
|
+
|
980
|
+
results.sort_by { |(_, _, hsh)| hsh["run_at"] }.each(&block)
|
937
981
|
end
|
938
982
|
|
939
983
|
# Note that #size is only as accurate as Sidekiq's heartbeat,
|
@@ -948,13 +992,17 @@ module Sidekiq
|
|
948
992
|
if procs.empty?
|
949
993
|
0
|
950
994
|
else
|
951
|
-
conn.pipelined {
|
995
|
+
conn.pipelined { |pipeline|
|
952
996
|
procs.each do |key|
|
953
|
-
|
997
|
+
pipeline.hget(key, "busy")
|
954
998
|
end
|
955
999
|
}.sum(&:to_i)
|
956
1000
|
end
|
957
1001
|
end
|
958
1002
|
end
|
959
1003
|
end
|
1004
|
+
# Since "worker" is a nebulous term, we've deprecated the use of this class name.
|
1005
|
+
# Is "worker" a process, a type of job, a thread? Undefined!
|
1006
|
+
# WorkSet better describes the data.
|
1007
|
+
Workers = WorkSet
|
960
1008
|
end
|
data/lib/sidekiq/cli.rb
CHANGED
@@ -20,7 +20,7 @@ module Sidekiq
|
|
20
20
|
attr_accessor :launcher
|
21
21
|
attr_accessor :environment
|
22
22
|
|
23
|
-
def parse(args = ARGV)
|
23
|
+
def parse(args = ARGV.dup)
|
24
24
|
setup_options(args)
|
25
25
|
initialize_logger
|
26
26
|
validate!
|
@@ -33,8 +33,9 @@ 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
|
@@ -43,9 +44,17 @@ module Sidekiq
|
|
43
44
|
self_read, self_write = IO.pipe
|
44
45
|
sigs = %w[INT TERM TTIN TSTP]
|
45
46
|
# USR1 and USR2 don't work on the JVM
|
46
|
-
sigs << "USR2"
|
47
|
+
sigs << "USR2" if Sidekiq.pro? && !jruby?
|
47
48
|
sigs.each do |sig|
|
48
|
-
trap
|
49
|
+
old_handler = Signal.trap(sig) do
|
50
|
+
if old_handler.respond_to?(:call)
|
51
|
+
begin
|
52
|
+
old_handler.call
|
53
|
+
rescue Exception => exc
|
54
|
+
# signal handlers can't use Logger so puts only
|
55
|
+
puts ["Error in #{sig} handler", exc].inspect
|
56
|
+
end
|
57
|
+
end
|
49
58
|
self_write.puts(sig)
|
50
59
|
end
|
51
60
|
rescue ArgumentError
|
@@ -58,9 +67,22 @@ module Sidekiq
|
|
58
67
|
|
59
68
|
# touch the connection pool so it is created before we
|
60
69
|
# fire startup and start multithreading.
|
61
|
-
|
70
|
+
info = Sidekiq.redis_info
|
71
|
+
ver = info["redis_version"]
|
62
72
|
raise "You are connecting to Redis v#{ver}, Sidekiq requires Redis v4.0.0 or greater" if ver < "4"
|
63
73
|
|
74
|
+
maxmemory_policy = info["maxmemory_policy"]
|
75
|
+
if maxmemory_policy != "noeviction"
|
76
|
+
logger.warn <<~EOM
|
77
|
+
|
78
|
+
|
79
|
+
WARNING: Your Redis instance will evict Sidekiq data under heavy load.
|
80
|
+
The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
|
81
|
+
See: https://github.com/mperham/sidekiq/wiki/Using-Redis#memory
|
82
|
+
|
83
|
+
EOM
|
84
|
+
end
|
85
|
+
|
64
86
|
# Since the user can pass us a connection pool explicitly in the initializer, we
|
65
87
|
# need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
|
66
88
|
cursize = Sidekiq.redis_pool.size
|
@@ -93,8 +115,8 @@ module Sidekiq
|
|
93
115
|
begin
|
94
116
|
launcher.run
|
95
117
|
|
96
|
-
while
|
97
|
-
signal =
|
118
|
+
while self_read.wait_readable
|
119
|
+
signal = self_read.gets.strip
|
98
120
|
handle_signal(signal)
|
99
121
|
end
|
100
122
|
rescue Interrupt
|
@@ -228,8 +250,7 @@ module Sidekiq
|
|
228
250
|
opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
|
229
251
|
|
230
252
|
# set defaults
|
231
|
-
opts[:queues] = ["default"] if opts[:queues].nil?
|
232
|
-
opts[:strict] = true if opts[:strict].nil?
|
253
|
+
opts[:queues] = ["default"] if opts[:queues].nil?
|
233
254
|
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
|
234
255
|
|
235
256
|
# merge with defaults
|
@@ -240,7 +261,7 @@ module Sidekiq
|
|
240
261
|
Sidekiq.options
|
241
262
|
end
|
242
263
|
|
243
|
-
def
|
264
|
+
def boot_application
|
244
265
|
ENV["RACK_ENV"] = ENV["RAILS_ENV"] = environment
|
245
266
|
|
246
267
|
if File.directory?(options[:require])
|
@@ -274,7 +295,7 @@ module Sidekiq
|
|
274
295
|
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
275
296
|
logger.info "=================================================================="
|
276
297
|
logger.info " Please point Sidekiq to a Rails application or a Ruby file "
|
277
|
-
logger.info " to load your
|
298
|
+
logger.info " to load your job classes with -r [DIR|FILE]."
|
278
299
|
logger.info "=================================================================="
|
279
300
|
logger.info @parser
|
280
301
|
die(1)
|
@@ -315,7 +336,7 @@ module Sidekiq
|
|
315
336
|
parse_queue opts, queue, weight
|
316
337
|
end
|
317
338
|
|
318
|
-
o.on "-r", "--require [PATH|DIR]", "Location of Rails application with
|
339
|
+
o.on "-r", "--require [PATH|DIR]", "Location of Rails application with jobs or file to require" do |arg|
|
319
340
|
opts[:require] = arg
|
320
341
|
end
|
321
342
|
|
@@ -359,7 +380,9 @@ module Sidekiq
|
|
359
380
|
end
|
360
381
|
|
361
382
|
def parse_config(path)
|
362
|
-
|
383
|
+
erb = ERB.new(File.read(path))
|
384
|
+
erb.filename = File.expand_path(path)
|
385
|
+
opts = load_yaml(erb.result) || {}
|
363
386
|
|
364
387
|
if opts.respond_to? :deep_symbolize_keys!
|
365
388
|
opts.deep_symbolize_keys!
|
@@ -368,19 +391,30 @@ module Sidekiq
|
|
368
391
|
end
|
369
392
|
|
370
393
|
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
394
|
+
opts.delete(:strict)
|
395
|
+
|
371
396
|
parse_queues(opts, opts.delete(:queues) || [])
|
372
397
|
|
373
398
|
opts
|
374
399
|
end
|
375
400
|
|
401
|
+
def load_yaml(src)
|
402
|
+
if Psych::VERSION > "4.0"
|
403
|
+
YAML.safe_load(src, permitted_classes: [Symbol], aliases: true)
|
404
|
+
else
|
405
|
+
YAML.load(src)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
376
409
|
def parse_queues(opts, queues_and_weights)
|
377
410
|
queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
|
378
411
|
end
|
379
412
|
|
380
413
|
def parse_queue(opts, queue, weight = nil)
|
381
414
|
opts[:queues] ||= []
|
415
|
+
opts[:strict] = true if opts[:strict].nil?
|
382
416
|
raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
|
383
|
-
[weight.to_i, 1].max.times { opts[:queues] << queue }
|
417
|
+
[weight.to_i, 1].max.times { opts[:queues] << queue.to_s }
|
384
418
|
opts[:strict] = false if weight.to_i > 0
|
385
419
|
end
|
386
420
|
|