sidekiq 5.0.1 → 5.2.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +61 -0
  3. data/.github/issue_template.md +3 -1
  4. data/.gitignore +2 -0
  5. data/.travis.yml +6 -13
  6. data/COMM-LICENSE +11 -9
  7. data/Changes.md +136 -1
  8. data/Ent-Changes.md +46 -3
  9. data/Gemfile +14 -20
  10. data/LICENSE +1 -1
  11. data/Pro-4.0-Upgrade.md +35 -0
  12. data/Pro-Changes.md +125 -0
  13. data/README.md +5 -3
  14. data/Rakefile +2 -5
  15. data/bin/sidekiqctl +13 -92
  16. data/bin/sidekiqload +2 -2
  17. data/lib/sidekiq.rb +24 -15
  18. data/lib/sidekiq/api.rb +83 -37
  19. data/lib/sidekiq/cli.rb +106 -76
  20. data/lib/sidekiq/client.rb +36 -33
  21. data/lib/sidekiq/ctl.rb +221 -0
  22. data/lib/sidekiq/delay.rb +23 -2
  23. data/lib/sidekiq/exception_handler.rb +2 -4
  24. data/lib/sidekiq/fetch.rb +1 -1
  25. data/lib/sidekiq/job_logger.rb +4 -3
  26. data/lib/sidekiq/job_retry.rb +51 -24
  27. data/lib/sidekiq/launcher.rb +18 -12
  28. data/lib/sidekiq/logging.rb +9 -5
  29. data/lib/sidekiq/manager.rb +5 -6
  30. data/lib/sidekiq/middleware/server/active_record.rb +2 -1
  31. data/lib/sidekiq/processor.rb +85 -48
  32. data/lib/sidekiq/rails.rb +7 -0
  33. data/lib/sidekiq/redis_connection.rb +40 -4
  34. data/lib/sidekiq/scheduled.rb +35 -8
  35. data/lib/sidekiq/testing.rb +4 -4
  36. data/lib/sidekiq/util.rb +5 -1
  37. data/lib/sidekiq/version.rb +1 -1
  38. data/lib/sidekiq/web.rb +4 -4
  39. data/lib/sidekiq/web/action.rb +2 -2
  40. data/lib/sidekiq/web/application.rb +24 -2
  41. data/lib/sidekiq/web/helpers.rb +18 -8
  42. data/lib/sidekiq/web/router.rb +10 -10
  43. data/lib/sidekiq/worker.rb +39 -22
  44. data/sidekiq.gemspec +6 -17
  45. data/web/assets/javascripts/application.js +0 -0
  46. data/web/assets/javascripts/dashboard.js +15 -5
  47. data/web/assets/stylesheets/application.css +35 -2
  48. data/web/assets/stylesheets/bootstrap.css +2 -2
  49. data/web/locales/ar.yml +1 -0
  50. data/web/locales/en.yml +2 -0
  51. data/web/locales/es.yml +4 -3
  52. data/web/locales/ja.yml +5 -3
  53. data/web/views/_footer.erb +3 -0
  54. data/web/views/_nav.erb +3 -17
  55. data/web/views/layout.erb +1 -1
  56. data/web/views/queue.erb +1 -0
  57. data/web/views/queues.erb +2 -0
  58. data/web/views/retries.erb +4 -0
  59. metadata +20 -156
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,30 +17,20 @@ module Sidekiq
17
17
  include Singleton unless $TESTING
18
18
 
19
19
  PROCTITLES = [
20
- proc { 'sidekiq'.freeze },
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 initialize
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
- daemonize
43
- write_pid
44
34
  end
45
35
 
46
36
  def jruby?
@@ -51,8 +41,10 @@ module Sidekiq
51
41
  # global process state irreversibly. PRs which improve the
52
42
  # test coverage of Sidekiq::CLI are welcomed.
53
43
  def run
44
+ daemonize if options[:daemon]
45
+ write_pid
54
46
  boot_system
55
- print_banner
47
+ print_banner if environment == 'development' && $stdout.tty?
56
48
 
57
49
  self_read, self_write = IO.pipe
58
50
  sigs = %w(INT TERM TTIN TSTP)
@@ -65,7 +57,7 @@ module Sidekiq
65
57
  sigs.each do |sig|
66
58
  begin
67
59
  trap sig do
68
- self_write.puts(sig)
60
+ self_write.write("#{sig}\n")
69
61
  end
70
62
  rescue ArgumentError
71
63
  puts "Signal #{sig} not supported"
@@ -80,25 +72,35 @@ module Sidekiq
80
72
  # fire startup and start multithreading.
81
73
  ver = Sidekiq.redis_info['redis_version']
82
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
83
82
 
84
83
  # cache process identity
85
- opts[:identity] = identity
84
+ Sidekiq.options[:identity] = identity
86
85
 
87
86
  # Touch middleware so it isn't lazy loaded by multiple threads, #3043
88
87
  Sidekiq.server_middleware
89
88
 
90
89
  # Before this point, the process is initializing with just the main thread.
91
90
  # Starting here the process will now have multiple threads running.
92
- fire_event(:startup)
91
+ fire_event(:startup, reverse: false, reraise: true)
93
92
 
94
93
  logger.debug { "Client Middleware: #{Sidekiq.client_middleware.map(&:klass).join(', ')}" }
95
94
  logger.debug { "Server Middleware: #{Sidekiq.server_middleware.map(&:klass).join(', ')}" }
96
95
 
96
+ launch(self_read)
97
+ end
98
+
99
+ def launch(self_read)
97
100
  if !options[:daemon]
98
101
  logger.info 'Starting processing, hit Ctrl-C to stop'
99
102
  end
100
103
 
101
- require 'sidekiq/launcher'
102
104
  @launcher = Sidekiq::Launcher.new(options)
103
105
 
104
106
  begin
@@ -135,60 +137,60 @@ module Sidekiq
135
137
  }
136
138
  end
137
139
 
138
- def handle_signal(sig)
139
- Sidekiq.logger.debug "Got #{sig} signal"
140
- case sig
141
- when 'INT'
142
- # Handle Ctrl-C in JRuby like MRI
143
- # http://jira.codehaus.org/browse/JRUBY-4637
144
- raise Interrupt
145
- when 'TERM'
146
- # Heroku sends TERM and then waits 10 seconds for process to exit.
147
- raise Interrupt
148
- 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) {
149
147
  Sidekiq.logger.info "Received USR1, no longer accepting new work"
150
- launcher.quiet
151
- when 'TSTP'
152
- # USR1 is not available on JVM, allow TSTP as an alternate signal
148
+ cli.launcher.quiet
149
+ },
150
+ 'TSTP' => ->(cli) {
153
151
  Sidekiq.logger.info "Received TSTP, no longer accepting new work"
154
- launcher.quiet
155
- when 'USR2'
152
+ cli.launcher.quiet
153
+ },
154
+ 'USR2' => ->(cli) {
156
155
  if Sidekiq.options[:logfile]
157
156
  Sidekiq.logger.info "Received USR2, reopening log file"
158
157
  Sidekiq::Logging.reopen_logs
159
158
  end
160
- when 'TTIN'
159
+ },
160
+ 'TTIN' => ->(cli) {
161
161
  Thread.list.each do |thread|
162
- 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']}"
163
163
  if thread.backtrace
164
164
  Sidekiq.logger.warn thread.backtrace.join("\n")
165
165
  else
166
166
  Sidekiq.logger.warn "<no backtrace available>"
167
167
  end
168
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}" }
169
179
  end
170
180
  end
171
181
 
172
182
  private
173
183
 
174
184
  def print_banner
175
- # Print logo and banner for development
176
- if environment == 'development' && $stdout.tty?
177
- puts "\e[#{31}m"
178
- puts Sidekiq::CLI.banner
179
- puts "\e[0m"
180
- end
185
+ puts "\e[#{31}m"
186
+ puts Sidekiq::CLI.banner
187
+ puts "\e[0m"
181
188
  end
182
189
 
183
190
  def daemonize
184
- return unless options[:daemon]
185
-
186
191
  raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
187
- files_to_reopen = []
188
- ObjectSpace.each_object(File) do |file|
189
- files_to_reopen << file unless file.closed?
190
- end
191
192
 
193
+ files_to_reopen = ObjectSpace.each_object(File).reject { |f| f.closed? }
192
194
  ::Process.daemon(true, true)
193
195
 
194
196
  files_to_reopen.each do |file|
@@ -214,19 +216,50 @@ module Sidekiq
214
216
  @environment = cli_env || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
215
217
  end
216
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
+
217
227
  alias_method :die, :exit
218
228
  alias_method :☠, :exit
219
229
 
220
230
  def setup_options(args)
231
+ # parse CLI options
221
232
  opts = parse_options(args)
233
+
222
234
  set_environment opts[:environment]
223
235
 
224
- cfile = opts[:config_file]
225
- opts = parse_config(cfile).merge(opts) if cfile
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
226
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?
227
259
  opts[:strict] = true if opts[:strict].nil?
228
- opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if !opts[:concurrency] && ENV["RAILS_MAX_THREADS"]
260
+ opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
229
261
 
262
+ # merge with defaults
230
263
  options.merge!(opts)
231
264
  end
232
265
 
@@ -237,8 +270,6 @@ module Sidekiq
237
270
  def boot_system
238
271
  ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment
239
272
 
240
- raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
241
-
242
273
  if File.directory?(options[:require])
243
274
  require 'rails'
244
275
  if ::Rails::VERSION::MAJOR < 4
@@ -258,10 +289,7 @@ module Sidekiq
258
289
  end
259
290
  options[:tag] ||= default_tag
260
291
  else
261
- not_required_message = "#{options[:require]} was not required, you should use an explicit path: " +
262
- "./#{options[:require]} or /path/to/#{options[:require]}"
263
-
264
- require(options[:require]) || raise(ArgumentError, not_required_message)
292
+ require options[:require]
265
293
  end
266
294
  end
267
295
 
@@ -277,12 +305,10 @@ module Sidekiq
277
305
  end
278
306
 
279
307
  def validate!
280
- options[:queues] << 'default' if options[:queues].empty?
281
-
282
308
  if !File.exist?(options[:require]) ||
283
309
  (File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
284
310
  logger.info "=================================================================="
285
- logger.info " Please point sidekiq to a Rails 3/4 application or a Ruby file "
311
+ logger.info " Please point sidekiq to a Rails 4/5 application or a Ruby file "
286
312
  logger.info " to load your worker classes with -r [DIR|FILE]."
287
313
  logger.info "=================================================================="
288
314
  logger.info @parser
@@ -304,6 +330,7 @@ module Sidekiq
304
330
 
305
331
  o.on '-d', '--daemon', "Daemonize process" do |arg|
306
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"
307
334
  end
308
335
 
309
336
  o.on '-e', '--environment ENV', "Application environment" do |arg|
@@ -314,6 +341,8 @@ module Sidekiq
314
341
  opts[:tag] = arg
315
342
  end
316
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.
317
346
  o.on '-i', '--index INT', "unique process index on this machine" do |arg|
318
347
  opts[:index] = Integer(arg.match(/\d+/)[0])
319
348
  end
@@ -341,10 +370,12 @@ module Sidekiq
341
370
 
342
371
  o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
343
372
  opts[:logfile] = arg
373
+ puts "WARNING: Logfile redirection will be removed in Sidekiq 6.0, see #4045. Sidekiq will only log to STDOUT"
344
374
  end
345
375
 
346
376
  o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
347
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"
348
379
  end
349
380
 
350
381
  o.on '-V', '--version', "Print version and exit" do |arg|
@@ -358,11 +389,8 @@ module Sidekiq
358
389
  logger.info @parser
359
390
  die 1
360
391
  end
361
- @parser.parse!(argv)
362
392
 
363
- %w[config/sidekiq.yml config/sidekiq.yml.erb].each do |filename|
364
- opts[:config_file] ||= filename if File.exist?(filename)
365
- end
393
+ @parser.parse!(argv)
366
394
 
367
395
  opts
368
396
  end
@@ -382,16 +410,18 @@ module Sidekiq
382
410
  end
383
411
  end
384
412
 
385
- def parse_config(cfile)
386
- opts = {}
387
- if File.exist?(cfile)
388
- opts = YAML.load(ERB.new(IO.read(cfile)).result) || opts
389
- opts = opts.merge(opts.delete(environment) || {})
390
- 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!
391
418
  else
392
- # allow a non-existent config file so Sidekiq
393
- # can be deployed by cap with just the defaults.
419
+ symbolize_keys_deep!(opts)
394
420
  end
421
+
422
+ opts = opts.merge(opts.delete(environment.to_sym) || {})
423
+ parse_queues(opts, opts.delete(:queues) || [])
424
+
395
425
  ns = opts.delete(:namespace)
396
426
  if ns
397
427
  # logger hasn't been initialized yet, puts is all we have.
@@ -405,10 +435,10 @@ module Sidekiq
405
435
  queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
406
436
  end
407
437
 
408
- def parse_queue(opts, q, weight=nil)
409
- [weight.to_i, 1].max.times do
410
- (opts[:queues] ||= []) << q
411
- end
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 }
412
442
  opts[:strict] = false if weight.to_i > 0
413
443
  end
414
444
  end
@@ -51,7 +51,10 @@ module Sidekiq
51
51
  # at - timestamp to schedule the job (optional), must be Numeric (e.g. Time.now.to_f)
52
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
+ #
55
58
  # Any options valid for a worker class's sidekiq_options are also available here.
56
59
  #
57
60
  # All options must be strings, not symbols. NB: because we are serializing to JSON, all
@@ -65,18 +68,19 @@ module Sidekiq
65
68
  #
66
69
  def push(item)
67
70
  normed = normalize_item(item)
68
- payload = process_single(item['class'.freeze], normed)
71
+ payload = process_single(item['class'], normed)
69
72
 
70
73
  if payload
71
74
  raw_push([payload])
72
- payload['jid'.freeze]
75
+ payload['jid']
73
76
  end
74
77
  end
75
78
 
76
79
  ##
77
- # Push a large number of jobs to Redis. In practice this method is only
78
- # useful if you are pushing thousands of jobs or more. This method
79
- # cuts out the redis network round trip latency.
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.
80
84
  #
81
85
  # Takes the same arguments as #push except that args is expected to be
82
86
  # an Array of Arrays. All other keys are duplicated for each job. Each job
@@ -86,19 +90,19 @@ module Sidekiq
86
90
  # Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less
87
91
  # than the number given if the middleware stopped processing for one or more jobs.
88
92
  def push_bulk(items)
89
- arg = items['args'.freeze].first
93
+ arg = items['args'].first
90
94
  return [] unless arg # no jobs to push
91
95
  raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" if !arg.is_a?(Array)
92
96
 
93
97
  normed = normalize_item(items)
94
- payloads = items['args'.freeze].map do |args|
95
- copy = normed.merge('args'.freeze => args, 'jid'.freeze => SecureRandom.hex(12), 'enqueued_at'.freeze => Time.now.to_f)
96
- result = process_single(items['class'.freeze], copy)
98
+ payloads = items['args'].map do |args|
99
+ copy = normed.merge('args' => args, 'jid' => SecureRandom.hex(12), 'enqueued_at' => Time.now.to_f)
100
+ result = process_single(items['class'], copy)
97
101
  result ? result : nil
98
102
  end.compact
99
103
 
100
104
  raw_push(payloads) if !payloads.empty?
101
- payloads.collect { |payload| payload['jid'.freeze] }
105
+ payloads.collect { |payload| payload['jid'] }
102
106
  end
103
107
 
104
108
  # Allows sharding of jobs across any number of Redis instances. All jobs
@@ -116,11 +120,10 @@ module Sidekiq
116
120
  def self.via(pool)
117
121
  raise ArgumentError, "No pool given" if pool.nil?
118
122
  current_sidekiq_pool = Thread.current[:sidekiq_via_pool]
119
- raise RuntimeError, "Sidekiq::Client.via is not re-entrant" if current_sidekiq_pool && current_sidekiq_pool != pool
120
123
  Thread.current[:sidekiq_via_pool] = pool
121
124
  yield
122
125
  ensure
123
- Thread.current[:sidekiq_via_pool] = nil
126
+ Thread.current[:sidekiq_via_pool] = current_sidekiq_pool
124
127
  end
125
128
 
126
129
  class << self
@@ -142,14 +145,14 @@ module Sidekiq
142
145
  # Messages are enqueued to the 'default' queue.
143
146
  #
144
147
  def enqueue(klass, *args)
145
- klass.client_push('class'.freeze => klass, 'args'.freeze => args)
148
+ klass.client_push('class' => klass, 'args' => args)
146
149
  end
147
150
 
148
151
  # Example usage:
149
152
  # Sidekiq::Client.enqueue_to(:queue_name, MyWorker, 'foo', 1, :bat => 'bar')
150
153
  #
151
154
  def enqueue_to(queue, klass, *args)
152
- klass.client_push('queue'.freeze => queue, 'class'.freeze => klass, 'args'.freeze => args)
155
+ klass.client_push('queue' => queue, 'class' => klass, 'args' => args)
153
156
  end
154
157
 
155
158
  # Example usage:
@@ -160,8 +163,8 @@ module Sidekiq
160
163
  now = Time.now.to_f
161
164
  ts = (int < 1_000_000_000 ? now + int : int)
162
165
 
163
- item = { 'class'.freeze => klass, 'args'.freeze => args, 'at'.freeze => ts, 'queue'.freeze => queue }
164
- item.delete('at'.freeze) if ts <= now
166
+ item = { 'class' => klass, 'args' => args, 'at' => ts, 'queue' => queue }
167
+ item.delete('at') if ts <= now
165
168
 
166
169
  klass.client_push(item)
167
170
  end
@@ -186,25 +189,25 @@ module Sidekiq
186
189
  end
187
190
 
188
191
  def atomic_push(conn, payloads)
189
- if payloads.first['at'.freeze]
190
- conn.zadd('schedule'.freeze, payloads.map do |hash|
191
- at = hash.delete('at'.freeze).to_s
192
+ if payloads.first['at']
193
+ conn.zadd('schedule', payloads.map do |hash|
194
+ at = hash.delete('at').to_s
192
195
  [at, Sidekiq.dump_json(hash)]
193
196
  end)
194
197
  else
195
- q = payloads.first['queue'.freeze]
198
+ q = payloads.first['queue']
196
199
  now = Time.now.to_f
197
200
  to_push = payloads.map do |entry|
198
- entry['enqueued_at'.freeze] = now
201
+ entry['enqueued_at'] = now
199
202
  Sidekiq.dump_json(entry)
200
203
  end
201
- conn.sadd('queues'.freeze, q)
204
+ conn.sadd('queues', q)
202
205
  conn.lpush("queue:#{q}", to_push)
203
206
  end
204
207
  end
205
208
 
206
209
  def process_single(worker_class, item)
207
- queue = item['queue'.freeze]
210
+ queue = item['queue']
208
211
 
209
212
  middleware.invoke(worker_class, item, queue, @redis_pool) do
210
213
  item
@@ -212,25 +215,25 @@ module Sidekiq
212
215
  end
213
216
 
214
217
  def normalize_item(item)
215
- 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'.freeze) && item.has_key?('args'.freeze)
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')
216
219
  raise(ArgumentError, "Job args must be an Array") unless item['args'].is_a?(Array)
217
- raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item['class'.freeze].is_a?(Class) || item['class'.freeze].is_a?(String)
218
- raise(ArgumentError, "Job 'at' must be a Numeric timestamp") if item.has_key?('at'.freeze) && !item['at'].is_a?(Numeric)
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)
219
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']
220
223
 
221
- normalized_hash(item['class'.freeze])
224
+ normalized_hash(item['class'])
222
225
  .each{ |key, value| item[key] = value if item[key].nil? }
223
226
 
224
- item['class'.freeze] = item['class'.freeze].to_s
225
- item['queue'.freeze] = item['queue'.freeze].to_s
226
- item['jid'.freeze] ||= SecureRandom.hex(12)
227
- item['created_at'.freeze] ||= Time.now.to_f
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
228
231
  item
229
232
  end
230
233
 
231
234
  def normalized_hash(item_class)
232
235
  if item_class.is_a?(Class)
233
- raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") if !item_class.respond_to?('get_sidekiq_options'.freeze)
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')
234
237
  item_class.get_sidekiq_options
235
238
  else
236
239
  Sidekiq.default_worker_options