sidekiq 4.2.10 → 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 +3 -1
- data/.gitignore +3 -0
- data/.travis.yml +6 -13
- data/5.0-Upgrade.md +56 -0
- data/COMM-LICENSE +12 -10
- data/Changes.md +177 -1
- data/Ent-Changes.md +67 -2
- data/Gemfile +12 -22
- data/LICENSE +1 -1
- data/Pro-4.0-Upgrade.md +35 -0
- data/Pro-Changes.md +133 -2
- data/README.md +8 -6
- data/Rakefile +2 -5
- data/bin/sidekiqctl +13 -92
- data/bin/sidekiqload +5 -10
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
- data/lib/sidekiq/api.rb +148 -58
- data/lib/sidekiq/cli.rb +120 -81
- data/lib/sidekiq/client.rb +25 -18
- data/lib/sidekiq/core_ext.rb +1 -119
- 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 +20 -20
- data/lib/sidekiq/logging.rb +18 -2
- data/lib/sidekiq/manager.rb +5 -6
- data/lib/sidekiq/middleware/server/active_record.rb +10 -0
- data/lib/sidekiq/processor.rb +126 -48
- data/lib/sidekiq/rails.rb +8 -73
- data/lib/sidekiq/redis_connection.rb +43 -5
- data/lib/sidekiq/scheduled.rb +35 -8
- data/lib/sidekiq/testing.rb +16 -7
- data/lib/sidekiq/util.rb +5 -2
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +3 -7
- data/lib/sidekiq/web/application.rb +37 -17
- data/lib/sidekiq/web/helpers.rb +69 -22
- data/lib/sidekiq/web/router.rb +10 -10
- data/lib/sidekiq/web.rb +4 -4
- data/lib/sidekiq/worker.rb +118 -19
- data/lib/sidekiq.rb +27 -27
- data/sidekiq.gemspec +6 -17
- data/web/assets/javascripts/application.js +0 -0
- data/web/assets/javascripts/dashboard.js +32 -17
- 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 +1 -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/_nav.erb +4 -18
- data/web/views/_paging.erb +1 -1
- data/web/views/busy.erb +9 -5
- data/web/views/dashboard.erb +1 -1
- data/web/views/layout.erb +11 -2
- data/web/views/morgue.erb +4 -4
- data/web/views/queue.erb +8 -7
- data/web/views/queues.erb +2 -0
- data/web/views/retries.erb +9 -5
- data/web/views/scheduled.erb +2 -2
- metadata +30 -159
- data/lib/sidekiq/middleware/server/logging.rb +0 -31
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
data/lib/sidekiq/cli.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
$stdout.sync = true
|
4
3
|
|
@@ -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,60 +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
|
-
|
139
|
-
|
148
|
+
cli.launcher.quiet
|
149
|
+
},
|
150
|
+
'TSTP' => ->(cli) {
|
140
151
|
Sidekiq.logger.info "Received TSTP, no longer accepting new work"
|
141
|
-
launcher.quiet
|
142
|
-
|
152
|
+
cli.launcher.quiet
|
153
|
+
},
|
154
|
+
'USR2' => ->(cli) {
|
143
155
|
if Sidekiq.options[:logfile]
|
144
156
|
Sidekiq.logger.info "Received USR2, reopening log file"
|
145
157
|
Sidekiq::Logging.reopen_logs
|
146
158
|
end
|
147
|
-
|
159
|
+
},
|
160
|
+
'TTIN' => ->(cli) {
|
148
161
|
Thread.list.each do |thread|
|
149
|
-
Sidekiq.logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['sidekiq_label']}"
|
162
|
+
Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread['sidekiq_label']}"
|
150
163
|
if thread.backtrace
|
151
164
|
Sidekiq.logger.warn thread.backtrace.join("\n")
|
152
165
|
else
|
153
166
|
Sidekiq.logger.warn "<no backtrace available>"
|
154
167
|
end
|
155
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}" }
|
156
179
|
end
|
157
180
|
end
|
158
181
|
|
159
182
|
private
|
160
183
|
|
161
184
|
def print_banner
|
162
|
-
#
|
163
|
-
|
164
|
-
|
165
|
-
puts Sidekiq::CLI.banner
|
166
|
-
puts "\e[0m"
|
167
|
-
end
|
185
|
+
puts "\e[#{31}m"
|
186
|
+
puts Sidekiq::CLI.banner
|
187
|
+
puts "\e[0m"
|
168
188
|
end
|
169
189
|
|
170
190
|
def daemonize
|
171
|
-
return unless options[:daemon]
|
172
|
-
|
173
191
|
raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
|
174
|
-
files_to_reopen = []
|
175
|
-
ObjectSpace.each_object(File) do |file|
|
176
|
-
files_to_reopen << file unless file.closed?
|
177
|
-
end
|
178
192
|
|
193
|
+
files_to_reopen = ObjectSpace.each_object(File).reject { |f| f.closed? }
|
179
194
|
::Process.daemon(true, true)
|
180
195
|
|
181
196
|
files_to_reopen.each do |file|
|
@@ -201,20 +216,50 @@ module Sidekiq
|
|
201
216
|
@environment = cli_env || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
202
217
|
end
|
203
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
|
+
|
204
227
|
alias_method :die, :exit
|
205
228
|
alias_method :☠, :exit
|
206
229
|
|
207
230
|
def setup_options(args)
|
231
|
+
# parse CLI options
|
208
232
|
opts = parse_options(args)
|
233
|
+
|
209
234
|
set_environment opts[:environment]
|
210
235
|
|
211
|
-
|
212
|
-
|
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]
|
213
256
|
|
257
|
+
# set defaults
|
258
|
+
opts[:queues] = Array(opts[:queues]) << 'default' if opts[:queues].nil? || opts[:queues].empty?
|
214
259
|
opts[:strict] = true if opts[:strict].nil?
|
215
|
-
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if
|
216
|
-
opts[:identity] = identity
|
260
|
+
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
|
217
261
|
|
262
|
+
# merge with defaults
|
218
263
|
options.merge!(opts)
|
219
264
|
end
|
220
265
|
|
@@ -225,14 +270,10 @@ module Sidekiq
|
|
225
270
|
def boot_system
|
226
271
|
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment
|
227
272
|
|
228
|
-
raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
|
229
|
-
|
230
273
|
if File.directory?(options[:require])
|
231
274
|
require 'rails'
|
232
275
|
if ::Rails::VERSION::MAJOR < 4
|
233
|
-
|
234
|
-
require File.expand_path("#{options[:require]}/config/environment.rb")
|
235
|
-
::Rails.application.eager_load!
|
276
|
+
raise "Sidekiq no longer supports this version of Rails"
|
236
277
|
elsif ::Rails::VERSION::MAJOR == 4
|
237
278
|
# Painful contortions, see 1791 for discussion
|
238
279
|
# No autoloading, we want to force eager load for everything.
|
@@ -243,16 +284,12 @@ module Sidekiq
|
|
243
284
|
require 'sidekiq/rails'
|
244
285
|
require File.expand_path("#{options[:require]}/config/environment.rb")
|
245
286
|
else
|
246
|
-
# Rails 5+ && development mode, use Reloader
|
247
287
|
require 'sidekiq/rails'
|
248
288
|
require File.expand_path("#{options[:require]}/config/environment.rb")
|
249
289
|
end
|
250
290
|
options[:tag] ||= default_tag
|
251
291
|
else
|
252
|
-
|
253
|
-
"./#{options[:require]} or /path/to/#{options[:require]}"
|
254
|
-
|
255
|
-
require(options[:require]) || raise(ArgumentError, not_required_message)
|
292
|
+
require options[:require]
|
256
293
|
end
|
257
294
|
end
|
258
295
|
|
@@ -268,12 +305,10 @@ module Sidekiq
|
|
268
305
|
end
|
269
306
|
|
270
307
|
def validate!
|
271
|
-
options[:queues] << 'default' if options[:queues].empty?
|
272
|
-
|
273
308
|
if !File.exist?(options[:require]) ||
|
274
309
|
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
275
310
|
logger.info "=================================================================="
|
276
|
-
logger.info " Please point sidekiq to a Rails
|
311
|
+
logger.info " Please point sidekiq to a Rails 4/5 application or a Ruby file "
|
277
312
|
logger.info " to load your worker classes with -r [DIR|FILE]."
|
278
313
|
logger.info "=================================================================="
|
279
314
|
logger.info @parser
|
@@ -295,6 +330,7 @@ module Sidekiq
|
|
295
330
|
|
296
331
|
o.on '-d', '--daemon', "Daemonize process" do |arg|
|
297
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"
|
298
334
|
end
|
299
335
|
|
300
336
|
o.on '-e', '--environment ENV', "Application environment" do |arg|
|
@@ -305,6 +341,8 @@ module Sidekiq
|
|
305
341
|
opts[:tag] = arg
|
306
342
|
end
|
307
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.
|
308
346
|
o.on '-i', '--index INT', "unique process index on this machine" do |arg|
|
309
347
|
opts[:index] = Integer(arg.match(/\d+/)[0])
|
310
348
|
end
|
@@ -332,10 +370,12 @@ module Sidekiq
|
|
332
370
|
|
333
371
|
o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
|
334
372
|
opts[:logfile] = arg
|
373
|
+
puts "WARNING: Logfile redirection will be removed in Sidekiq 6.0, see #4045. Sidekiq will only log to STDOUT"
|
335
374
|
end
|
336
375
|
|
337
376
|
o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
|
338
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"
|
339
379
|
end
|
340
380
|
|
341
381
|
o.on '-V', '--version', "Print version and exit" do |arg|
|
@@ -349,11 +389,8 @@ module Sidekiq
|
|
349
389
|
logger.info @parser
|
350
390
|
die 1
|
351
391
|
end
|
352
|
-
@parser.parse!(argv)
|
353
392
|
|
354
|
-
|
355
|
-
opts[:config_file] ||= filename if File.exist?(filename)
|
356
|
-
end
|
393
|
+
@parser.parse!(argv)
|
357
394
|
|
358
395
|
opts
|
359
396
|
end
|
@@ -373,16 +410,18 @@ module Sidekiq
|
|
373
410
|
end
|
374
411
|
end
|
375
412
|
|
376
|
-
def parse_config(
|
377
|
-
opts = {}
|
378
|
-
|
379
|
-
|
380
|
-
opts
|
381
|
-
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!
|
382
418
|
else
|
383
|
-
|
384
|
-
# can be deployed by cap with just the defaults.
|
419
|
+
symbolize_keys_deep!(opts)
|
385
420
|
end
|
421
|
+
|
422
|
+
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
423
|
+
parse_queues(opts, opts.delete(:queues) || [])
|
424
|
+
|
386
425
|
ns = opts.delete(:namespace)
|
387
426
|
if ns
|
388
427
|
# logger hasn't been initialized yet, puts is all we have.
|
@@ -396,10 +435,10 @@ module Sidekiq
|
|
396
435
|
queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
|
397
436
|
end
|
398
437
|
|
399
|
-
def parse_queue(opts,
|
400
|
-
[
|
401
|
-
|
402
|
-
|
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 }
|
403
442
|
opts[:strict] = false if weight.to_i > 0
|
404
443
|
end
|
405
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,119 +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
|
-
|
107
|
-
begin
|
108
|
-
require 'active_support/core_ext/kernel/reporting'
|
109
|
-
rescue LoadError
|
110
|
-
module Kernel
|
111
|
-
module_function
|
112
|
-
def silence_warnings
|
113
|
-
old_verbose, $VERBOSE = $VERBOSE, nil
|
114
|
-
yield
|
115
|
-
ensure
|
116
|
-
$VERBOSE = old_verbose
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
1
|
+
raise "no longer used, will be removed in 5.1"
|