sidekiq 5.1.3 → 5.2.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 +5 -5
- data/.circleci/config.yml +61 -0
- data/.gitignore +2 -0
- data/.travis.yml +2 -5
- data/COMM-LICENSE +11 -9
- data/Changes.md +73 -0
- data/Ent-Changes.md +22 -0
- data/Gemfile +20 -5
- data/Pro-Changes.md +30 -0
- data/README.md +1 -1
- data/Rakefile +2 -1
- data/bin/sidekiqctl +13 -92
- data/bin/sidekiqload +1 -1
- data/lib/sidekiq.rb +5 -3
- data/lib/sidekiq/api.rb +47 -14
- data/lib/sidekiq/cli.rb +64 -58
- data/lib/sidekiq/client.rb +4 -3
- data/lib/sidekiq/ctl.rb +221 -0
- data/lib/sidekiq/job_logger.rb +2 -2
- data/lib/sidekiq/job_retry.rb +33 -12
- data/lib/sidekiq/launcher.rb +14 -7
- data/lib/sidekiq/manager.rb +3 -3
- data/lib/sidekiq/middleware/server/active_record.rb +1 -1
- data/lib/sidekiq/processor.rb +76 -25
- data/lib/sidekiq/rails.rb +2 -1
- data/lib/sidekiq/redis_connection.rb +20 -1
- data/lib/sidekiq/scheduled.rb +32 -3
- data/lib/sidekiq/testing.rb +4 -4
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/application.rb +22 -0
- data/lib/sidekiq/web/helpers.rb +13 -6
- data/lib/sidekiq/worker.rb +24 -8
- data/sidekiq.gemspec +5 -12
- data/web/assets/javascripts/application.js +0 -0
- data/web/assets/javascripts/dashboard.js +15 -5
- data/web/assets/stylesheets/application.css +35 -2
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +1 -0
- data/web/locales/en.yml +1 -0
- data/web/locales/es.yml +3 -3
- data/web/views/_nav.erb +3 -17
- data/web/views/layout.erb +1 -1
- data/web/views/queue.erb +1 -0
- data/web/views/queues.erb +1 -1
- data/web/views/retries.erb +4 -0
- metadata +12 -79
data/bin/sidekiqload
CHANGED
@@ -65,7 +65,7 @@ def handle_signal(launcher, sig)
|
|
65
65
|
# http://jira.codehaus.org/browse/JRUBY-4637
|
66
66
|
raise Interrupt
|
67
67
|
when 'TERM'
|
68
|
-
# Heroku sends TERM and then waits
|
68
|
+
# Heroku sends TERM and then waits 30 seconds for process to exit.
|
69
69
|
raise Interrupt
|
70
70
|
when 'TSTP'
|
71
71
|
Sidekiq.logger.info "Received TSTP, no longer accepting new work"
|
data/lib/sidekiq.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'sidekiq/version'
|
3
4
|
fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.2.2." if RUBY_PLATFORM != 'java' && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.2.2')
|
4
5
|
|
@@ -17,7 +18,7 @@ module Sidekiq
|
|
17
18
|
DEFAULTS = {
|
18
19
|
queues: [],
|
19
20
|
labels: [],
|
20
|
-
concurrency:
|
21
|
+
concurrency: 10,
|
21
22
|
require: '.',
|
22
23
|
environment: nil,
|
23
24
|
timeout: 8,
|
@@ -56,6 +57,7 @@ module Sidekiq
|
|
56
57
|
def self.options
|
57
58
|
@options ||= DEFAULTS.dup
|
58
59
|
end
|
60
|
+
|
59
61
|
def self.options=(opts)
|
60
62
|
@options = opts
|
61
63
|
end
|
@@ -94,8 +96,8 @@ module Sidekiq
|
|
94
96
|
begin
|
95
97
|
yield conn
|
96
98
|
rescue Redis::CommandError => ex
|
97
|
-
#2550 Failover can cause the server to become a
|
98
|
-
# to disconnect and reopen the socket to get back to the
|
99
|
+
#2550 Failover can cause the server to become a replica, need
|
100
|
+
# to disconnect and reopen the socket to get back to the primary.
|
99
101
|
(conn.disconnect!; retryable = false; retry) if retryable && ex.message =~ /READONLY/
|
100
102
|
raise
|
101
103
|
end
|
data/lib/sidekiq/api.rb
CHANGED
@@ -2,7 +2,23 @@
|
|
2
2
|
require 'sidekiq'
|
3
3
|
|
4
4
|
module Sidekiq
|
5
|
+
|
6
|
+
module RedisScanner
|
7
|
+
def sscan(conn, key)
|
8
|
+
cursor = '0'
|
9
|
+
result = []
|
10
|
+
loop do
|
11
|
+
cursor, values = conn.sscan(key, cursor)
|
12
|
+
result.push(*values)
|
13
|
+
break if cursor == '0'
|
14
|
+
end
|
15
|
+
result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
5
19
|
class Stats
|
20
|
+
include RedisScanner
|
21
|
+
|
6
22
|
def initialize
|
7
23
|
fetch_stats!
|
8
24
|
end
|
@@ -57,19 +73,25 @@ module Sidekiq
|
|
57
73
|
conn.zcard('dead')
|
58
74
|
conn.scard('processes')
|
59
75
|
conn.lrange('queue:default', -1, -1)
|
60
|
-
conn.smembers('processes')
|
61
|
-
conn.smembers('queues')
|
62
76
|
end
|
63
77
|
end
|
64
78
|
|
79
|
+
processes = Sidekiq.redis do |conn|
|
80
|
+
sscan(conn, 'processes')
|
81
|
+
end
|
82
|
+
|
83
|
+
queues = Sidekiq.redis do |conn|
|
84
|
+
sscan(conn, 'queues')
|
85
|
+
end
|
86
|
+
|
65
87
|
pipe2_res = Sidekiq.redis do |conn|
|
66
88
|
conn.pipelined do
|
67
|
-
|
68
|
-
|
89
|
+
processes.each {|key| conn.hget(key, 'busy') }
|
90
|
+
queues.each {|queue| conn.llen("queue:#{queue}") }
|
69
91
|
end
|
70
92
|
end
|
71
93
|
|
72
|
-
s =
|
94
|
+
s = processes.size
|
73
95
|
workers_size = pipe2_res[0...s].map(&:to_i).inject(0, &:+)
|
74
96
|
enqueued = pipe2_res[s..-1].map(&:to_i).inject(0, &:+)
|
75
97
|
|
@@ -116,9 +138,11 @@ module Sidekiq
|
|
116
138
|
end
|
117
139
|
|
118
140
|
class Queues
|
141
|
+
include RedisScanner
|
142
|
+
|
119
143
|
def lengths
|
120
144
|
Sidekiq.redis do |conn|
|
121
|
-
queues = conn
|
145
|
+
queues = sscan(conn, 'queues')
|
122
146
|
|
123
147
|
lengths = conn.pipelined do
|
124
148
|
queues.each do |queue|
|
@@ -198,18 +222,19 @@ module Sidekiq
|
|
198
222
|
#
|
199
223
|
class Queue
|
200
224
|
include Enumerable
|
225
|
+
extend RedisScanner
|
201
226
|
|
202
227
|
##
|
203
228
|
# Return all known queues within Redis.
|
204
229
|
#
|
205
230
|
def self.all
|
206
|
-
Sidekiq.redis { |c| c
|
231
|
+
Sidekiq.redis { |c| sscan(c, 'queues') }.sort.map { |q| Sidekiq::Queue.new(q) }
|
207
232
|
end
|
208
233
|
|
209
234
|
attr_reader :name
|
210
235
|
|
211
236
|
def initialize(name="default")
|
212
|
-
@name = name
|
237
|
+
@name = name.to_s
|
213
238
|
@rname = "queue:#{name}"
|
214
239
|
end
|
215
240
|
|
@@ -645,6 +670,12 @@ module Sidekiq
|
|
645
670
|
each(&:retry)
|
646
671
|
end
|
647
672
|
end
|
673
|
+
|
674
|
+
def kill_all
|
675
|
+
while size > 0
|
676
|
+
each(&:kill)
|
677
|
+
end
|
678
|
+
end
|
648
679
|
end
|
649
680
|
|
650
681
|
##
|
@@ -700,17 +731,18 @@ module Sidekiq
|
|
700
731
|
#
|
701
732
|
class ProcessSet
|
702
733
|
include Enumerable
|
734
|
+
include RedisScanner
|
703
735
|
|
704
736
|
def initialize(clean_plz=true)
|
705
|
-
|
737
|
+
cleanup if clean_plz
|
706
738
|
end
|
707
739
|
|
708
740
|
# Cleans up dead processes recorded in Redis.
|
709
741
|
# Returns the number of processes cleaned.
|
710
|
-
def
|
742
|
+
def cleanup
|
711
743
|
count = 0
|
712
744
|
Sidekiq.redis do |conn|
|
713
|
-
procs = conn
|
745
|
+
procs = sscan(conn, 'processes').sort
|
714
746
|
heartbeats = conn.pipelined do
|
715
747
|
procs.each do |key|
|
716
748
|
conn.hget(key, 'info')
|
@@ -730,7 +762,7 @@ module Sidekiq
|
|
730
762
|
end
|
731
763
|
|
732
764
|
def each
|
733
|
-
procs = Sidekiq.redis { |conn| conn
|
765
|
+
procs = Sidekiq.redis { |conn| sscan(conn, 'processes') }.sort
|
734
766
|
|
735
767
|
Sidekiq.redis do |conn|
|
736
768
|
# We're making a tradeoff here between consuming more memory instead of
|
@@ -865,10 +897,11 @@ module Sidekiq
|
|
865
897
|
#
|
866
898
|
class Workers
|
867
899
|
include Enumerable
|
900
|
+
include RedisScanner
|
868
901
|
|
869
902
|
def each
|
870
903
|
Sidekiq.redis do |conn|
|
871
|
-
procs = conn
|
904
|
+
procs = sscan(conn, 'processes')
|
872
905
|
procs.sort.each do |key|
|
873
906
|
valid, workers = conn.pipelined do
|
874
907
|
conn.exists(key)
|
@@ -890,7 +923,7 @@ module Sidekiq
|
|
890
923
|
# which can easily get out of sync with crashy processes.
|
891
924
|
def size
|
892
925
|
Sidekiq.redis do |conn|
|
893
|
-
procs = conn
|
926
|
+
procs = sscan(conn, 'processes')
|
894
927
|
if procs.empty?
|
895
928
|
0
|
896
929
|
else
|
data/lib/sidekiq/cli.rb
CHANGED
@@ -9,6 +9,7 @@ require 'fileutils'
|
|
9
9
|
|
10
10
|
require 'sidekiq'
|
11
11
|
require 'sidekiq/util'
|
12
|
+
require 'sidekiq/launcher'
|
12
13
|
|
13
14
|
module Sidekiq
|
14
15
|
class CLI
|
@@ -23,23 +24,13 @@ module Sidekiq
|
|
23
24
|
proc { |me, data| "stopping" if me.stopping? },
|
24
25
|
]
|
25
26
|
|
26
|
-
# Used for CLI testing
|
27
|
-
attr_accessor :code
|
28
27
|
attr_accessor :launcher
|
29
28
|
attr_accessor :environment
|
30
29
|
|
31
|
-
def
|
32
|
-
@code = nil
|
33
|
-
end
|
34
|
-
|
35
|
-
def parse(args=ARGV)
|
36
|
-
@code = nil
|
37
|
-
|
30
|
+
def parse(args = ARGV)
|
38
31
|
setup_options(args)
|
39
32
|
initialize_logger
|
40
33
|
validate!
|
41
|
-
daemonize
|
42
|
-
write_pid
|
43
34
|
end
|
44
35
|
|
45
36
|
def jruby?
|
@@ -50,8 +41,10 @@ module Sidekiq
|
|
50
41
|
# global process state irreversibly. PRs which improve the
|
51
42
|
# test coverage of Sidekiq::CLI are welcomed.
|
52
43
|
def run
|
44
|
+
daemonize if options[:daemon]
|
45
|
+
write_pid
|
53
46
|
boot_system
|
54
|
-
print_banner
|
47
|
+
print_banner if environment == 'development' && $stdout.tty?
|
55
48
|
|
56
49
|
self_read, self_write = IO.pipe
|
57
50
|
sigs = %w(INT TERM TTIN TSTP)
|
@@ -79,6 +72,13 @@ module Sidekiq
|
|
79
72
|
# fire startup and start multithreading.
|
80
73
|
ver = Sidekiq.redis_info['redis_version']
|
81
74
|
raise "You are using Redis v#{ver}, Sidekiq requires Redis v2.8.0 or greater" if ver < '2.8'
|
75
|
+
logger.warn "Sidekiq 6.0 will require Redis 4.0+, you are using Redis v#{ver}" if ver < '4'
|
76
|
+
|
77
|
+
# Since the user can pass us a connection pool explicitly in the initializer, we
|
78
|
+
# need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
|
79
|
+
cursize = Sidekiq.redis_pool.size
|
80
|
+
needed = Sidekiq.options[:concurrency] + 2
|
81
|
+
raise "Your pool of #{cursize} Redis connections is too small, please increase the size to at least #{needed}" if cursize < needed
|
82
82
|
|
83
83
|
# cache process identity
|
84
84
|
Sidekiq.options[:identity] = identity
|
@@ -93,11 +93,14 @@ module Sidekiq
|
|
93
93
|
logger.debug { "Client Middleware: #{Sidekiq.client_middleware.map(&:klass).join(', ')}" }
|
94
94
|
logger.debug { "Server Middleware: #{Sidekiq.server_middleware.map(&:klass).join(', ')}" }
|
95
95
|
|
96
|
+
launch(self_read)
|
97
|
+
end
|
98
|
+
|
99
|
+
def launch(self_read)
|
96
100
|
if !options[:daemon]
|
97
101
|
logger.info 'Starting processing, hit Ctrl-C to stop'
|
98
102
|
end
|
99
103
|
|
100
|
-
require 'sidekiq/launcher'
|
101
104
|
@launcher = Sidekiq::Launcher.new(options)
|
102
105
|
|
103
106
|
begin
|
@@ -179,23 +182,15 @@ module Sidekiq
|
|
179
182
|
private
|
180
183
|
|
181
184
|
def print_banner
|
182
|
-
#
|
183
|
-
|
184
|
-
|
185
|
-
puts Sidekiq::CLI.banner
|
186
|
-
puts "\e[0m"
|
187
|
-
end
|
185
|
+
puts "\e[#{31}m"
|
186
|
+
puts Sidekiq::CLI.banner
|
187
|
+
puts "\e[0m"
|
188
188
|
end
|
189
189
|
|
190
190
|
def daemonize
|
191
|
-
return unless options[:daemon]
|
192
|
-
|
193
191
|
raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
|
194
|
-
files_to_reopen = []
|
195
|
-
ObjectSpace.each_object(File) do |file|
|
196
|
-
files_to_reopen << file unless file.closed?
|
197
|
-
end
|
198
192
|
|
193
|
+
files_to_reopen = ObjectSpace.each_object(File).reject { |f| f.closed? }
|
199
194
|
::Process.daemon(true, true)
|
200
195
|
|
201
196
|
files_to_reopen.each do |file|
|
@@ -233,15 +228,38 @@ module Sidekiq
|
|
233
228
|
alias_method :☠, :exit
|
234
229
|
|
235
230
|
def setup_options(args)
|
231
|
+
# parse CLI options
|
236
232
|
opts = parse_options(args)
|
233
|
+
|
237
234
|
set_environment opts[:environment]
|
238
235
|
|
239
|
-
|
240
|
-
|
236
|
+
# check config file presence
|
237
|
+
if opts[:config_file]
|
238
|
+
if opts[:config_file] && !File.exist?(opts[:config_file])
|
239
|
+
raise ArgumentError, "No such file #{opts[:config_file]}"
|
240
|
+
end
|
241
|
+
else
|
242
|
+
config_dir = if File.directory?(opts[:require].to_s)
|
243
|
+
File.join(opts[:require], 'config')
|
244
|
+
else
|
245
|
+
File.join(options[:require], 'config')
|
246
|
+
end
|
241
247
|
|
248
|
+
%w[sidekiq.yml sidekiq.yml.erb].each do |config_file|
|
249
|
+
path = File.join(config_dir, config_file)
|
250
|
+
opts[:config_file] ||= path if File.exist?(path)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# parse config file options
|
255
|
+
opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
|
256
|
+
|
257
|
+
# set defaults
|
258
|
+
opts[:queues] = Array(opts[:queues]) << 'default' if opts[:queues].nil? || opts[:queues].empty?
|
242
259
|
opts[:strict] = true if opts[:strict].nil?
|
243
|
-
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if
|
260
|
+
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
|
244
261
|
|
262
|
+
# merge with defaults
|
245
263
|
options.merge!(opts)
|
246
264
|
end
|
247
265
|
|
@@ -252,8 +270,6 @@ module Sidekiq
|
|
252
270
|
def boot_system
|
253
271
|
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment
|
254
272
|
|
255
|
-
raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
|
256
|
-
|
257
273
|
if File.directory?(options[:require])
|
258
274
|
require 'rails'
|
259
275
|
if ::Rails::VERSION::MAJOR < 4
|
@@ -273,10 +289,7 @@ module Sidekiq
|
|
273
289
|
end
|
274
290
|
options[:tag] ||= default_tag
|
275
291
|
else
|
276
|
-
|
277
|
-
"./#{options[:require]} or /path/to/#{options[:require]}"
|
278
|
-
|
279
|
-
require(options[:require]) || raise(ArgumentError, not_required_message)
|
292
|
+
require options[:require]
|
280
293
|
end
|
281
294
|
end
|
282
295
|
|
@@ -292,8 +305,6 @@ module Sidekiq
|
|
292
305
|
end
|
293
306
|
|
294
307
|
def validate!
|
295
|
-
options[:queues] << 'default' if options[:queues].empty?
|
296
|
-
|
297
308
|
if !File.exist?(options[:require]) ||
|
298
309
|
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
299
310
|
logger.info "=================================================================="
|
@@ -319,6 +330,7 @@ module Sidekiq
|
|
319
330
|
|
320
331
|
o.on '-d', '--daemon', "Daemonize process" do |arg|
|
321
332
|
opts[:daemon] = arg
|
333
|
+
puts "WARNING: Daemonization mode will be removed in Sidekiq 6.0, see #4045. Please use a proper process supervisor to start and manage your services"
|
322
334
|
end
|
323
335
|
|
324
336
|
o.on '-e', '--environment ENV', "Application environment" do |arg|
|
@@ -358,10 +370,12 @@ module Sidekiq
|
|
358
370
|
|
359
371
|
o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
|
360
372
|
opts[:logfile] = arg
|
373
|
+
puts "WARNING: Logfile redirection will be removed in Sidekiq 6.0, see #4045. Sidekiq will only log to STDOUT"
|
361
374
|
end
|
362
375
|
|
363
376
|
o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
|
364
377
|
opts[:pidfile] = arg
|
378
|
+
puts "WARNING: PID file creation will be removed in Sidekiq 6.0, see #4045. Please use a proper process supervisor to start and manage your services"
|
365
379
|
end
|
366
380
|
|
367
381
|
o.on '-V', '--version', "Print version and exit" do |arg|
|
@@ -375,11 +389,8 @@ module Sidekiq
|
|
375
389
|
logger.info @parser
|
376
390
|
die 1
|
377
391
|
end
|
378
|
-
@parser.parse!(argv)
|
379
392
|
|
380
|
-
|
381
|
-
opts[:config_file] ||= filename if File.exist?(filename)
|
382
|
-
end
|
393
|
+
@parser.parse!(argv)
|
383
394
|
|
384
395
|
opts
|
385
396
|
end
|
@@ -399,23 +410,18 @@ module Sidekiq
|
|
399
410
|
end
|
400
411
|
end
|
401
412
|
|
402
|
-
def parse_config(
|
403
|
-
opts = {}
|
404
|
-
if File.exist?(cfile)
|
405
|
-
opts = YAML.load(ERB.new(IO.read(cfile)).result) || opts
|
406
|
-
|
407
|
-
if opts.respond_to? :deep_symbolize_keys!
|
408
|
-
opts.deep_symbolize_keys!
|
409
|
-
else
|
410
|
-
symbolize_keys_deep!(opts)
|
411
|
-
end
|
413
|
+
def parse_config(path)
|
414
|
+
opts = YAML.load(ERB.new(File.read(path)).result) || {}
|
412
415
|
|
413
|
-
|
414
|
-
|
416
|
+
if opts.respond_to? :deep_symbolize_keys!
|
417
|
+
opts.deep_symbolize_keys!
|
415
418
|
else
|
416
|
-
|
417
|
-
# can be deployed by cap with just the defaults.
|
419
|
+
symbolize_keys_deep!(opts)
|
418
420
|
end
|
421
|
+
|
422
|
+
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
423
|
+
parse_queues(opts, opts.delete(:queues) || [])
|
424
|
+
|
419
425
|
ns = opts.delete(:namespace)
|
420
426
|
if ns
|
421
427
|
# logger hasn't been initialized yet, puts is all we have.
|
@@ -429,10 +435,10 @@ module Sidekiq
|
|
429
435
|
queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
|
430
436
|
end
|
431
437
|
|
432
|
-
def parse_queue(opts,
|
433
|
-
[
|
434
|
-
|
435
|
-
|
438
|
+
def parse_queue(opts, queue, weight = nil)
|
439
|
+
opts[:queues] ||= []
|
440
|
+
raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
|
441
|
+
[weight.to_i, 1].max.times { opts[:queues] << queue }
|
436
442
|
opts[:strict] = false if weight.to_i > 0
|
437
443
|
end
|
438
444
|
end
|
data/lib/sidekiq/client.rb
CHANGED
@@ -77,9 +77,10 @@ module Sidekiq
|
|
77
77
|
end
|
78
78
|
|
79
79
|
##
|
80
|
-
# Push a large number of jobs to Redis.
|
81
|
-
#
|
82
|
-
#
|
80
|
+
# Push a large number of jobs to Redis. This method cuts out the redis
|
81
|
+
# network round trip latency. I wouldn't recommend pushing more than
|
82
|
+
# 1000 per call but YMMV based on network quality, size of job args, etc.
|
83
|
+
# A large number of jobs can cause a bit of Redis command processing latency.
|
83
84
|
#
|
84
85
|
# Takes the same arguments as #push except that args is expected to be
|
85
86
|
# an Array of Arrays. All other keys are duplicated for each job. Each job
|