sidekiq 5.2.2 → 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 +50 -0
- data/Ent-Changes.md +18 -1
- data/Gemfile +15 -6
- data/Pro-Changes.md +13 -0
- data/README.md +1 -1
- data/Rakefile +2 -1
- data/bin/sidekiqctl +13 -92
- data/bin/sidekiqload +1 -1
- data/lib/sidekiq/cli.rb +57 -57
- 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 +1 -1
- data/lib/sidekiq/manager.rb +3 -3
- data/lib/sidekiq/middleware/server/active_record.rb +1 -1
- data/lib/sidekiq/processor.rb +24 -17
- data/lib/sidekiq/rails.rb +2 -1
- data/lib/sidekiq/redis_connection.rb +19 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/application.rb +1 -1
- data/lib/sidekiq/web/helpers.rb +10 -3
- data/lib/sidekiq/worker.rb +24 -8
- data/lib/sidekiq.rb +4 -2
- data/sidekiq.gemspec +3 -5
- data/web/assets/javascripts/dashboard.js +15 -5
- data/web/assets/stylesheets/application.css +35 -2
- data/web/assets/stylesheets/bootstrap.css +1 -1
- data/web/views/_nav.erb +3 -17
- data/web/views/queues.erb +1 -1
- metadata +21 -6
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,7 @@ 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'
|
82
76
|
|
83
77
|
# Since the user can pass us a connection pool explicitly in the initializer, we
|
84
78
|
# need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
|
@@ -99,11 +93,14 @@ module Sidekiq
|
|
99
93
|
logger.debug { "Client Middleware: #{Sidekiq.client_middleware.map(&:klass).join(', ')}" }
|
100
94
|
logger.debug { "Server Middleware: #{Sidekiq.server_middleware.map(&:klass).join(', ')}" }
|
101
95
|
|
96
|
+
launch(self_read)
|
97
|
+
end
|
98
|
+
|
99
|
+
def launch(self_read)
|
102
100
|
if !options[:daemon]
|
103
101
|
logger.info 'Starting processing, hit Ctrl-C to stop'
|
104
102
|
end
|
105
103
|
|
106
|
-
require 'sidekiq/launcher'
|
107
104
|
@launcher = Sidekiq::Launcher.new(options)
|
108
105
|
|
109
106
|
begin
|
@@ -185,23 +182,15 @@ module Sidekiq
|
|
185
182
|
private
|
186
183
|
|
187
184
|
def print_banner
|
188
|
-
#
|
189
|
-
|
190
|
-
|
191
|
-
puts Sidekiq::CLI.banner
|
192
|
-
puts "\e[0m"
|
193
|
-
end
|
185
|
+
puts "\e[#{31}m"
|
186
|
+
puts Sidekiq::CLI.banner
|
187
|
+
puts "\e[0m"
|
194
188
|
end
|
195
189
|
|
196
190
|
def daemonize
|
197
|
-
return unless options[:daemon]
|
198
|
-
|
199
191
|
raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
|
200
|
-
files_to_reopen = []
|
201
|
-
ObjectSpace.each_object(File) do |file|
|
202
|
-
files_to_reopen << file unless file.closed?
|
203
|
-
end
|
204
192
|
|
193
|
+
files_to_reopen = ObjectSpace.each_object(File).reject { |f| f.closed? }
|
205
194
|
::Process.daemon(true, true)
|
206
195
|
|
207
196
|
files_to_reopen.each do |file|
|
@@ -239,15 +228,38 @@ module Sidekiq
|
|
239
228
|
alias_method :☠, :exit
|
240
229
|
|
241
230
|
def setup_options(args)
|
231
|
+
# parse CLI options
|
242
232
|
opts = parse_options(args)
|
233
|
+
|
243
234
|
set_environment opts[:environment]
|
244
235
|
|
245
|
-
|
246
|
-
|
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
|
247
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?
|
248
259
|
opts[:strict] = true if opts[:strict].nil?
|
249
|
-
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if
|
260
|
+
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
|
250
261
|
|
262
|
+
# merge with defaults
|
251
263
|
options.merge!(opts)
|
252
264
|
end
|
253
265
|
|
@@ -258,8 +270,6 @@ module Sidekiq
|
|
258
270
|
def boot_system
|
259
271
|
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment
|
260
272
|
|
261
|
-
raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
|
262
|
-
|
263
273
|
if File.directory?(options[:require])
|
264
274
|
require 'rails'
|
265
275
|
if ::Rails::VERSION::MAJOR < 4
|
@@ -279,10 +289,7 @@ module Sidekiq
|
|
279
289
|
end
|
280
290
|
options[:tag] ||= default_tag
|
281
291
|
else
|
282
|
-
|
283
|
-
"./#{options[:require]} or /path/to/#{options[:require]}"
|
284
|
-
|
285
|
-
require(options[:require]) || raise(ArgumentError, not_required_message)
|
292
|
+
require options[:require]
|
286
293
|
end
|
287
294
|
end
|
288
295
|
|
@@ -298,8 +305,6 @@ module Sidekiq
|
|
298
305
|
end
|
299
306
|
|
300
307
|
def validate!
|
301
|
-
options[:queues] << 'default' if options[:queues].empty?
|
302
|
-
|
303
308
|
if !File.exist?(options[:require]) ||
|
304
309
|
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
305
310
|
logger.info "=================================================================="
|
@@ -325,6 +330,7 @@ module Sidekiq
|
|
325
330
|
|
326
331
|
o.on '-d', '--daemon', "Daemonize process" do |arg|
|
327
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"
|
328
334
|
end
|
329
335
|
|
330
336
|
o.on '-e', '--environment ENV', "Application environment" do |arg|
|
@@ -364,10 +370,12 @@ module Sidekiq
|
|
364
370
|
|
365
371
|
o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
|
366
372
|
opts[:logfile] = arg
|
373
|
+
puts "WARNING: Logfile redirection will be removed in Sidekiq 6.0, see #4045. Sidekiq will only log to STDOUT"
|
367
374
|
end
|
368
375
|
|
369
376
|
o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
|
370
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"
|
371
379
|
end
|
372
380
|
|
373
381
|
o.on '-V', '--version', "Print version and exit" do |arg|
|
@@ -381,11 +389,8 @@ module Sidekiq
|
|
381
389
|
logger.info @parser
|
382
390
|
die 1
|
383
391
|
end
|
384
|
-
@parser.parse!(argv)
|
385
392
|
|
386
|
-
|
387
|
-
opts[:config_file] ||= filename if File.exist?(filename)
|
388
|
-
end
|
393
|
+
@parser.parse!(argv)
|
389
394
|
|
390
395
|
opts
|
391
396
|
end
|
@@ -405,23 +410,18 @@ module Sidekiq
|
|
405
410
|
end
|
406
411
|
end
|
407
412
|
|
408
|
-
def parse_config(
|
409
|
-
opts = {}
|
410
|
-
if File.exist?(cfile)
|
411
|
-
opts = YAML.load(ERB.new(IO.read(cfile)).result) || opts
|
413
|
+
def parse_config(path)
|
414
|
+
opts = YAML.load(ERB.new(File.read(path)).result) || {}
|
412
415
|
|
413
|
-
|
414
|
-
|
415
|
-
else
|
416
|
-
symbolize_keys_deep!(opts)
|
417
|
-
end
|
418
|
-
|
419
|
-
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
420
|
-
parse_queues(opts, opts.delete(:queues) || [])
|
416
|
+
if opts.respond_to? :deep_symbolize_keys!
|
417
|
+
opts.deep_symbolize_keys!
|
421
418
|
else
|
422
|
-
|
423
|
-
# can be deployed by cap with just the defaults.
|
419
|
+
symbolize_keys_deep!(opts)
|
424
420
|
end
|
421
|
+
|
422
|
+
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
423
|
+
parse_queues(opts, opts.delete(:queues) || [])
|
424
|
+
|
425
425
|
ns = opts.delete(:namespace)
|
426
426
|
if ns
|
427
427
|
# logger hasn't been initialized yet, puts is all we have.
|
@@ -435,10 +435,10 @@ module Sidekiq
|
|
435
435
|
queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
|
436
436
|
end
|
437
437
|
|
438
|
-
def parse_queue(opts,
|
438
|
+
def parse_queue(opts, queue, weight = nil)
|
439
439
|
opts[:queues] ||= []
|
440
|
-
raise ArgumentError, "queues: #{
|
441
|
-
[weight.to_i, 1].max.times { 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 }
|
442
442
|
opts[:strict] = false if weight.to_i > 0
|
443
443
|
end
|
444
444
|
end
|
data/lib/sidekiq/ctl.rb
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'sidekiq/api'
|
5
|
+
|
6
|
+
class Sidekiq::Ctl
|
7
|
+
DEFAULT_KILL_TIMEOUT = 10
|
8
|
+
CMD = File.basename($0)
|
9
|
+
|
10
|
+
attr_reader :stage, :pidfile, :kill_timeout
|
11
|
+
|
12
|
+
def self.print_usage
|
13
|
+
puts "#{CMD} - control Sidekiq from the command line."
|
14
|
+
puts
|
15
|
+
puts "Usage: #{CMD} quiet <pidfile> <kill_timeout>"
|
16
|
+
puts " #{CMD} stop <pidfile> <kill_timeout>"
|
17
|
+
puts " #{CMD} status <section>"
|
18
|
+
puts
|
19
|
+
puts " <pidfile> is path to a pidfile"
|
20
|
+
puts " <kill_timeout> is number of seconds to wait until Sidekiq exits"
|
21
|
+
puts " (default: #{Sidekiq::Ctl::DEFAULT_KILL_TIMEOUT}), after which Sidekiq will be KILL'd"
|
22
|
+
puts
|
23
|
+
puts " <section> (optional) view a specific section of the status output"
|
24
|
+
puts " Valid sections are: #{Sidekiq::Ctl::Status::VALID_SECTIONS.join(', ')}"
|
25
|
+
puts
|
26
|
+
puts "Be sure to set the kill_timeout LONGER than Sidekiq's -t timeout. If you want"
|
27
|
+
puts "to wait 60 seconds for jobs to finish, use `sidekiq -t 60` and `sidekiqctl stop"
|
28
|
+
puts " path_to_pidfile 61`"
|
29
|
+
puts
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(stage, pidfile, timeout)
|
33
|
+
@stage = stage
|
34
|
+
@pidfile = pidfile
|
35
|
+
@kill_timeout = timeout
|
36
|
+
|
37
|
+
done('No pidfile given', :error) if !pidfile
|
38
|
+
done("Pidfile #{pidfile} does not exist", :warn) if !File.exist?(pidfile)
|
39
|
+
done('Invalid pidfile content', :error) if pid == 0
|
40
|
+
|
41
|
+
fetch_process
|
42
|
+
|
43
|
+
begin
|
44
|
+
send(stage)
|
45
|
+
rescue NoMethodError
|
46
|
+
done "Invalid command: #{stage}", :error
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch_process
|
51
|
+
Process.kill(0, pid)
|
52
|
+
rescue Errno::ESRCH
|
53
|
+
done "Process doesn't exist", :error
|
54
|
+
# We were not allowed to send a signal, but the process must have existed
|
55
|
+
# when Process.kill() was called.
|
56
|
+
rescue Errno::EPERM
|
57
|
+
return pid
|
58
|
+
end
|
59
|
+
|
60
|
+
def done(msg, error = nil)
|
61
|
+
puts msg
|
62
|
+
exit(exit_signal(error))
|
63
|
+
end
|
64
|
+
|
65
|
+
def exit_signal(error)
|
66
|
+
(error == :error) ? 1 : 0
|
67
|
+
end
|
68
|
+
|
69
|
+
def pid
|
70
|
+
@pid ||= File.read(pidfile).to_i
|
71
|
+
end
|
72
|
+
|
73
|
+
def quiet
|
74
|
+
`kill -TSTP #{pid}`
|
75
|
+
end
|
76
|
+
|
77
|
+
def stop
|
78
|
+
`kill -TERM #{pid}`
|
79
|
+
kill_timeout.times do
|
80
|
+
begin
|
81
|
+
Process.kill(0, pid)
|
82
|
+
rescue Errno::ESRCH
|
83
|
+
FileUtils.rm_f pidfile
|
84
|
+
done 'Sidekiq shut down gracefully.'
|
85
|
+
rescue Errno::EPERM
|
86
|
+
done 'Not permitted to shut down Sidekiq.'
|
87
|
+
end
|
88
|
+
sleep 1
|
89
|
+
end
|
90
|
+
`kill -9 #{pid}`
|
91
|
+
FileUtils.rm_f pidfile
|
92
|
+
done 'Sidekiq shut down forcefully.'
|
93
|
+
end
|
94
|
+
alias_method :shutdown, :stop
|
95
|
+
|
96
|
+
class Status
|
97
|
+
VALID_SECTIONS = %w[all version overview processes queues]
|
98
|
+
def display(section = nil)
|
99
|
+
section ||= 'all'
|
100
|
+
unless VALID_SECTIONS.include? section
|
101
|
+
puts "I don't know how to check the status of '#{section}'!"
|
102
|
+
puts "Try one of these: #{VALID_SECTIONS.join(', ')}"
|
103
|
+
return
|
104
|
+
end
|
105
|
+
send(section)
|
106
|
+
rescue StandardError => e
|
107
|
+
puts "Couldn't get status: #{e}"
|
108
|
+
end
|
109
|
+
|
110
|
+
def all
|
111
|
+
version
|
112
|
+
puts
|
113
|
+
overview
|
114
|
+
puts
|
115
|
+
processes
|
116
|
+
puts
|
117
|
+
queues
|
118
|
+
end
|
119
|
+
|
120
|
+
def version
|
121
|
+
puts "Sidekiq #{Sidekiq::VERSION}"
|
122
|
+
puts Time.now
|
123
|
+
end
|
124
|
+
|
125
|
+
def overview
|
126
|
+
puts '---- Overview ----'
|
127
|
+
puts " Processed: #{delimit stats.processed}"
|
128
|
+
puts " Failed: #{delimit stats.failed}"
|
129
|
+
puts " Busy: #{delimit stats.workers_size}"
|
130
|
+
puts " Enqueued: #{delimit stats.enqueued}"
|
131
|
+
puts " Retries: #{delimit stats.retry_size}"
|
132
|
+
puts " Scheduled: #{delimit stats.scheduled_size}"
|
133
|
+
puts " Dead: #{delimit stats.dead_size}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def processes
|
137
|
+
puts "---- Processes (#{process_set.size}) ----"
|
138
|
+
process_set.each_with_index do |process, index|
|
139
|
+
puts "#{process['identity']} #{tags_for(process)}"
|
140
|
+
puts " Started: #{Time.at(process['started_at'])} (#{time_ago(process['started_at'])})"
|
141
|
+
puts " Threads: #{process['concurrency']} (#{process['busy']} busy)"
|
142
|
+
puts " Queues: #{split_multiline(process['queues'].sort, pad: 11)}"
|
143
|
+
puts '' unless (index+1) == process_set.size
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
COL_PAD = 2
|
148
|
+
def queues
|
149
|
+
puts "---- Queues (#{queue_data.size}) ----"
|
150
|
+
columns = {
|
151
|
+
name: [:ljust, (['name'] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
|
152
|
+
size: [:rjust, (['size'] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
|
153
|
+
latency: [:rjust, (['latency'] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
|
154
|
+
}
|
155
|
+
columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
|
156
|
+
puts
|
157
|
+
queue_data.each do |q|
|
158
|
+
columns.each do |col, (dir, width)|
|
159
|
+
print q.send(col).public_send(dir, width)
|
160
|
+
end
|
161
|
+
puts
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def delimit(number)
|
168
|
+
number.to_s.reverse.scan(/.{1,3}/).join(',').reverse
|
169
|
+
end
|
170
|
+
|
171
|
+
def split_multiline(values, opts = {})
|
172
|
+
return 'none' unless values
|
173
|
+
pad = opts[:pad] || 0
|
174
|
+
max_length = opts[:max_length] || (80 - pad)
|
175
|
+
out = []
|
176
|
+
line = ''
|
177
|
+
values.each do |value|
|
178
|
+
if (line.length + value.length) > max_length
|
179
|
+
out << line
|
180
|
+
line = ' ' * pad
|
181
|
+
end
|
182
|
+
line << value + ', '
|
183
|
+
end
|
184
|
+
out << line[0..-3]
|
185
|
+
out.join("\n")
|
186
|
+
end
|
187
|
+
|
188
|
+
def tags_for(process)
|
189
|
+
tags = [
|
190
|
+
process['tag'],
|
191
|
+
process['labels'],
|
192
|
+
(process['quiet'] == 'true' ? 'quiet' : nil)
|
193
|
+
].flatten.compact
|
194
|
+
tags.any? ? "[#{tags.join('] [')}]" : nil
|
195
|
+
end
|
196
|
+
|
197
|
+
def time_ago(timestamp)
|
198
|
+
seconds = Time.now - Time.at(timestamp)
|
199
|
+
return 'just now' if seconds < 60
|
200
|
+
return 'a minute ago' if seconds < 120
|
201
|
+
return "#{seconds.floor / 60} minutes ago" if seconds < 3600
|
202
|
+
return 'an hour ago' if seconds < 7200
|
203
|
+
"#{seconds.floor / 60 / 60} hours ago"
|
204
|
+
end
|
205
|
+
|
206
|
+
QUEUE_STRUCT = Struct.new(:name, :size, :latency)
|
207
|
+
def queue_data
|
208
|
+
@queue_data ||= Sidekiq::Queue.all.map do |q|
|
209
|
+
QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf('%#.2f', q.latency))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def process_set
|
214
|
+
@process_set ||= Sidekiq::ProcessSet.new
|
215
|
+
end
|
216
|
+
|
217
|
+
def stats
|
218
|
+
@stats ||= Sidekiq::Stats.new
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
data/lib/sidekiq/job_logger.rb
CHANGED
@@ -3,7 +3,7 @@ module Sidekiq
|
|
3
3
|
class JobLogger
|
4
4
|
|
5
5
|
def call(item, queue)
|
6
|
-
start =
|
6
|
+
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
7
7
|
logger.info("start")
|
8
8
|
yield
|
9
9
|
logger.info("done: #{elapsed(start)} sec")
|
@@ -15,7 +15,7 @@ module Sidekiq
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def elapsed(start)
|
18
|
-
(
|
18
|
+
(::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start).round(3)
|
19
19
|
end
|
20
20
|
|
21
21
|
def logger
|
data/lib/sidekiq/job_retry.rb
CHANGED
@@ -56,7 +56,8 @@ module Sidekiq
|
|
56
56
|
# end
|
57
57
|
#
|
58
58
|
class JobRetry
|
59
|
-
class
|
59
|
+
class Handled < ::RuntimeError; end
|
60
|
+
class Skip < Handled; end
|
60
61
|
|
61
62
|
include Sidekiq::Util
|
62
63
|
|
@@ -71,7 +72,7 @@ module Sidekiq
|
|
71
72
|
# require the worker to be instantiated.
|
72
73
|
def global(msg, queue)
|
73
74
|
yield
|
74
|
-
rescue
|
75
|
+
rescue Handled => ex
|
75
76
|
raise ex
|
76
77
|
rescue Sidekiq::Shutdown => ey
|
77
78
|
# ignore, will be pushed back onto queue during hard_shutdown
|
@@ -80,9 +81,19 @@ module Sidekiq
|
|
80
81
|
# ignore, will be pushed back onto queue during hard_shutdown
|
81
82
|
raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e)
|
82
83
|
|
83
|
-
|
84
|
-
|
85
|
-
|
84
|
+
if msg['retry']
|
85
|
+
attempt_retry(nil, msg, queue, e)
|
86
|
+
else
|
87
|
+
Sidekiq.death_handlers.each do |handler|
|
88
|
+
begin
|
89
|
+
handler.call(msg, e)
|
90
|
+
rescue => handler_ex
|
91
|
+
handle_exception(handler_ex, { context: "Error calling death handler", job: msg })
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
raise Handled
|
86
97
|
end
|
87
98
|
|
88
99
|
|
@@ -96,7 +107,7 @@ module Sidekiq
|
|
96
107
|
# calling the handle_exception handlers.
|
97
108
|
def local(worker, msg, queue)
|
98
109
|
yield
|
99
|
-
rescue
|
110
|
+
rescue Handled => ex
|
100
111
|
raise ex
|
101
112
|
rescue Sidekiq::Shutdown => ey
|
102
113
|
# ignore, will be pushed back onto queue during hard_shutdown
|
@@ -130,9 +141,7 @@ module Sidekiq
|
|
130
141
|
queue
|
131
142
|
end
|
132
143
|
|
133
|
-
|
134
|
-
# that won't convert to JSON.
|
135
|
-
m = exception.message.to_s[0, 10_000]
|
144
|
+
m = exception_message(exception)
|
136
145
|
if m.respond_to?(:scrub!)
|
137
146
|
m.force_encoding("utf-8")
|
138
147
|
m.scrub!
|
@@ -158,7 +167,8 @@ module Sidekiq
|
|
158
167
|
|
159
168
|
if count < max_retry_attempts
|
160
169
|
delay = delay_for(worker, count, exception)
|
161
|
-
|
170
|
+
# Logging here can break retries if the logging device raises ENOSPC #3979
|
171
|
+
#logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
|
162
172
|
retry_at = Time.now.to_f + delay
|
163
173
|
payload = Sidekiq.dump_json(msg)
|
164
174
|
Sidekiq.redis do |conn|
|
@@ -171,7 +181,6 @@ module Sidekiq
|
|
171
181
|
end
|
172
182
|
|
173
183
|
def retries_exhausted(worker, msg, exception)
|
174
|
-
logger.debug { "Retries exhausted for job" }
|
175
184
|
begin
|
176
185
|
block = worker && worker.sidekiq_retries_exhausted_block
|
177
186
|
block.call(msg, exception) if block
|
@@ -191,7 +200,7 @@ module Sidekiq
|
|
191
200
|
end
|
192
201
|
|
193
202
|
def send_to_morgue(msg)
|
194
|
-
|
203
|
+
logger.info { "Adding dead #{msg['class']} job #{msg['jid']}" }
|
195
204
|
payload = Sidekiq.dump_json(msg)
|
196
205
|
DeadSet.new.kill(payload, notify_failure: false)
|
197
206
|
end
|
@@ -237,5 +246,17 @@ module Sidekiq
|
|
237
246
|
exception_caused_by_shutdown?(e.cause, checked_causes)
|
238
247
|
end
|
239
248
|
|
249
|
+
# Extract message from exception.
|
250
|
+
# Set a default if the message raises an error
|
251
|
+
def exception_message(exception)
|
252
|
+
begin
|
253
|
+
# App code can stuff all sorts of crazy binary data into the error message
|
254
|
+
# that won't convert to JSON.
|
255
|
+
exception.message.to_s[0, 10_000]
|
256
|
+
rescue
|
257
|
+
"!!! ERROR MESSAGE THREW AN ERROR !!!".dup
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
240
261
|
end
|
241
262
|
end
|
data/lib/sidekiq/launcher.rb
CHANGED
@@ -40,7 +40,7 @@ module Sidekiq
|
|
40
40
|
# return until all work is complete and cleaned up.
|
41
41
|
# It can take up to the timeout to complete.
|
42
42
|
def stop
|
43
|
-
deadline =
|
43
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @options[:timeout]
|
44
44
|
|
45
45
|
@done = true
|
46
46
|
@manager.quiet
|
data/lib/sidekiq/manager.rb
CHANGED
@@ -30,7 +30,7 @@ module Sidekiq
|
|
30
30
|
def initialize(options={})
|
31
31
|
logger.debug { options.inspect }
|
32
32
|
@options = options
|
33
|
-
@count = options[:concurrency] ||
|
33
|
+
@count = options[:concurrency] || 10
|
34
34
|
raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1
|
35
35
|
|
36
36
|
@done = false
|
@@ -70,11 +70,11 @@ module Sidekiq
|
|
70
70
|
return if @workers.empty?
|
71
71
|
|
72
72
|
logger.info { "Pausing to allow workers to finish..." }
|
73
|
-
remaining = deadline -
|
73
|
+
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
74
74
|
while remaining > PAUSE_TIME
|
75
75
|
return if @workers.empty?
|
76
76
|
sleep PAUSE_TIME
|
77
|
-
remaining = deadline -
|
77
|
+
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
78
78
|
end
|
79
79
|
return if @workers.empty?
|
80
80
|
|
@@ -7,7 +7,7 @@ module Sidekiq
|
|
7
7
|
def initialize
|
8
8
|
# With Rails 5+ we must use the Reloader **always**.
|
9
9
|
# The reloader handles code loading and db connection management.
|
10
|
-
if defined?(::Rails) && ::Rails::VERSION::MAJOR >= 5
|
10
|
+
if defined?(::Rails) && defined?(::Rails::VERSION) && ::Rails::VERSION::MAJOR >= 5
|
11
11
|
raise ArgumentError, "Rails 5 no longer needs or uses the ActiveRecord middleware."
|
12
12
|
end
|
13
13
|
end
|