sidekiq 4.2.4 → 5.2.10
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/.github/issue_template.md +8 -1
- data/.gitignore +3 -0
- data/.travis.yml +5 -6
- data/5.0-Upgrade.md +56 -0
- data/COMM-LICENSE +12 -10
- data/Changes.md +220 -0
- data/Ent-Changes.md +94 -2
- data/Gemfile +12 -22
- data/LICENSE +1 -1
- data/Pro-4.0-Upgrade.md +35 -0
- data/Pro-Changes.md +176 -2
- data/README.md +10 -7
- data/Rakefile +3 -3
- data/bin/sidekiqctl +13 -92
- data/bin/sidekiqload +16 -34
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +1 -1
- data/lib/sidekiq/api.rb +166 -68
- data/lib/sidekiq/cli.rb +122 -77
- data/lib/sidekiq/client.rb +25 -18
- data/lib/sidekiq/core_ext.rb +1 -106
- data/lib/sidekiq/ctl.rb +221 -0
- data/lib/sidekiq/delay.rb +42 -0
- data/lib/sidekiq/exception_handler.rb +2 -4
- data/lib/sidekiq/extensions/generic_proxy.rb +7 -1
- data/lib/sidekiq/fetch.rb +1 -1
- data/lib/sidekiq/job_logger.rb +25 -0
- data/lib/sidekiq/job_retry.rb +262 -0
- data/lib/sidekiq/launcher.rb +49 -40
- data/lib/sidekiq/logging.rb +18 -2
- data/lib/sidekiq/manager.rb +6 -7
- data/lib/sidekiq/middleware/server/active_record.rb +10 -0
- data/lib/sidekiq/processor.rb +127 -37
- data/lib/sidekiq/rails.rb +16 -51
- data/lib/sidekiq/redis_connection.rb +50 -5
- data/lib/sidekiq/scheduled.rb +35 -8
- data/lib/sidekiq/testing.rb +24 -7
- data/lib/sidekiq/util.rb +6 -2
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +3 -7
- data/lib/sidekiq/web/application.rb +38 -22
- data/lib/sidekiq/web/helpers.rb +78 -27
- data/lib/sidekiq/web/router.rb +14 -10
- data/lib/sidekiq/web.rb +4 -4
- data/lib/sidekiq/worker.rb +118 -19
- data/lib/sidekiq.rb +27 -26
- data/sidekiq.gemspec +8 -13
- data/web/assets/javascripts/application.js +0 -0
- data/web/assets/javascripts/dashboard.js +33 -18
- data/web/assets/stylesheets/application-rtl.css +246 -0
- data/web/assets/stylesheets/application.css +371 -6
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +81 -0
- data/web/locales/en.yml +2 -0
- data/web/locales/es.yml +4 -3
- data/web/locales/fa.yml +80 -0
- data/web/locales/he.yml +79 -0
- data/web/locales/ja.yml +5 -3
- data/web/locales/ur.yml +80 -0
- data/web/views/_footer.erb +5 -2
- data/web/views/_job_info.erb +1 -1
- data/web/views/_nav.erb +4 -18
- data/web/views/_paging.erb +1 -1
- data/web/views/busy.erb +9 -5
- data/web/views/dashboard.erb +3 -3
- data/web/views/layout.erb +11 -2
- data/web/views/morgue.erb +14 -10
- data/web/views/queue.erb +11 -10
- data/web/views/queues.erb +4 -2
- data/web/views/retries.erb +17 -11
- data/web/views/retry.erb +1 -1
- data/web/views/scheduled.erb +2 -2
- metadata +32 -151
- data/lib/sidekiq/middleware/server/logging.rb +0 -40
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
- data/test/config.yml +0 -9
- data/test/env_based_config.yml +0 -11
- data/test/fake_env.rb +0 -1
- data/test/fixtures/en.yml +0 -2
- data/test/helper.rb +0 -75
- data/test/test_actors.rb +0 -138
- data/test/test_api.rb +0 -528
- data/test/test_cli.rb +0 -418
- data/test/test_client.rb +0 -266
- data/test/test_exception_handler.rb +0 -56
- data/test/test_extensions.rb +0 -127
- data/test/test_fetch.rb +0 -50
- data/test/test_launcher.rb +0 -95
- data/test/test_logging.rb +0 -35
- data/test/test_manager.rb +0 -50
- data/test/test_middleware.rb +0 -158
- data/test/test_processor.rb +0 -235
- data/test/test_rails.rb +0 -22
- data/test/test_redis_connection.rb +0 -132
- data/test/test_retry.rb +0 -326
- data/test/test_retry_exhausted.rb +0 -149
- data/test/test_scheduled.rb +0 -115
- data/test/test_scheduling.rb +0 -58
- data/test/test_sidekiq.rb +0 -107
- data/test/test_testing.rb +0 -143
- data/test/test_testing_fake.rb +0 -357
- data/test/test_testing_inline.rb +0 -94
- data/test/test_util.rb +0 -13
- data/test/test_web.rb +0 -726
- data/test/test_web_helpers.rb +0 -54
data/lib/sidekiq/cli.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# encoding: utf-8
|
3
2
|
$stdout.sync = true
|
4
3
|
|
5
4
|
require 'yaml'
|
@@ -10,6 +9,7 @@ require 'fileutils'
|
|
10
9
|
|
11
10
|
require 'sidekiq'
|
12
11
|
require 'sidekiq/util'
|
12
|
+
require 'sidekiq/launcher'
|
13
13
|
|
14
14
|
module Sidekiq
|
15
15
|
class CLI
|
@@ -17,45 +17,47 @@ module Sidekiq
|
|
17
17
|
include Singleton unless $TESTING
|
18
18
|
|
19
19
|
PROCTITLES = [
|
20
|
-
proc { 'sidekiq'
|
20
|
+
proc { 'sidekiq' },
|
21
21
|
proc { Sidekiq::VERSION },
|
22
22
|
proc { |me, data| data['tag'] },
|
23
23
|
proc { |me, data| "[#{Processor::WORKER_STATE.size} of #{data['concurrency']} busy]" },
|
24
24
|
proc { |me, data| "stopping" if me.stopping? },
|
25
25
|
]
|
26
26
|
|
27
|
-
# Used for CLI testing
|
28
|
-
attr_accessor :code
|
29
27
|
attr_accessor :launcher
|
30
28
|
attr_accessor :environment
|
31
29
|
|
32
|
-
def
|
33
|
-
@code = nil
|
34
|
-
end
|
35
|
-
|
36
|
-
def parse(args=ARGV)
|
37
|
-
@code = nil
|
38
|
-
|
30
|
+
def parse(args = ARGV)
|
39
31
|
setup_options(args)
|
40
32
|
initialize_logger
|
41
33
|
validate!
|
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"
|
@@ -70,22 +72,35 @@ module Sidekiq
|
|
70
72
|
# fire startup and start multithreading.
|
71
73
|
ver = Sidekiq.redis_info['redis_version']
|
72
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
|
+
|
83
|
+
# cache process identity
|
84
|
+
Sidekiq.options[:identity] = identity
|
73
85
|
|
74
86
|
# Touch middleware so it isn't lazy loaded by multiple threads, #3043
|
75
87
|
Sidekiq.server_middleware
|
76
88
|
|
77
89
|
# Before this point, the process is initializing with just the main thread.
|
78
90
|
# Starting here the process will now have multiple threads running.
|
79
|
-
fire_event(:startup)
|
91
|
+
fire_event(:startup, reverse: false, reraise: true)
|
80
92
|
|
81
93
|
logger.debug { "Client Middleware: #{Sidekiq.client_middleware.map(&:klass).join(', ')}" }
|
82
94
|
logger.debug { "Server Middleware: #{Sidekiq.server_middleware.map(&:klass).join(', ')}" }
|
83
95
|
|
96
|
+
launch(self_read)
|
97
|
+
end
|
98
|
+
|
99
|
+
def launch(self_read)
|
84
100
|
if !options[:daemon]
|
85
101
|
logger.info 'Starting processing, hit Ctrl-C to stop'
|
86
102
|
end
|
87
103
|
|
88
|
-
require 'sidekiq/launcher'
|
89
104
|
@launcher = Sidekiq::Launcher.new(options)
|
90
105
|
|
91
106
|
begin
|
@@ -122,56 +137,60 @@ module Sidekiq
|
|
122
137
|
}
|
123
138
|
end
|
124
139
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
when 'TERM'
|
133
|
-
# Heroku sends TERM and then waits 10 seconds for process to exit.
|
134
|
-
raise Interrupt
|
135
|
-
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) {
|
136
147
|
Sidekiq.logger.info "Received USR1, no longer accepting new work"
|
137
|
-
launcher.quiet
|
138
|
-
|
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) {
|
139
155
|
if Sidekiq.options[:logfile]
|
140
156
|
Sidekiq.logger.info "Received USR2, reopening log file"
|
141
157
|
Sidekiq::Logging.reopen_logs
|
142
158
|
end
|
143
|
-
|
159
|
+
},
|
160
|
+
'TTIN' => ->(cli) {
|
144
161
|
Thread.list.each do |thread|
|
145
|
-
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']}"
|
146
163
|
if thread.backtrace
|
147
164
|
Sidekiq.logger.warn thread.backtrace.join("\n")
|
148
165
|
else
|
149
166
|
Sidekiq.logger.warn "<no backtrace available>"
|
150
167
|
end
|
151
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}" }
|
152
179
|
end
|
153
180
|
end
|
154
181
|
|
155
182
|
private
|
156
183
|
|
157
184
|
def print_banner
|
158
|
-
#
|
159
|
-
|
160
|
-
|
161
|
-
puts Sidekiq::CLI.banner
|
162
|
-
puts "\e[0m"
|
163
|
-
end
|
185
|
+
puts "\e[#{31}m"
|
186
|
+
puts Sidekiq::CLI.banner
|
187
|
+
puts "\e[0m"
|
164
188
|
end
|
165
189
|
|
166
190
|
def daemonize
|
167
|
-
return unless options[:daemon]
|
168
|
-
|
169
191
|
raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
|
170
|
-
files_to_reopen = []
|
171
|
-
ObjectSpace.each_object(File) do |file|
|
172
|
-
files_to_reopen << file unless file.closed?
|
173
|
-
end
|
174
192
|
|
193
|
+
files_to_reopen = ObjectSpace.each_object(File).reject { |f| f.closed? }
|
175
194
|
::Process.daemon(true, true)
|
176
195
|
|
177
196
|
files_to_reopen.each do |file|
|
@@ -197,20 +216,50 @@ module Sidekiq
|
|
197
216
|
@environment = cli_env || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
198
217
|
end
|
199
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
|
+
|
200
227
|
alias_method :die, :exit
|
201
228
|
alias_method :☠, :exit
|
202
229
|
|
203
230
|
def setup_options(args)
|
231
|
+
# parse CLI options
|
204
232
|
opts = parse_options(args)
|
233
|
+
|
205
234
|
set_environment opts[:environment]
|
206
235
|
|
207
|
-
|
208
|
-
|
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
|
253
|
+
|
254
|
+
# parse config file options
|
255
|
+
opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
|
209
256
|
|
257
|
+
# set defaults
|
258
|
+
opts[:queues] = Array(opts[:queues]) << 'default' if opts[:queues].nil? || opts[:queues].empty?
|
210
259
|
opts[:strict] = true if opts[:strict].nil?
|
211
|
-
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if
|
212
|
-
opts[:identity] = identity
|
260
|
+
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
|
213
261
|
|
262
|
+
# merge with defaults
|
214
263
|
options.merge!(opts)
|
215
264
|
end
|
216
265
|
|
@@ -221,16 +270,13 @@ module Sidekiq
|
|
221
270
|
def boot_system
|
222
271
|
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment
|
223
272
|
|
224
|
-
raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
|
225
|
-
|
226
273
|
if File.directory?(options[:require])
|
227
274
|
require 'rails'
|
228
275
|
if ::Rails::VERSION::MAJOR < 4
|
229
|
-
|
230
|
-
require File.expand_path("#{options[:require]}/config/environment.rb")
|
231
|
-
::Rails.application.eager_load!
|
276
|
+
raise "Sidekiq no longer supports this version of Rails"
|
232
277
|
elsif ::Rails::VERSION::MAJOR == 4
|
233
278
|
# Painful contortions, see 1791 for discussion
|
279
|
+
# No autoloading, we want to force eager load for everything.
|
234
280
|
require File.expand_path("#{options[:require]}/config/application.rb")
|
235
281
|
::Rails::Application.initializer "sidekiq.eager_load" do
|
236
282
|
::Rails.application.config.eager_load = true
|
@@ -243,10 +289,7 @@ module Sidekiq
|
|
243
289
|
end
|
244
290
|
options[:tag] ||= default_tag
|
245
291
|
else
|
246
|
-
|
247
|
-
"./#{options[:require]} or /path/to/#{options[:require]}"
|
248
|
-
|
249
|
-
require(options[:require]) || raise(ArgumentError, not_required_message)
|
292
|
+
require options[:require]
|
250
293
|
end
|
251
294
|
end
|
252
295
|
|
@@ -262,12 +305,10 @@ module Sidekiq
|
|
262
305
|
end
|
263
306
|
|
264
307
|
def validate!
|
265
|
-
options[:queues] << 'default' if options[:queues].empty?
|
266
|
-
|
267
308
|
if !File.exist?(options[:require]) ||
|
268
309
|
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
269
310
|
logger.info "=================================================================="
|
270
|
-
logger.info " Please point sidekiq to a Rails
|
311
|
+
logger.info " Please point sidekiq to a Rails 4/5 application or a Ruby file "
|
271
312
|
logger.info " to load your worker classes with -r [DIR|FILE]."
|
272
313
|
logger.info "=================================================================="
|
273
314
|
logger.info @parser
|
@@ -289,6 +330,7 @@ module Sidekiq
|
|
289
330
|
|
290
331
|
o.on '-d', '--daemon', "Daemonize process" do |arg|
|
291
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"
|
292
334
|
end
|
293
335
|
|
294
336
|
o.on '-e', '--environment ENV', "Application environment" do |arg|
|
@@ -299,6 +341,8 @@ module Sidekiq
|
|
299
341
|
opts[:tag] = arg
|
300
342
|
end
|
301
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.
|
302
346
|
o.on '-i', '--index INT', "unique process index on this machine" do |arg|
|
303
347
|
opts[:index] = Integer(arg.match(/\d+/)[0])
|
304
348
|
end
|
@@ -326,10 +370,12 @@ module Sidekiq
|
|
326
370
|
|
327
371
|
o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
|
328
372
|
opts[:logfile] = arg
|
373
|
+
puts "WARNING: Logfile redirection will be removed in Sidekiq 6.0, see #4045. Sidekiq will only log to STDOUT"
|
329
374
|
end
|
330
375
|
|
331
376
|
o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
|
332
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"
|
333
379
|
end
|
334
380
|
|
335
381
|
o.on '-V', '--version', "Print version and exit" do |arg|
|
@@ -343,11 +389,8 @@ module Sidekiq
|
|
343
389
|
logger.info @parser
|
344
390
|
die 1
|
345
391
|
end
|
346
|
-
@parser.parse!(argv)
|
347
392
|
|
348
|
-
|
349
|
-
opts[:config_file] ||= filename if File.exist?(filename)
|
350
|
-
end
|
393
|
+
@parser.parse!(argv)
|
351
394
|
|
352
395
|
opts
|
353
396
|
end
|
@@ -367,16 +410,18 @@ module Sidekiq
|
|
367
410
|
end
|
368
411
|
end
|
369
412
|
|
370
|
-
def parse_config(
|
371
|
-
opts = {}
|
372
|
-
|
373
|
-
|
374
|
-
opts
|
375
|
-
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!
|
376
418
|
else
|
377
|
-
|
378
|
-
# can be deployed by cap with just the defaults.
|
419
|
+
symbolize_keys_deep!(opts)
|
379
420
|
end
|
421
|
+
|
422
|
+
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
423
|
+
parse_queues(opts, opts.delete(:queues) || [])
|
424
|
+
|
380
425
|
ns = opts.delete(:namespace)
|
381
426
|
if ns
|
382
427
|
# logger hasn't been initialized yet, puts is all we have.
|
@@ -390,10 +435,10 @@ module Sidekiq
|
|
390
435
|
queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
|
391
436
|
end
|
392
437
|
|
393
|
-
def parse_queue(opts,
|
394
|
-
[
|
395
|
-
|
396
|
-
|
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 }
|
397
442
|
opts[:strict] = false if weight.to_i > 0
|
398
443
|
end
|
399
444
|
end
|
data/lib/sidekiq/client.rb
CHANGED
@@ -48,9 +48,15 @@ module Sidekiq
|
|
48
48
|
# queue - the named queue to use, default 'default'
|
49
49
|
# class - the worker class to call, required
|
50
50
|
# args - an array of simple arguments to the perform method, must be JSON-serializable
|
51
|
+
# at - timestamp to schedule the job (optional), must be Numeric (e.g. Time.now.to_f)
|
51
52
|
# retry - whether to retry this job if it fails, default true or an integer number of retries
|
52
53
|
# backtrace - whether to save any error backtrace, default false
|
53
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
|
+
#
|
54
60
|
# All options must be strings, not symbols. NB: because we are serializing to JSON, all
|
55
61
|
# symbols in 'args' will be converted to strings. Note that +backtrace: true+ can take quite a bit of
|
56
62
|
# space in Redis; a large volume of failing jobs can start Redis swapping if you aren't careful.
|
@@ -71,9 +77,10 @@ module Sidekiq
|
|
71
77
|
end
|
72
78
|
|
73
79
|
##
|
74
|
-
# Push a large number of jobs to Redis.
|
75
|
-
#
|
76
|
-
#
|
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.
|
77
84
|
#
|
78
85
|
# Takes the same arguments as #push except that args is expected to be
|
79
86
|
# an Array of Arrays. All other keys are duplicated for each job. Each job
|
@@ -113,11 +120,10 @@ module Sidekiq
|
|
113
120
|
def self.via(pool)
|
114
121
|
raise ArgumentError, "No pool given" if pool.nil?
|
115
122
|
current_sidekiq_pool = Thread.current[:sidekiq_via_pool]
|
116
|
-
raise RuntimeError, "Sidekiq::Client.via is not re-entrant" if current_sidekiq_pool && current_sidekiq_pool != pool
|
117
123
|
Thread.current[:sidekiq_via_pool] = pool
|
118
124
|
yield
|
119
125
|
ensure
|
120
|
-
Thread.current[:sidekiq_via_pool] =
|
126
|
+
Thread.current[:sidekiq_via_pool] = current_sidekiq_pool
|
121
127
|
end
|
122
128
|
|
123
129
|
class << self
|
@@ -158,7 +164,7 @@ module Sidekiq
|
|
158
164
|
ts = (int < 1_000_000_000 ? now + int : int)
|
159
165
|
|
160
166
|
item = { 'class' => klass, 'args' => args, 'at' => ts, 'queue' => queue }
|
161
|
-
item.delete('at'
|
167
|
+
item.delete('at') if ts <= now
|
162
168
|
|
163
169
|
klass.client_push(item)
|
164
170
|
end
|
@@ -184,18 +190,18 @@ module Sidekiq
|
|
184
190
|
|
185
191
|
def atomic_push(conn, payloads)
|
186
192
|
if payloads.first['at']
|
187
|
-
conn.zadd('schedule'
|
188
|
-
at = hash.delete('at'
|
193
|
+
conn.zadd('schedule', payloads.map do |hash|
|
194
|
+
at = hash.delete('at').to_s
|
189
195
|
[at, Sidekiq.dump_json(hash)]
|
190
196
|
end)
|
191
197
|
else
|
192
198
|
q = payloads.first['queue']
|
193
199
|
now = Time.now.to_f
|
194
200
|
to_push = payloads.map do |entry|
|
195
|
-
entry['enqueued_at'
|
201
|
+
entry['enqueued_at'] = now
|
196
202
|
Sidekiq.dump_json(entry)
|
197
203
|
end
|
198
|
-
conn.sadd('queues'
|
204
|
+
conn.sadd('queues', q)
|
199
205
|
conn.lpush("queue:#{q}", to_push)
|
200
206
|
end
|
201
207
|
end
|
@@ -209,24 +215,25 @@ module Sidekiq
|
|
209
215
|
end
|
210
216
|
|
211
217
|
def normalize_item(item)
|
212
|
-
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')
|
213
219
|
raise(ArgumentError, "Job args must be an Array") unless item['args'].is_a?(Array)
|
214
|
-
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)
|
215
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']
|
216
223
|
|
217
|
-
normalized_hash(item['class'
|
224
|
+
normalized_hash(item['class'])
|
218
225
|
.each{ |key, value| item[key] = value if item[key].nil? }
|
219
226
|
|
220
|
-
item['class'
|
221
|
-
item['queue'
|
222
|
-
item['jid'
|
223
|
-
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
|
224
231
|
item
|
225
232
|
end
|
226
233
|
|
227
234
|
def normalized_hash(item_class)
|
228
235
|
if item_class.is_a?(Class)
|
229
|
-
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')
|
230
237
|
item_class.get_sidekiq_options
|
231
238
|
else
|
232
239
|
Sidekiq.default_worker_options
|
data/lib/sidekiq/core_ext.rb
CHANGED
@@ -1,106 +1 @@
|
|
1
|
-
|
2
|
-
begin
|
3
|
-
require 'active_support/core_ext/class/attribute'
|
4
|
-
rescue LoadError
|
5
|
-
|
6
|
-
# A dumbed down version of ActiveSupport's
|
7
|
-
# Class#class_attribute helper.
|
8
|
-
class Class
|
9
|
-
def class_attribute(*attrs)
|
10
|
-
instance_writer = true
|
11
|
-
|
12
|
-
attrs.each do |name|
|
13
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
14
|
-
def self.#{name}() nil end
|
15
|
-
def self.#{name}?() !!#{name} end
|
16
|
-
|
17
|
-
def self.#{name}=(val)
|
18
|
-
singleton_class.class_eval do
|
19
|
-
define_method(:#{name}) { val }
|
20
|
-
end
|
21
|
-
|
22
|
-
if singleton_class?
|
23
|
-
class_eval do
|
24
|
-
def #{name}
|
25
|
-
defined?(@#{name}) ? @#{name} : singleton_class.#{name}
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
val
|
30
|
-
end
|
31
|
-
|
32
|
-
def #{name}
|
33
|
-
defined?(@#{name}) ? @#{name} : self.class.#{name}
|
34
|
-
end
|
35
|
-
|
36
|
-
def #{name}?
|
37
|
-
!!#{name}
|
38
|
-
end
|
39
|
-
RUBY
|
40
|
-
|
41
|
-
attr_writer name if instance_writer
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
46
|
-
def singleton_class?
|
47
|
-
ancestors.first != self
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
begin
|
53
|
-
require 'active_support/core_ext/hash/keys'
|
54
|
-
require 'active_support/core_ext/hash/deep_merge'
|
55
|
-
rescue LoadError
|
56
|
-
class Hash
|
57
|
-
def stringify_keys
|
58
|
-
keys.each do |key|
|
59
|
-
self[key.to_s] = delete(key)
|
60
|
-
end
|
61
|
-
self
|
62
|
-
end if !{}.respond_to?(:stringify_keys)
|
63
|
-
|
64
|
-
def symbolize_keys
|
65
|
-
keys.each do |key|
|
66
|
-
self[(key.to_sym rescue key) || key] = delete(key)
|
67
|
-
end
|
68
|
-
self
|
69
|
-
end if !{}.respond_to?(:symbolize_keys)
|
70
|
-
|
71
|
-
def deep_merge(other_hash, &block)
|
72
|
-
dup.deep_merge!(other_hash, &block)
|
73
|
-
end if !{}.respond_to?(:deep_merge)
|
74
|
-
|
75
|
-
def deep_merge!(other_hash, &block)
|
76
|
-
other_hash.each_pair do |k,v|
|
77
|
-
tv = self[k]
|
78
|
-
if tv.is_a?(Hash) && v.is_a?(Hash)
|
79
|
-
self[k] = tv.deep_merge(v, &block)
|
80
|
-
else
|
81
|
-
self[k] = block && tv ? block.call(k, tv, v) : v
|
82
|
-
end
|
83
|
-
end
|
84
|
-
self
|
85
|
-
end if !{}.respond_to?(:deep_merge!)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
begin
|
90
|
-
require 'active_support/core_ext/string/inflections'
|
91
|
-
rescue LoadError
|
92
|
-
class String
|
93
|
-
def constantize
|
94
|
-
names = self.split('::')
|
95
|
-
names.shift if names.empty? || names.first.empty?
|
96
|
-
|
97
|
-
constant = Object
|
98
|
-
names.each do |name|
|
99
|
-
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
100
|
-
end
|
101
|
-
constant
|
102
|
-
end
|
103
|
-
end if !"".respond_to?(:constantize)
|
104
|
-
end
|
105
|
-
|
106
|
-
|
1
|
+
raise "no longer used, will be removed in 5.1"
|