sidekiq 3.5.4 → 5.2.7
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/{Contributing.md → .github/contributing.md} +0 -0
- data/.github/issue_template.md +11 -0
- data/.gitignore +3 -0
- data/.travis.yml +5 -10
- data/4.0-Upgrade.md +53 -0
- data/5.0-Upgrade.md +56 -0
- data/COMM-LICENSE +13 -11
- data/Changes.md +376 -1
- data/Ent-Changes.md +201 -2
- data/Gemfile +14 -18
- data/LICENSE +1 -1
- data/Pro-3.0-Upgrade.md +44 -0
- data/Pro-4.0-Upgrade.md +35 -0
- data/Pro-Changes.md +307 -2
- data/README.md +34 -22
- data/Rakefile +3 -3
- data/bin/sidekiq +0 -1
- data/bin/sidekiqctl +13 -86
- data/bin/sidekiqload +23 -27
- data/code_of_conduct.md +50 -0
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +3 -3
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +6 -6
- data/lib/sidekiq.rb +72 -25
- data/lib/sidekiq/api.rb +206 -73
- data/lib/sidekiq/cli.rb +145 -101
- data/lib/sidekiq/client.rb +42 -36
- data/lib/sidekiq/core_ext.rb +1 -105
- data/lib/sidekiq/ctl.rb +221 -0
- data/lib/sidekiq/delay.rb +42 -0
- data/lib/sidekiq/exception_handler.rb +4 -5
- data/lib/sidekiq/extensions/action_mailer.rb +1 -0
- data/lib/sidekiq/extensions/active_record.rb +1 -0
- data/lib/sidekiq/extensions/class_methods.rb +1 -0
- data/lib/sidekiq/extensions/generic_proxy.rb +8 -1
- data/lib/sidekiq/fetch.rb +36 -111
- data/lib/sidekiq/job_logger.rb +25 -0
- data/lib/sidekiq/job_retry.rb +262 -0
- data/lib/sidekiq/launcher.rb +129 -55
- data/lib/sidekiq/logging.rb +21 -3
- data/lib/sidekiq/manager.rb +83 -182
- data/lib/sidekiq/middleware/chain.rb +1 -0
- data/lib/sidekiq/middleware/i18n.rb +1 -0
- data/lib/sidekiq/middleware/server/active_record.rb +10 -0
- data/lib/sidekiq/paginator.rb +1 -0
- data/lib/sidekiq/processor.rb +221 -103
- data/lib/sidekiq/rails.rb +47 -27
- data/lib/sidekiq/redis_connection.rb +74 -7
- data/lib/sidekiq/scheduled.rb +87 -28
- data/lib/sidekiq/testing.rb +150 -19
- data/lib/sidekiq/testing/inline.rb +1 -0
- data/lib/sidekiq/util.rb +15 -17
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web.rb +120 -184
- data/lib/sidekiq/web/action.rb +89 -0
- data/lib/sidekiq/web/application.rb +353 -0
- data/lib/sidekiq/{web_helpers.rb → web/helpers.rb} +123 -47
- data/lib/sidekiq/web/router.rb +100 -0
- data/lib/sidekiq/worker.rb +135 -18
- data/sidekiq.gemspec +8 -14
- data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
- data/web/assets/javascripts/application.js +24 -20
- data/web/assets/javascripts/dashboard.js +33 -18
- data/web/assets/stylesheets/application-rtl.css +246 -0
- data/web/assets/stylesheets/application.css +401 -7
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +4 -8
- data/web/locales/ar.yml +81 -0
- data/web/locales/cs.yml +11 -1
- data/web/locales/de.yml +1 -1
- data/web/locales/en.yml +4 -0
- data/web/locales/es.yml +4 -3
- data/web/locales/fa.yml +80 -0
- data/web/locales/fr.yml +21 -12
- data/web/locales/he.yml +79 -0
- data/web/locales/ja.yml +24 -13
- data/web/locales/ru.yml +3 -0
- data/web/locales/ur.yml +80 -0
- data/web/views/_footer.erb +7 -9
- data/web/views/_job_info.erb +5 -1
- data/web/views/_nav.erb +5 -19
- data/web/views/_paging.erb +1 -1
- data/web/views/busy.erb +18 -9
- data/web/views/dashboard.erb +5 -5
- data/web/views/dead.erb +1 -1
- data/web/views/layout.erb +13 -5
- data/web/views/morgue.erb +16 -12
- data/web/views/queue.erb +12 -11
- data/web/views/queues.erb +5 -3
- data/web/views/retries.erb +19 -13
- data/web/views/retry.erb +2 -2
- data/web/views/scheduled.erb +4 -4
- data/web/views/scheduled_job_info.erb +1 -1
- metadata +45 -227
- data/lib/sidekiq/actor.rb +0 -39
- data/lib/sidekiq/middleware/server/logging.rb +0 -40
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -206
- data/test/config.yml +0 -9
- data/test/env_based_config.yml +0 -11
- data/test/fake_env.rb +0 -0
- data/test/fixtures/en.yml +0 -2
- data/test/helper.rb +0 -49
- data/test/test_api.rb +0 -493
- data/test/test_cli.rb +0 -335
- data/test/test_client.rb +0 -194
- data/test/test_exception_handler.rb +0 -55
- data/test/test_extensions.rb +0 -126
- data/test/test_fetch.rb +0 -104
- data/test/test_logging.rb +0 -34
- data/test/test_manager.rb +0 -168
- data/test/test_middleware.rb +0 -159
- data/test/test_processor.rb +0 -237
- data/test/test_rails.rb +0 -21
- data/test/test_redis_connection.rb +0 -126
- data/test/test_retry.rb +0 -325
- data/test/test_scheduled.rb +0 -114
- data/test/test_scheduling.rb +0 -49
- data/test/test_sidekiq.rb +0 -99
- data/test/test_testing.rb +0 -142
- data/test/test_testing_fake.rb +0 -268
- data/test/test_testing_inline.rb +0 -93
- data/test/test_util.rb +0 -16
- data/test/test_web.rb +0 -608
- data/test/test_web_helpers.rb +0 -53
- data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/web/assets/images/status/active.png +0 -0
- data/web/assets/images/status/idle.png +0 -0
- data/web/assets/javascripts/locales/README.md +0 -27
- data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
- data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
- data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
- data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
- data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
- data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
- data/web/views/_poll_js.erb +0 -5
data/lib/sidekiq/core_ext.rb
CHANGED
@@ -1,105 +1 @@
|
|
1
|
-
|
2
|
-
require 'active_support/core_ext/class/attribute'
|
3
|
-
rescue LoadError
|
4
|
-
|
5
|
-
# A dumbed down version of ActiveSupport's
|
6
|
-
# Class#class_attribute helper.
|
7
|
-
class Class
|
8
|
-
def class_attribute(*attrs)
|
9
|
-
instance_writer = true
|
10
|
-
|
11
|
-
attrs.each do |name|
|
12
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
13
|
-
def self.#{name}() nil end
|
14
|
-
def self.#{name}?() !!#{name} end
|
15
|
-
|
16
|
-
def self.#{name}=(val)
|
17
|
-
singleton_class.class_eval do
|
18
|
-
define_method(:#{name}) { val }
|
19
|
-
end
|
20
|
-
|
21
|
-
if singleton_class?
|
22
|
-
class_eval do
|
23
|
-
def #{name}
|
24
|
-
defined?(@#{name}) ? @#{name} : singleton_class.#{name}
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
val
|
29
|
-
end
|
30
|
-
|
31
|
-
def #{name}
|
32
|
-
defined?(@#{name}) ? @#{name} : self.class.#{name}
|
33
|
-
end
|
34
|
-
|
35
|
-
def #{name}?
|
36
|
-
!!#{name}
|
37
|
-
end
|
38
|
-
RUBY
|
39
|
-
|
40
|
-
attr_writer name if instance_writer
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
def singleton_class?
|
46
|
-
ancestors.first != self
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
begin
|
52
|
-
require 'active_support/core_ext/hash/keys'
|
53
|
-
require 'active_support/core_ext/hash/deep_merge'
|
54
|
-
rescue LoadError
|
55
|
-
class Hash
|
56
|
-
def stringify_keys
|
57
|
-
keys.each do |key|
|
58
|
-
self[key.to_s] = delete(key)
|
59
|
-
end
|
60
|
-
self
|
61
|
-
end if !{}.respond_to?(:stringify_keys)
|
62
|
-
|
63
|
-
def symbolize_keys
|
64
|
-
keys.each do |key|
|
65
|
-
self[(key.to_sym rescue key) || key] = delete(key)
|
66
|
-
end
|
67
|
-
self
|
68
|
-
end if !{}.respond_to?(:symbolize_keys)
|
69
|
-
|
70
|
-
def deep_merge(other_hash, &block)
|
71
|
-
dup.deep_merge!(other_hash, &block)
|
72
|
-
end if !{}.respond_to?(:deep_merge)
|
73
|
-
|
74
|
-
def deep_merge!(other_hash, &block)
|
75
|
-
other_hash.each_pair do |k,v|
|
76
|
-
tv = self[k]
|
77
|
-
if tv.is_a?(Hash) && v.is_a?(Hash)
|
78
|
-
self[k] = tv.deep_merge(v, &block)
|
79
|
-
else
|
80
|
-
self[k] = block && tv ? block.call(k, tv, v) : v
|
81
|
-
end
|
82
|
-
end
|
83
|
-
self
|
84
|
-
end if !{}.respond_to?(:deep_merge!)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
begin
|
89
|
-
require 'active_support/core_ext/string/inflections'
|
90
|
-
rescue LoadError
|
91
|
-
class String
|
92
|
-
def constantize
|
93
|
-
names = self.split('::')
|
94
|
-
names.shift if names.empty? || names.first.empty?
|
95
|
-
|
96
|
-
constant = Object
|
97
|
-
names.each do |name|
|
98
|
-
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
99
|
-
end
|
100
|
-
constant
|
101
|
-
end
|
102
|
-
end if !"".respond_to?(:constantize)
|
103
|
-
end
|
104
|
-
|
105
|
-
|
1
|
+
raise "no longer used, will be removed in 5.1"
|
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: #{Sidekiqctl::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: #{Sidekiqctl::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
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Sidekiq
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
def self.enable_delay!
|
6
|
+
if defined?(::ActiveSupport)
|
7
|
+
require 'sidekiq/extensions/active_record'
|
8
|
+
require 'sidekiq/extensions/action_mailer'
|
9
|
+
|
10
|
+
# Need to patch Psych so it can autoload classes whose names are serialized
|
11
|
+
# in the delayed YAML.
|
12
|
+
Psych::Visitors::ToRuby.prepend(Sidekiq::Extensions::PsychAutoload)
|
13
|
+
|
14
|
+
ActiveSupport.on_load(:active_record) do
|
15
|
+
include Sidekiq::Extensions::ActiveRecord
|
16
|
+
end
|
17
|
+
ActiveSupport.on_load(:action_mailer) do
|
18
|
+
extend Sidekiq::Extensions::ActionMailer
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'sidekiq/extensions/class_methods'
|
23
|
+
Module.__send__(:include, Sidekiq::Extensions::Klass)
|
24
|
+
end
|
25
|
+
|
26
|
+
module PsychAutoload
|
27
|
+
def resolve_class(klass_name)
|
28
|
+
return nil if !klass_name || klass_name.empty?
|
29
|
+
# constantize
|
30
|
+
names = klass_name.split('::')
|
31
|
+
names.shift if names.empty? || names.first.empty?
|
32
|
+
|
33
|
+
names.inject(Object) do |constant, name|
|
34
|
+
constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
35
|
+
end
|
36
|
+
rescue NameError
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'sidekiq'
|
2
3
|
|
3
4
|
module Sidekiq
|
@@ -5,12 +6,11 @@ module Sidekiq
|
|
5
6
|
|
6
7
|
class Logger
|
7
8
|
def call(ex, ctxHash)
|
8
|
-
Sidekiq.logger.warn(ctxHash) if !ctxHash.empty?
|
9
|
-
Sidekiq.logger.warn
|
10
|
-
Sidekiq.logger.warn
|
9
|
+
Sidekiq.logger.warn(Sidekiq.dump_json(ctxHash)) if !ctxHash.empty?
|
10
|
+
Sidekiq.logger.warn("#{ex.class.name}: #{ex.message}")
|
11
|
+
Sidekiq.logger.warn(ex.backtrace.join("\n")) unless ex.backtrace.nil?
|
11
12
|
end
|
12
13
|
|
13
|
-
# Set up default handler which just logs the error
|
14
14
|
Sidekiq.error_handlers << Sidekiq::ExceptionHandler::Logger.new
|
15
15
|
end
|
16
16
|
|
@@ -25,6 +25,5 @@ module Sidekiq
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
28
|
-
|
29
28
|
end
|
30
29
|
end
|
@@ -1,7 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'yaml'
|
2
3
|
|
3
4
|
module Sidekiq
|
4
5
|
module Extensions
|
6
|
+
SIZE_LIMIT = 8_192
|
7
|
+
|
5
8
|
class Proxy < BasicObject
|
6
9
|
def initialize(performable, target, options={})
|
7
10
|
@performable = performable
|
@@ -16,7 +19,11 @@ module Sidekiq
|
|
16
19
|
# to JSON and then deserialized on the other side back into a
|
17
20
|
# Ruby object.
|
18
21
|
obj = [@target, name, args]
|
19
|
-
|
22
|
+
marshalled = ::YAML.dump(obj)
|
23
|
+
if marshalled.size > SIZE_LIMIT
|
24
|
+
::Sidekiq.logger.warn { "#{@target}.#{name} job argument is #{marshalled.bytesize} bytes, you should refactor it to reduce the size" }
|
25
|
+
end
|
26
|
+
@performable.client_push({ 'class' => @performable, 'args' => [marshalled] }.merge(@opts))
|
20
27
|
end
|
21
28
|
end
|
22
29
|
|
data/lib/sidekiq/fetch.rb
CHANGED
@@ -1,101 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'sidekiq'
|
2
|
-
require 'sidekiq/util'
|
3
|
-
require 'sidekiq/actor'
|
4
3
|
|
5
4
|
module Sidekiq
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
class Fetcher
|
11
|
-
include Util
|
12
|
-
include Actor
|
13
|
-
|
14
|
-
TIMEOUT = 1
|
15
|
-
|
16
|
-
attr_reader :down
|
17
|
-
|
18
|
-
def initialize(mgr, options)
|
19
|
-
@down = nil
|
20
|
-
@mgr = mgr
|
21
|
-
@strategy = Fetcher.strategy.new(options)
|
22
|
-
end
|
23
|
-
|
24
|
-
# Fetching is straightforward: the Manager makes a fetch
|
25
|
-
# request for each idle processor when Sidekiq starts and
|
26
|
-
# then issues a new fetch request every time a Processor
|
27
|
-
# finishes a message.
|
28
|
-
#
|
29
|
-
# Because we have to shut down cleanly, we can't block
|
30
|
-
# forever and we can't loop forever. Instead we reschedule
|
31
|
-
# a new fetch if the current fetch turned up nothing.
|
32
|
-
def fetch
|
33
|
-
watchdog('Fetcher#fetch died') do
|
34
|
-
return if Sidekiq::Fetcher.done?
|
35
|
-
|
36
|
-
begin
|
37
|
-
work = @strategy.retrieve_work
|
38
|
-
::Sidekiq.logger.info("Redis is online, #{Time.now - @down} sec downtime") if @down
|
39
|
-
@down = nil
|
40
|
-
|
41
|
-
if work
|
42
|
-
@mgr.async.assign(work)
|
43
|
-
else
|
44
|
-
after(0) { fetch }
|
45
|
-
end
|
46
|
-
rescue => ex
|
47
|
-
handle_fetch_exception(ex)
|
48
|
-
end
|
5
|
+
class BasicFetch
|
6
|
+
# We want the fetch operation to timeout every few seconds so the thread
|
7
|
+
# can check if the process is shutting down.
|
8
|
+
TIMEOUT = 2
|
49
9
|
|
10
|
+
UnitOfWork = Struct.new(:queue, :job) do
|
11
|
+
def acknowledge
|
12
|
+
# nothing to do
|
50
13
|
end
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
14
|
|
55
|
-
|
56
|
-
|
57
|
-
|
15
|
+
def queue_name
|
16
|
+
queue.sub(/.*queue:/, '')
|
17
|
+
end
|
58
18
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
ex.backtrace.each do |bt|
|
63
|
-
logger.error(bt)
|
19
|
+
def requeue
|
20
|
+
Sidekiq.redis do |conn|
|
21
|
+
conn.rpush("queue:#{queue_name}", job)
|
64
22
|
end
|
65
23
|
end
|
66
|
-
@down ||= Time.now
|
67
|
-
pause
|
68
|
-
after(0) { fetch }
|
69
|
-
rescue Celluloid::TaskTerminated
|
70
|
-
# If redis is down when we try to shut down, all the fetch backlog
|
71
|
-
# raises these errors. Haven't been able to figure out what I'm doing wrong.
|
72
|
-
end
|
73
|
-
|
74
|
-
# Ugh. Say hello to a bloody hack.
|
75
|
-
# Can't find a clean way to get the fetcher to just stop processing
|
76
|
-
# its mailbox when shutdown starts.
|
77
|
-
def self.done!
|
78
|
-
@done = true
|
79
|
-
end
|
80
|
-
|
81
|
-
def self.reset # testing only
|
82
|
-
@done = nil
|
83
24
|
end
|
84
25
|
|
85
|
-
def self.done?
|
86
|
-
defined?(@done) && @done
|
87
|
-
end
|
88
|
-
|
89
|
-
def self.strategy
|
90
|
-
Sidekiq.options[:fetch] || BasicFetch
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
class BasicFetch
|
95
26
|
def initialize(options)
|
96
27
|
@strictly_ordered_queues = !!options[:strict]
|
97
28
|
@queues = options[:queues].map { |q| "queue:#{q}" }
|
98
|
-
|
29
|
+
if @strictly_ordered_queues
|
30
|
+
@queues = @queues.uniq
|
31
|
+
@queues << TIMEOUT
|
32
|
+
end
|
99
33
|
end
|
100
34
|
|
101
35
|
def retrieve_work
|
@@ -103,6 +37,22 @@ module Sidekiq
|
|
103
37
|
UnitOfWork.new(*work) if work
|
104
38
|
end
|
105
39
|
|
40
|
+
# Creating the Redis#brpop command takes into account any
|
41
|
+
# configured queue weights. By default Redis#brpop returns
|
42
|
+
# data from the first queue that has pending elements. We
|
43
|
+
# recreate the queue command each time we invoke Redis#brpop
|
44
|
+
# to honor weights and avoid queue starvation.
|
45
|
+
def queues_cmd
|
46
|
+
if @strictly_ordered_queues
|
47
|
+
@queues
|
48
|
+
else
|
49
|
+
queues = @queues.shuffle.uniq
|
50
|
+
queues << TIMEOUT
|
51
|
+
queues
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
106
56
|
# By leaving this as a class method, it can be pluggable and used by the Manager actor. Making it
|
107
57
|
# an instance method will make it async to the Fetcher actor
|
108
58
|
def self.bulk_requeue(inprogress, options)
|
@@ -112,7 +62,7 @@ module Sidekiq
|
|
112
62
|
jobs_to_requeue = {}
|
113
63
|
inprogress.each do |unit_of_work|
|
114
64
|
jobs_to_requeue[unit_of_work.queue_name] ||= []
|
115
|
-
jobs_to_requeue[unit_of_work.queue_name] << unit_of_work.
|
65
|
+
jobs_to_requeue[unit_of_work.queue_name] << unit_of_work.job
|
116
66
|
end
|
117
67
|
|
118
68
|
Sidekiq.redis do |conn|
|
@@ -122,35 +72,10 @@ module Sidekiq
|
|
122
72
|
end
|
123
73
|
end
|
124
74
|
end
|
125
|
-
Sidekiq.logger.info("Pushed #{inprogress.size}
|
75
|
+
Sidekiq.logger.info("Pushed #{inprogress.size} jobs back to Redis")
|
126
76
|
rescue => ex
|
127
77
|
Sidekiq.logger.warn("Failed to requeue #{inprogress.size} jobs: #{ex.message}")
|
128
78
|
end
|
129
79
|
|
130
|
-
UnitOfWork = Struct.new(:queue, :message) do
|
131
|
-
def acknowledge
|
132
|
-
# nothing to do
|
133
|
-
end
|
134
|
-
|
135
|
-
def queue_name
|
136
|
-
queue.gsub(/.*queue:/, '')
|
137
|
-
end
|
138
|
-
|
139
|
-
def requeue
|
140
|
-
Sidekiq.redis do |conn|
|
141
|
-
conn.rpush("queue:#{queue_name}", message)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
# Creating the Redis#brpop command takes into account any
|
147
|
-
# configured queue weights. By default Redis#brpop returns
|
148
|
-
# data from the first queue that has pending elements. We
|
149
|
-
# recreate the queue command each time we invoke Redis#brpop
|
150
|
-
# to honor weights and avoid queue starvation.
|
151
|
-
def queues_cmd
|
152
|
-
queues = @strictly_ordered_queues ? @unique_queues.dup : @queues.shuffle.uniq
|
153
|
-
queues << Sidekiq::Fetcher::TIMEOUT
|
154
|
-
end
|
155
80
|
end
|
156
81
|
end
|