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/cli.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
$stdout.sync = true
|
3
3
|
|
4
4
|
require 'yaml'
|
@@ -9,53 +9,55 @@ require 'fileutils'
|
|
9
9
|
|
10
10
|
require 'sidekiq'
|
11
11
|
require 'sidekiq/util'
|
12
|
+
require 'sidekiq/launcher'
|
12
13
|
|
13
14
|
module Sidekiq
|
14
|
-
# We are shutting down Sidekiq but what about workers that
|
15
|
-
# are working on some long job? This error is
|
16
|
-
# raised in workers that have not finished within the hard
|
17
|
-
# timeout limit. This is needed to rollback db transactions,
|
18
|
-
# otherwise Ruby's Thread#kill will commit. See #377.
|
19
|
-
# DO NOT RESCUE THIS ERROR.
|
20
|
-
class Shutdown < Interrupt; end
|
21
|
-
|
22
15
|
class CLI
|
23
16
|
include Util
|
24
17
|
include Singleton unless $TESTING
|
25
18
|
|
26
|
-
|
27
|
-
|
19
|
+
PROCTITLES = [
|
20
|
+
proc { 'sidekiq' },
|
21
|
+
proc { Sidekiq::VERSION },
|
22
|
+
proc { |me, data| data['tag'] },
|
23
|
+
proc { |me, data| "[#{Processor::WORKER_STATE.size} of #{data['concurrency']} busy]" },
|
24
|
+
proc { |me, data| "stopping" if me.stopping? },
|
25
|
+
]
|
26
|
+
|
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
|
-
|
42
|
-
|
43
|
-
|
34
|
+
end
|
35
|
+
|
36
|
+
def jruby?
|
37
|
+
defined?(::JRUBY_VERSION)
|
44
38
|
end
|
45
39
|
|
46
40
|
# Code within this method is not tested because it alters
|
47
41
|
# global process state irreversibly. PRs which improve the
|
48
42
|
# test coverage of Sidekiq::CLI are welcomed.
|
49
43
|
def run
|
44
|
+
daemonize if options[:daemon]
|
45
|
+
write_pid
|
50
46
|
boot_system
|
51
|
-
print_banner
|
47
|
+
print_banner if environment == 'development' && $stdout.tty?
|
52
48
|
|
53
49
|
self_read, self_write = IO.pipe
|
50
|
+
sigs = %w(INT TERM TTIN TSTP)
|
51
|
+
# USR1 and USR2 don't work on the JVM
|
52
|
+
if !jruby?
|
53
|
+
sigs << 'USR1'
|
54
|
+
sigs << 'USR2'
|
55
|
+
end
|
54
56
|
|
55
|
-
|
57
|
+
sigs.each do |sig|
|
56
58
|
begin
|
57
59
|
trap sig do
|
58
|
-
self_write.
|
60
|
+
self_write.write("#{sig}\n")
|
59
61
|
end
|
60
62
|
rescue ArgumentError
|
61
63
|
puts "Signal #{sig} not supported"
|
@@ -66,22 +68,39 @@ module Sidekiq
|
|
66
68
|
logger.info Sidekiq::LICENSE
|
67
69
|
logger.info "Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org" unless defined?(::Sidekiq::Pro)
|
68
70
|
|
69
|
-
|
71
|
+
# touch the connection pool so it is created before we
|
72
|
+
# fire startup and start multithreading.
|
73
|
+
ver = Sidekiq.redis_info['redis_version']
|
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'
|
70
76
|
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
74
82
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
83
|
+
# cache process identity
|
84
|
+
Sidekiq.options[:identity] = identity
|
85
|
+
|
86
|
+
# Touch middleware so it isn't lazy loaded by multiple threads, #3043
|
87
|
+
Sidekiq.server_middleware
|
79
88
|
|
89
|
+
# Before this point, the process is initializing with just the main thread.
|
90
|
+
# Starting here the process will now have multiple threads running.
|
91
|
+
fire_event(:startup, reverse: false, reraise: true)
|
92
|
+
|
93
|
+
logger.debug { "Client Middleware: #{Sidekiq.client_middleware.map(&:klass).join(', ')}" }
|
94
|
+
logger.debug { "Server Middleware: #{Sidekiq.server_middleware.map(&:klass).join(', ')}" }
|
95
|
+
|
96
|
+
launch(self_read)
|
97
|
+
end
|
98
|
+
|
99
|
+
def launch(self_read)
|
80
100
|
if !options[:daemon]
|
81
101
|
logger.info 'Starting processing, hit Ctrl-C to stop'
|
82
102
|
end
|
83
103
|
|
84
|
-
require 'sidekiq/launcher'
|
85
104
|
@launcher = Sidekiq::Launcher.new(options)
|
86
105
|
|
87
106
|
begin
|
@@ -96,6 +115,7 @@ module Sidekiq
|
|
96
115
|
launcher.stop
|
97
116
|
# Explicitly exit so busy Processor threads can't block
|
98
117
|
# process shutdown.
|
118
|
+
logger.info "Bye!"
|
99
119
|
exit(0)
|
100
120
|
end
|
101
121
|
end
|
@@ -117,70 +137,60 @@ module Sidekiq
|
|
117
137
|
}
|
118
138
|
end
|
119
139
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
when 'TERM'
|
128
|
-
# Heroku sends TERM and then waits 10 seconds for process to exit.
|
129
|
-
raise Interrupt
|
130
|
-
when 'USR1'
|
140
|
+
SIGNAL_HANDLERS = {
|
141
|
+
# Ctrl-C in terminal
|
142
|
+
'INT' => ->(cli) { raise Interrupt },
|
143
|
+
# TERM is the signal that Sidekiq must exit.
|
144
|
+
# Heroku sends TERM and then waits 30 seconds for process to exit.
|
145
|
+
'TERM' => ->(cli) { raise Interrupt },
|
146
|
+
'USR1' => ->(cli) {
|
131
147
|
Sidekiq.logger.info "Received USR1, no longer accepting new work"
|
132
|
-
launcher.
|
133
|
-
|
134
|
-
|
148
|
+
cli.launcher.quiet
|
149
|
+
},
|
150
|
+
'TSTP' => ->(cli) {
|
151
|
+
Sidekiq.logger.info "Received TSTP, no longer accepting new work"
|
152
|
+
cli.launcher.quiet
|
153
|
+
},
|
154
|
+
'USR2' => ->(cli) {
|
135
155
|
if Sidekiq.options[:logfile]
|
136
156
|
Sidekiq.logger.info "Received USR2, reopening log file"
|
137
157
|
Sidekiq::Logging.reopen_logs
|
138
158
|
end
|
139
|
-
|
159
|
+
},
|
160
|
+
'TTIN' => ->(cli) {
|
140
161
|
Thread.list.each do |thread|
|
141
|
-
Sidekiq.logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['
|
162
|
+
Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread['sidekiq_label']}"
|
142
163
|
if thread.backtrace
|
143
164
|
Sidekiq.logger.warn thread.backtrace.join("\n")
|
144
165
|
else
|
145
166
|
Sidekiq.logger.warn "<no backtrace available>"
|
146
167
|
end
|
147
168
|
end
|
169
|
+
},
|
170
|
+
}
|
171
|
+
|
172
|
+
def handle_signal(sig)
|
173
|
+
Sidekiq.logger.debug "Got #{sig} signal"
|
174
|
+
handy = SIGNAL_HANDLERS[sig]
|
175
|
+
if handy
|
176
|
+
handy.call(self)
|
177
|
+
else
|
178
|
+
Sidekiq.logger.info { "No signal handler for #{sig}" }
|
148
179
|
end
|
149
180
|
end
|
150
181
|
|
151
182
|
private
|
152
183
|
|
153
184
|
def print_banner
|
154
|
-
#
|
155
|
-
|
156
|
-
|
157
|
-
puts Sidekiq::CLI.banner
|
158
|
-
puts "\e[0m"
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def load_celluloid
|
163
|
-
raise "Celluloid cannot be required until here, or it will break Sidekiq's daemonization" if defined?(::Celluloid) && options[:daemon]
|
164
|
-
|
165
|
-
# Celluloid can't be loaded until after we've daemonized
|
166
|
-
# because it spins up threads and creates locks which get
|
167
|
-
# into a very bad state if forked.
|
168
|
-
require 'celluloid/current'
|
169
|
-
Celluloid.logger = (options[:verbose] ? Sidekiq.logger : nil)
|
170
|
-
|
171
|
-
require 'sidekiq/manager'
|
172
|
-
require 'sidekiq/scheduled'
|
185
|
+
puts "\e[#{31}m"
|
186
|
+
puts Sidekiq::CLI.banner
|
187
|
+
puts "\e[0m"
|
173
188
|
end
|
174
189
|
|
175
190
|
def daemonize
|
176
|
-
return unless options[:daemon]
|
177
|
-
|
178
191
|
raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
|
179
|
-
files_to_reopen = []
|
180
|
-
ObjectSpace.each_object(File) do |file|
|
181
|
-
files_to_reopen << file unless file.closed?
|
182
|
-
end
|
183
192
|
|
193
|
+
files_to_reopen = ObjectSpace.each_object(File).reject { |f| f.closed? }
|
184
194
|
::Process.daemon(true, true)
|
185
195
|
|
186
196
|
files_to_reopen.each do |file|
|
@@ -206,18 +216,50 @@ module Sidekiq
|
|
206
216
|
@environment = cli_env || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
207
217
|
end
|
208
218
|
|
219
|
+
def symbolize_keys_deep!(hash)
|
220
|
+
hash.keys.each do |k|
|
221
|
+
symkey = k.respond_to?(:to_sym) ? k.to_sym : k
|
222
|
+
hash[symkey] = hash.delete k
|
223
|
+
symbolize_keys_deep! hash[symkey] if hash[symkey].kind_of? Hash
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
209
227
|
alias_method :die, :exit
|
210
228
|
alias_method :☠, :exit
|
211
229
|
|
212
230
|
def setup_options(args)
|
231
|
+
# parse CLI options
|
213
232
|
opts = parse_options(args)
|
233
|
+
|
214
234
|
set_environment opts[:environment]
|
215
235
|
|
216
|
-
|
217
|
-
|
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
|
+
|
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
|
218
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?
|
219
259
|
opts[:strict] = true if opts[:strict].nil?
|
260
|
+
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
|
220
261
|
|
262
|
+
# merge with defaults
|
221
263
|
options.merge!(opts)
|
222
264
|
end
|
223
265
|
|
@@ -228,22 +270,22 @@ module Sidekiq
|
|
228
270
|
def boot_system
|
229
271
|
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment
|
230
272
|
|
231
|
-
raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
|
232
|
-
|
233
273
|
if File.directory?(options[:require])
|
234
274
|
require 'rails'
|
235
275
|
if ::Rails::VERSION::MAJOR < 4
|
236
|
-
|
237
|
-
|
238
|
-
::Rails.application.eager_load!
|
239
|
-
else
|
276
|
+
raise "Sidekiq no longer supports this version of Rails"
|
277
|
+
elsif ::Rails::VERSION::MAJOR == 4
|
240
278
|
# Painful contortions, see 1791 for discussion
|
279
|
+
# No autoloading, we want to force eager load for everything.
|
241
280
|
require File.expand_path("#{options[:require]}/config/application.rb")
|
242
281
|
::Rails::Application.initializer "sidekiq.eager_load" do
|
243
282
|
::Rails.application.config.eager_load = true
|
244
283
|
end
|
245
284
|
require 'sidekiq/rails'
|
246
285
|
require File.expand_path("#{options[:require]}/config/environment.rb")
|
286
|
+
else
|
287
|
+
require 'sidekiq/rails'
|
288
|
+
require File.expand_path("#{options[:require]}/config/environment.rb")
|
247
289
|
end
|
248
290
|
options[:tag] ||= default_tag
|
249
291
|
else
|
@@ -263,12 +305,10 @@ module Sidekiq
|
|
263
305
|
end
|
264
306
|
|
265
307
|
def validate!
|
266
|
-
options[:queues] << 'default' if options[:queues].empty?
|
267
|
-
|
268
308
|
if !File.exist?(options[:require]) ||
|
269
309
|
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
270
310
|
logger.info "=================================================================="
|
271
|
-
logger.info " Please point sidekiq to a Rails
|
311
|
+
logger.info " Please point sidekiq to a Rails 4/5 application or a Ruby file "
|
272
312
|
logger.info " to load your worker classes with -r [DIR|FILE]."
|
273
313
|
logger.info "=================================================================="
|
274
314
|
logger.info @parser
|
@@ -290,6 +330,7 @@ module Sidekiq
|
|
290
330
|
|
291
331
|
o.on '-d', '--daemon', "Daemonize process" do |arg|
|
292
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"
|
293
334
|
end
|
294
335
|
|
295
336
|
o.on '-e', '--environment ENV', "Application environment" do |arg|
|
@@ -300,6 +341,8 @@ module Sidekiq
|
|
300
341
|
opts[:tag] = arg
|
301
342
|
end
|
302
343
|
|
344
|
+
# this index remains here for backwards compatibility but none of the Sidekiq
|
345
|
+
# family use this value anymore. it was used by Pro's original reliable_fetch.
|
303
346
|
o.on '-i', '--index INT', "unique process index on this machine" do |arg|
|
304
347
|
opts[:index] = Integer(arg.match(/\d+/)[0])
|
305
348
|
end
|
@@ -327,10 +370,12 @@ module Sidekiq
|
|
327
370
|
|
328
371
|
o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
|
329
372
|
opts[:logfile] = arg
|
373
|
+
puts "WARNING: Logfile redirection will be removed in Sidekiq 6.0, see #4045. Sidekiq will only log to STDOUT"
|
330
374
|
end
|
331
375
|
|
332
376
|
o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
|
333
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"
|
334
379
|
end
|
335
380
|
|
336
381
|
o.on '-V', '--version', "Print version and exit" do |arg|
|
@@ -344,11 +389,8 @@ module Sidekiq
|
|
344
389
|
logger.info @parser
|
345
390
|
die 1
|
346
391
|
end
|
347
|
-
@parser.parse!(argv)
|
348
392
|
|
349
|
-
|
350
|
-
opts[:config_file] ||= filename if File.exist?(filename)
|
351
|
-
end
|
393
|
+
@parser.parse!(argv)
|
352
394
|
|
353
395
|
opts
|
354
396
|
end
|
@@ -368,16 +410,18 @@ module Sidekiq
|
|
368
410
|
end
|
369
411
|
end
|
370
412
|
|
371
|
-
def parse_config(
|
372
|
-
opts = {}
|
373
|
-
|
374
|
-
|
375
|
-
opts
|
376
|
-
parse_queues(opts, opts.delete(:queues) || [])
|
413
|
+
def parse_config(path)
|
414
|
+
opts = YAML.load(ERB.new(File.read(path)).result) || {}
|
415
|
+
|
416
|
+
if opts.respond_to? :deep_symbolize_keys!
|
417
|
+
opts.deep_symbolize_keys!
|
377
418
|
else
|
378
|
-
|
379
|
-
# can be deployed by cap with just the defaults.
|
419
|
+
symbolize_keys_deep!(opts)
|
380
420
|
end
|
421
|
+
|
422
|
+
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
423
|
+
parse_queues(opts, opts.delete(:queues) || [])
|
424
|
+
|
381
425
|
ns = opts.delete(:namespace)
|
382
426
|
if ns
|
383
427
|
# logger hasn't been initialized yet, puts is all we have.
|
@@ -391,10 +435,10 @@ module Sidekiq
|
|
391
435
|
queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
|
392
436
|
end
|
393
437
|
|
394
|
-
def parse_queue(opts,
|
395
|
-
[
|
396
|
-
|
397
|
-
|
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 }
|
398
442
|
opts[:strict] = false if weight.to_i > 0
|
399
443
|
end
|
400
444
|
end
|
data/lib/sidekiq/client.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'securerandom'
|
2
3
|
require 'sidekiq/middleware/chain'
|
3
4
|
|
@@ -35,10 +36,8 @@ module Sidekiq
|
|
35
36
|
# Sidekiq::Client.new(ConnectionPool.new { Redis.new })
|
36
37
|
#
|
37
38
|
# Generally this is only needed for very large Sidekiq installs processing
|
38
|
-
#
|
39
|
-
#
|
40
|
-
# Some features, like the API, do not support sharding: they are designed to work
|
41
|
-
# against a single Redis instance only.
|
39
|
+
# thousands of jobs per second. I don't recommend sharding unless you
|
40
|
+
# cannot scale any other way (e.g. splitting your app into smaller apps).
|
42
41
|
def initialize(redis_pool=nil)
|
43
42
|
@redis_pool = redis_pool || Thread.current[:sidekiq_via_pool] || Sidekiq.redis_pool
|
44
43
|
end
|
@@ -49,11 +48,18 @@ module Sidekiq
|
|
49
48
|
# queue - the named queue to use, default 'default'
|
50
49
|
# class - the worker class to call, required
|
51
50
|
# args - an array of simple arguments to the perform method, must be JSON-serializable
|
52
|
-
#
|
51
|
+
# at - timestamp to schedule the job (optional), must be Numeric (e.g. Time.now.to_f)
|
52
|
+
# retry - whether to retry this job if it fails, default true or an integer number of retries
|
53
53
|
# backtrace - whether to save any error backtrace, default false
|
54
54
|
#
|
55
|
+
# If class is set to the class name, the jobs' options will be based on Sidekiq's default
|
56
|
+
# worker options. Otherwise, they will be based on the job class's options.
|
57
|
+
#
|
58
|
+
# Any options valid for a worker class's sidekiq_options are also available here.
|
59
|
+
#
|
55
60
|
# All options must be strings, not symbols. NB: because we are serializing to JSON, all
|
56
|
-
# symbols in 'args' will be converted to strings.
|
61
|
+
# symbols in 'args' will be converted to strings. Note that +backtrace: true+ can take quite a bit of
|
62
|
+
# space in Redis; a large volume of failing jobs can start Redis swapping if you aren't careful.
|
57
63
|
#
|
58
64
|
# Returns a unique Job ID. If middleware stops the job, nil will be returned instead.
|
59
65
|
#
|
@@ -71,10 +77,10 @@ module Sidekiq
|
|
71
77
|
end
|
72
78
|
|
73
79
|
##
|
74
|
-
# Push a large number of jobs to Redis.
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
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.
|
78
84
|
#
|
79
85
|
# Takes the same arguments as #push except that args is expected to be
|
80
86
|
# an Array of Arrays. All other keys are duplicated for each job. Each job
|
@@ -84,10 +90,15 @@ module Sidekiq
|
|
84
90
|
# Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less
|
85
91
|
# than the number given if the middleware stopped processing for one or more jobs.
|
86
92
|
def push_bulk(items)
|
93
|
+
arg = items['args'].first
|
94
|
+
return [] unless arg # no jobs to push
|
95
|
+
raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" if !arg.is_a?(Array)
|
96
|
+
|
87
97
|
normed = normalize_item(items)
|
88
98
|
payloads = items['args'].map do |args|
|
89
|
-
|
90
|
-
process_single(items['class'],
|
99
|
+
copy = normed.merge('args' => args, 'jid' => SecureRandom.hex(12), 'enqueued_at' => Time.now.to_f)
|
100
|
+
result = process_single(items['class'], copy)
|
101
|
+
result ? result : nil
|
91
102
|
end.compact
|
92
103
|
|
93
104
|
raw_push(payloads) if !payloads.empty?
|
@@ -104,26 +115,19 @@ module Sidekiq
|
|
104
115
|
# end
|
105
116
|
#
|
106
117
|
# Generally this is only needed for very large Sidekiq installs processing
|
107
|
-
#
|
108
|
-
# you
|
109
|
-
# Some features, like the API, do not support sharding: they are designed to work
|
110
|
-
# against a single Redis instance.
|
118
|
+
# thousands of jobs per second. I do not recommend sharding unless
|
119
|
+
# you cannot scale any other way (e.g. splitting your app into smaller apps).
|
111
120
|
def self.via(pool)
|
112
121
|
raise ArgumentError, "No pool given" if pool.nil?
|
113
|
-
|
122
|
+
current_sidekiq_pool = Thread.current[:sidekiq_via_pool]
|
114
123
|
Thread.current[:sidekiq_via_pool] = pool
|
115
124
|
yield
|
116
125
|
ensure
|
117
|
-
Thread.current[:sidekiq_via_pool] =
|
126
|
+
Thread.current[:sidekiq_via_pool] = current_sidekiq_pool
|
118
127
|
end
|
119
128
|
|
120
129
|
class << self
|
121
130
|
|
122
|
-
# deprecated
|
123
|
-
def default
|
124
|
-
@default ||= new
|
125
|
-
end
|
126
|
-
|
127
131
|
def push(item)
|
128
132
|
new.push(item)
|
129
133
|
end
|
@@ -160,7 +164,7 @@ module Sidekiq
|
|
160
164
|
ts = (int < 1_000_000_000 ? now + int : int)
|
161
165
|
|
162
166
|
item = { 'class' => klass, 'args' => args, 'at' => ts, 'queue' => queue }
|
163
|
-
item.delete('at'
|
167
|
+
item.delete('at') if ts <= now
|
164
168
|
|
165
169
|
klass.client_push(item)
|
166
170
|
end
|
@@ -186,18 +190,18 @@ module Sidekiq
|
|
186
190
|
|
187
191
|
def atomic_push(conn, payloads)
|
188
192
|
if payloads.first['at']
|
189
|
-
conn.zadd('schedule'
|
190
|
-
at = hash.delete('at'
|
193
|
+
conn.zadd('schedule', payloads.map do |hash|
|
194
|
+
at = hash.delete('at').to_s
|
191
195
|
[at, Sidekiq.dump_json(hash)]
|
192
196
|
end)
|
193
197
|
else
|
194
198
|
q = payloads.first['queue']
|
195
199
|
now = Time.now.to_f
|
196
200
|
to_push = payloads.map do |entry|
|
197
|
-
entry['enqueued_at'
|
201
|
+
entry['enqueued_at'] = now
|
198
202
|
Sidekiq.dump_json(entry)
|
199
203
|
end
|
200
|
-
conn.sadd('queues'
|
204
|
+
conn.sadd('queues', q)
|
201
205
|
conn.lpush("queue:#{q}", to_push)
|
202
206
|
end
|
203
207
|
end
|
@@ -211,23 +215,25 @@ module Sidekiq
|
|
211
215
|
end
|
212
216
|
|
213
217
|
def normalize_item(item)
|
214
|
-
raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash) && item.has_key?('class'
|
218
|
+
raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash) && item.has_key?('class') && item.has_key?('args')
|
215
219
|
raise(ArgumentError, "Job args must be an Array") unless item['args'].is_a?(Array)
|
216
|
-
raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item['class'
|
220
|
+
raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item['class'].is_a?(Class) || item['class'].is_a?(String)
|
221
|
+
raise(ArgumentError, "Job 'at' must be a Numeric timestamp") if item.has_key?('at') && !item['at'].is_a?(Numeric)
|
222
|
+
#raise(ArgumentError, "Arguments must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices") unless JSON.load(JSON.dump(item['args'])) == item['args']
|
217
223
|
|
218
|
-
normalized_hash(item['class'
|
224
|
+
normalized_hash(item['class'])
|
219
225
|
.each{ |key, value| item[key] = value if item[key].nil? }
|
220
226
|
|
221
|
-
item['class'
|
222
|
-
item['queue'
|
223
|
-
item['jid'
|
224
|
-
item['created_at'
|
227
|
+
item['class'] = item['class'].to_s
|
228
|
+
item['queue'] = item['queue'].to_s
|
229
|
+
item['jid'] ||= SecureRandom.hex(12)
|
230
|
+
item['created_at'] ||= Time.now.to_f
|
225
231
|
item
|
226
232
|
end
|
227
233
|
|
228
234
|
def normalized_hash(item_class)
|
229
235
|
if item_class.is_a?(Class)
|
230
|
-
raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") if !item_class.respond_to?('get_sidekiq_options'
|
236
|
+
raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") if !item_class.respond_to?('get_sidekiq_options')
|
231
237
|
item_class.get_sidekiq_options
|
232
238
|
else
|
233
239
|
Sidekiq.default_worker_options
|