sidekiq 6.0.0 → 6.0.2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +21 -0
  3. data/6.0-Upgrade.md +3 -1
  4. data/Changes.md +82 -1
  5. data/Ent-Changes.md +6 -0
  6. data/Gemfile.lock +3 -3
  7. data/Pro-Changes.md +9 -1
  8. data/README.md +3 -1
  9. data/bin/sidekiqload +8 -4
  10. data/bin/sidekiqmon +4 -5
  11. data/lib/generators/sidekiq/worker_generator.rb +10 -0
  12. data/lib/sidekiq/api.rb +104 -63
  13. data/lib/sidekiq/cli.rb +18 -16
  14. data/lib/sidekiq/client.rb +8 -2
  15. data/lib/sidekiq/fetch.rb +7 -7
  16. data/lib/sidekiq/job_logger.rb +11 -3
  17. data/lib/sidekiq/job_retry.rb +21 -8
  18. data/lib/sidekiq/launcher.rb +1 -3
  19. data/lib/sidekiq/logger.rb +107 -11
  20. data/lib/sidekiq/middleware/chain.rb +11 -2
  21. data/lib/sidekiq/monitor.rb +1 -16
  22. data/lib/sidekiq/paginator.rb +7 -2
  23. data/lib/sidekiq/processor.rb +17 -19
  24. data/lib/sidekiq/scheduled.rb +13 -12
  25. data/lib/sidekiq/testing.rb +12 -0
  26. data/lib/sidekiq/util.rb +0 -2
  27. data/lib/sidekiq/version.rb +1 -1
  28. data/lib/sidekiq/web/application.rb +8 -13
  29. data/lib/sidekiq/web/helpers.rb +22 -10
  30. data/lib/sidekiq/worker.rb +4 -4
  31. data/lib/sidekiq.rb +8 -0
  32. data/sidekiq.gemspec +1 -1
  33. data/web/assets/javascripts/dashboard.js +2 -2
  34. data/web/assets/stylesheets/application-dark.css +125 -0
  35. data/web/assets/stylesheets/application.css +9 -0
  36. data/web/locales/de.yml +14 -2
  37. data/web/views/_job_info.erb +2 -1
  38. data/web/views/busy.erb +4 -1
  39. data/web/views/dead.erb +2 -2
  40. data/web/views/layout.erb +1 -0
  41. data/web/views/morgue.erb +4 -1
  42. data/web/views/queue.erb +10 -1
  43. data/web/views/retries.erb +4 -1
  44. data/web/views/retry.erb +2 -2
  45. data/web/views/scheduled.erb +4 -1
  46. metadata +5 -4
data/lib/sidekiq/cli.rb CHANGED
@@ -41,9 +41,11 @@ module Sidekiq
41
41
 
42
42
  self_read, self_write = IO.pipe
43
43
  sigs = %w[INT TERM TTIN TSTP]
44
+ # USR1 and USR2 don't work on the JVM
45
+ sigs << "USR2" unless jruby?
44
46
  sigs.each do |sig|
45
47
  trap sig do
46
- self_write.write("#{sig}\n")
48
+ self_write.puts(sig)
47
49
  end
48
50
  rescue ArgumentError
49
51
  puts "Signal #{sig} not supported"
@@ -162,15 +164,12 @@ module Sidekiq
162
164
  end
163
165
  },
164
166
  }
167
+ UNHANDLED_SIGNAL_HANDLER = ->(cli) { Sidekiq.logger.info "No signal handler registered, ignoring" }
168
+ SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER
165
169
 
166
170
  def handle_signal(sig)
167
171
  Sidekiq.logger.debug "Got #{sig} signal"
168
- handy = SIGNAL_HANDLERS[sig]
169
- if handy
170
- handy.call(self)
171
- else
172
- Sidekiq.logger.info { "No signal handler for #{sig}" }
173
- end
172
+ SIGNAL_HANDLERS[sig].call(self)
174
173
  end
175
174
 
176
175
  private
@@ -204,7 +203,7 @@ module Sidekiq
204
203
 
205
204
  # check config file presence
206
205
  if opts[:config_file]
207
- if opts[:config_file] && !File.exist?(opts[:config_file])
206
+ unless File.exist?(opts[:config_file])
208
207
  raise ArgumentError, "No such file #{opts[:config_file]}"
209
208
  end
210
209
  else
@@ -224,7 +223,7 @@ module Sidekiq
224
223
  opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
225
224
 
226
225
  # set defaults
227
- opts[:queues] = Array(opts[:queues]) << "default" if opts[:queues].nil? || opts[:queues].empty?
226
+ opts[:queues] = ["default"] if opts[:queues].nil? || opts[:queues].empty?
228
227
  opts[:strict] = true if opts[:strict].nil?
229
228
  opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
230
229
 
@@ -283,8 +282,13 @@ module Sidekiq
283
282
 
284
283
  def parse_options(argv)
285
284
  opts = {}
285
+ @parser = option_parser(opts)
286
+ @parser.parse!(argv)
287
+ opts
288
+ end
286
289
 
287
- @parser = OptionParser.new { |o|
290
+ def option_parser(opts)
291
+ parser = OptionParser.new { |o|
288
292
  o.on "-c", "--concurrency INT", "processor threads to use" do |arg|
289
293
  opts[:concurrency] = Integer(arg)
290
294
  end
@@ -336,15 +340,13 @@ module Sidekiq
336
340
  end
337
341
  }
338
342
 
339
- @parser.banner = "sidekiq [options]"
340
- @parser.on_tail "-h", "--help", "Show help" do
341
- logger.info @parser
343
+ parser.banner = "sidekiq [options]"
344
+ parser.on_tail "-h", "--help", "Show help" do
345
+ logger.info parser
342
346
  die 1
343
347
  end
344
348
 
345
- @parser.parse!(argv)
346
-
347
- opts
349
+ parser
348
350
  end
349
351
 
350
352
  def initialize_logger
@@ -94,9 +94,14 @@ module Sidekiq
94
94
  return [] unless arg # no jobs to push
95
95
  raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless arg.is_a?(Array)
96
96
 
97
+ at = items.delete("at")
98
+ raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all?(Numeric))
99
+
97
100
  normed = normalize_item(items)
98
- payloads = items["args"].map { |args|
99
- copy = normed.merge("args" => args, "jid" => SecureRandom.hex(12), "enqueued_at" => Time.now.to_f)
101
+ payloads = items["args"].map.with_index { |args, index|
102
+ single_at = at.is_a?(Array) ? at[index] : at
103
+ copy = normed.merge("args" => args, "jid" => SecureRandom.hex(12), "at" => single_at, "enqueued_at" => Time.now.to_f)
104
+
100
105
  result = process_single(items["class"], copy)
101
106
  result || nil
102
107
  }.compact
@@ -218,6 +223,7 @@ module Sidekiq
218
223
  raise(ArgumentError, "Job args must be an Array") unless item["args"].is_a?(Array)
219
224
  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)
220
225
  raise(ArgumentError, "Job 'at' must be a Numeric timestamp") if item.key?("at") && !item["at"].is_a?(Numeric)
226
+ raise(ArgumentError, "Job tags must be an Array") if item["tags"] && !item["tags"].is_a?(Array)
221
227
  # 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']
222
228
 
223
229
  normalized_hash(item["class"])
data/lib/sidekiq/fetch.rb CHANGED
@@ -14,12 +14,12 @@ module Sidekiq
14
14
  end
15
15
 
16
16
  def queue_name
17
- queue.sub(/.*queue:/, "")
17
+ queue.delete_prefix("queue:")
18
18
  end
19
19
 
20
20
  def requeue
21
21
  Sidekiq.redis do |conn|
22
- conn.rpush("queue:#{queue_name}", job)
22
+ conn.rpush(queue, job)
23
23
  end
24
24
  end
25
25
  }
@@ -28,7 +28,7 @@ module Sidekiq
28
28
  @strictly_ordered_queues = !!options[:strict]
29
29
  @queues = options[:queues].map { |q| "queue:#{q}" }
30
30
  if @strictly_ordered_queues
31
- @queues = @queues.uniq
31
+ @queues.uniq!
32
32
  @queues << TIMEOUT
33
33
  end
34
34
  end
@@ -47,7 +47,7 @@ module Sidekiq
47
47
  if @strictly_ordered_queues
48
48
  @queues
49
49
  else
50
- queues = @queues.shuffle.uniq
50
+ queues = @queues.shuffle!.uniq
51
51
  queues << TIMEOUT
52
52
  queues
53
53
  end
@@ -61,14 +61,14 @@ module Sidekiq
61
61
  Sidekiq.logger.debug { "Re-queueing terminated jobs" }
62
62
  jobs_to_requeue = {}
63
63
  inprogress.each do |unit_of_work|
64
- jobs_to_requeue[unit_of_work.queue_name] ||= []
65
- jobs_to_requeue[unit_of_work.queue_name] << unit_of_work.job
64
+ jobs_to_requeue[unit_of_work.queue] ||= []
65
+ jobs_to_requeue[unit_of_work.queue] << unit_of_work.job
66
66
  end
67
67
 
68
68
  Sidekiq.redis do |conn|
69
69
  conn.pipelined do
70
70
  jobs_to_requeue.each do |queue, jobs|
71
- conn.rpush("queue:#{queue}", jobs)
71
+ conn.rpush(queue, jobs)
72
72
  end
73
73
  end
74
74
  end
@@ -23,8 +23,15 @@ module Sidekiq
23
23
  raise
24
24
  end
25
25
 
26
- def with_job_hash_context(job_hash, &block)
27
- @logger.with_context(job_hash_context(job_hash), &block)
26
+ def prepare(job_hash, &block)
27
+ level = job_hash["log_level"]
28
+ if level
29
+ @logger.log_at(level) do
30
+ Sidekiq::Context.with(job_hash_context(job_hash), &block)
31
+ end
32
+ else
33
+ Sidekiq::Context.with(job_hash_context(job_hash), &block)
34
+ end
28
35
  end
29
36
 
30
37
  def job_hash_context(job_hash)
@@ -35,11 +42,12 @@ module Sidekiq
35
42
  jid: job_hash["jid"],
36
43
  }
37
44
  h[:bid] = job_hash["bid"] if job_hash["bid"]
45
+ h[:tags] = job_hash["tags"] if job_hash["tags"]
38
46
  h
39
47
  end
40
48
 
41
49
  def with_elapsed_time_context(start, &block)
42
- @logger.with_context(elapsed_time_context(start), &block)
50
+ Sidekiq::Context.with(elapsed_time_context(start), &block)
43
51
  end
44
52
 
45
53
  def elapsed_time_context(start)
@@ -3,6 +3,9 @@
3
3
  require "sidekiq/scheduled"
4
4
  require "sidekiq/api"
5
5
 
6
+ require "zlib"
7
+ require "base64"
8
+
6
9
  module Sidekiq
7
10
  ##
8
11
  # Automatically retry jobs that fail in Sidekiq.
@@ -71,7 +74,7 @@ module Sidekiq
71
74
  # The global retry handler requires only the barest of data.
72
75
  # We want to be able to retry as much as possible so we don't
73
76
  # require the worker to be instantiated.
74
- def global(msg, queue)
77
+ def global(jobstr, queue)
75
78
  yield
76
79
  rescue Handled => ex
77
80
  raise ex
@@ -82,6 +85,7 @@ module Sidekiq
82
85
  # ignore, will be pushed back onto queue during hard_shutdown
83
86
  raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e)
84
87
 
88
+ msg = Sidekiq.load_json(jobstr)
85
89
  if msg["retry"]
86
90
  attempt_retry(nil, msg, queue, e)
87
91
  else
@@ -103,7 +107,7 @@ module Sidekiq
103
107
  # exception so the global block does not reprocess the error. The
104
108
  # Skip exception is unwrapped within Sidekiq::Processor#process before
105
109
  # calling the handle_exception handlers.
106
- def local(worker, msg, queue)
110
+ def local(worker, jobstr, queue)
107
111
  yield
108
112
  rescue Handled => ex
109
113
  raise ex
@@ -114,6 +118,7 @@ module Sidekiq
114
118
  # ignore, will be pushed back onto queue during hard_shutdown
115
119
  raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e)
116
120
 
121
+ msg = Sidekiq.load_json(jobstr)
117
122
  if msg["retry"].nil?
118
123
  msg["retry"] = worker.class.get_sidekiq_options["retry"]
119
124
  end
@@ -151,12 +156,14 @@ module Sidekiq
151
156
  msg["retry_count"] = 0
152
157
  end
153
158
 
154
- if msg["backtrace"] == true
155
- msg["error_backtrace"] = exception.backtrace
156
- elsif !msg["backtrace"]
157
- # do nothing
158
- elsif msg["backtrace"].to_i != 0
159
- msg["error_backtrace"] = exception.backtrace[0...msg["backtrace"].to_i]
159
+ if msg["backtrace"]
160
+ lines = if msg["backtrace"] == true
161
+ exception.backtrace
162
+ else
163
+ exception.backtrace[0...msg["backtrace"].to_i]
164
+ end
165
+
166
+ msg["error_backtrace"] = compress_backtrace(lines)
160
167
  end
161
168
 
162
169
  if count < max_retry_attempts
@@ -245,5 +252,11 @@ module Sidekiq
245
252
  rescue
246
253
  +"!!! ERROR MESSAGE THREW AN ERROR !!!"
247
254
  end
255
+
256
+ def compress_backtrace(backtrace)
257
+ serialized = Sidekiq.dump_json(backtrace)
258
+ compressed = Zlib::Deflate.deflate(serialized)
259
+ Base64.encode64(compressed)
260
+ end
248
261
  end
249
262
  end
@@ -129,15 +129,13 @@ module Sidekiq
129
129
  fails = procd = 0
130
130
 
131
131
  _, exists, _, _, msg = Sidekiq.redis { |conn|
132
- res = conn.multi {
132
+ conn.multi {
133
133
  conn.sadd("processes", key)
134
134
  conn.exists(key)
135
135
  conn.hmset(key, "info", to_json, "busy", curstate.size, "beat", Time.now.to_f, "quiet", @done)
136
136
  conn.expire(key, 60)
137
137
  conn.rpop("#{key}-signals")
138
138
  }
139
-
140
- res
141
139
  }
142
140
 
143
141
  # first heartbeat or recovering from an outage and need to reestablish our heartbeat
@@ -4,22 +4,109 @@ require "logger"
4
4
  require "time"
5
5
 
6
6
  module Sidekiq
7
- class Logger < ::Logger
8
- def initialize(*args)
9
- super
7
+ module Context
8
+ def self.with(hash)
9
+ current.merge!(hash)
10
+ yield
11
+ ensure
12
+ hash.each_key { |key| current.delete(key) }
13
+ end
10
14
 
11
- self.formatter = Sidekiq.log_formatter
15
+ def self.current
16
+ Thread.current[:sidekiq_context] ||= {}
17
+ end
18
+ end
19
+
20
+ module LoggingUtils
21
+ LEVELS = {
22
+ "debug" => 0,
23
+ "info" => 1,
24
+ "warn" => 2,
25
+ "error" => 3,
26
+ "fatal" => 4,
27
+ }
28
+ LEVELS.default_proc = proc do |_, level|
29
+ Sidekiq.logger.warn("Invalid log level: #{level.inspect}")
30
+ nil
31
+ end
32
+
33
+ def debug?
34
+ level >= 0
35
+ end
36
+
37
+ def info?
38
+ level >= 1
39
+ end
40
+
41
+ def warn?
42
+ level >= 2
43
+ end
44
+
45
+ def error?
46
+ level >= 3
47
+ end
48
+
49
+ def fatal?
50
+ level >= 4
12
51
  end
13
52
 
14
- def with_context(hash)
15
- ctx.merge!(hash)
53
+ def local_level
54
+ Thread.current[:sidekiq_log_level]
55
+ end
56
+
57
+ def local_level=(level)
58
+ case level
59
+ when Integer
60
+ Thread.current[:sidekiq_log_level] = level
61
+ when Symbol, String
62
+ Thread.current[:sidekiq_log_level] = LEVELS[level.to_s]
63
+ when nil
64
+ Thread.current[:sidekiq_log_level] = nil
65
+ else
66
+ raise ArgumentError, "Invalid log level: #{level.inspect}"
67
+ end
68
+ end
69
+
70
+ def level
71
+ local_level || super
72
+ end
73
+
74
+ # Change the thread-local level for the duration of the given block.
75
+ def log_at(level)
76
+ old_local_level = local_level
77
+ self.local_level = level
16
78
  yield
17
79
  ensure
18
- hash.keys.each { |key| ctx.delete(key) }
80
+ self.local_level = old_local_level
19
81
  end
20
82
 
21
- def ctx
22
- Thread.current[:sidekiq_context] ||= {}
83
+ # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
84
+ # FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
85
+ def add(severity, message = nil, progname = nil, &block)
86
+ severity ||= UNKNOWN
87
+ progname ||= @progname
88
+
89
+ return true if @logdev.nil? || severity < level
90
+
91
+ if message.nil?
92
+ if block_given?
93
+ message = yield
94
+ else
95
+ message = progname
96
+ progname = @progname
97
+ end
98
+ end
99
+
100
+ @logdev.write format_message(format_severity(severity), Time.now, progname, message)
101
+ end
102
+ end
103
+
104
+ class Logger < ::Logger
105
+ include LoggingUtils
106
+
107
+ def initialize(*args)
108
+ super
109
+ self.formatter = Sidekiq.log_formatter
23
110
  end
24
111
 
25
112
  module Formatters
@@ -29,11 +116,20 @@ module Sidekiq
29
116
  end
30
117
 
31
118
  def ctx
32
- Thread.current[:sidekiq_context] ||= {}
119
+ Sidekiq::Context.current
33
120
  end
34
121
 
35
122
  def format_context
36
- " " + ctx.compact.map { |k, v| "#{k}=#{v}" }.join(" ") if ctx.any?
123
+ if ctx.any?
124
+ " " + ctx.compact.map { |k, v|
125
+ case v
126
+ when Array
127
+ "#{k}=#{v.join(",")}"
128
+ else
129
+ "#{k}=#{v}"
130
+ end
131
+ }.join(" ")
132
+ end
37
133
  end
38
134
  end
39
135
 
@@ -67,7 +67,6 @@ module Sidekiq
67
67
  module Middleware
68
68
  class Chain
69
69
  include Enumerable
70
- attr_reader :entries
71
70
 
72
71
  def initialize_copy(copy)
73
72
  copy.instance_variable_set(:@entries, entries.dup)
@@ -78,10 +77,14 @@ module Sidekiq
78
77
  end
79
78
 
80
79
  def initialize
81
- @entries = []
80
+ @entries = nil
82
81
  yield self if block_given?
83
82
  end
84
83
 
84
+ def entries
85
+ @entries ||= []
86
+ end
87
+
85
88
  def remove(klass)
86
89
  entries.delete_if { |entry| entry.klass == klass }
87
90
  end
@@ -114,6 +117,10 @@ module Sidekiq
114
117
  any? { |entry| entry.klass == klass }
115
118
  end
116
119
 
120
+ def empty?
121
+ @entries.nil? || @entries.empty?
122
+ end
123
+
117
124
  def retrieve
118
125
  map(&:make_new)
119
126
  end
@@ -123,6 +130,8 @@ module Sidekiq
123
130
  end
124
131
 
125
132
  def invoke(*args)
133
+ return yield if empty?
134
+
126
135
  chain = retrieve.dup
127
136
  traverse_chain = lambda do
128
137
  if chain.empty?
@@ -4,21 +4,6 @@ require "fileutils"
4
4
  require "sidekiq/api"
5
5
 
6
6
  class Sidekiq::Monitor
7
- CMD = File.basename($PROGRAM_NAME)
8
-
9
- attr_reader :stage
10
-
11
- def self.print_usage
12
- puts "#{CMD} - monitor Sidekiq from the command line."
13
- puts
14
- puts "Usage: #{CMD} status <section>"
15
- puts
16
- puts " <section> (optional) view a specific section of the status output"
17
- puts " Valid sections are: #{Sidekiq::Monitor::Status::VALID_SECTIONS.join(", ")}"
18
- puts
19
- puts "Set REDIS_URL to the location of your Redis server if not monitoring localhost."
20
- end
21
-
22
7
  class Status
23
8
  VALID_SECTIONS = %w[all version overview processes queues]
24
9
  COL_PAD = 2
@@ -47,7 +32,7 @@ class Sidekiq::Monitor
47
32
 
48
33
  def version
49
34
  puts "Sidekiq #{Sidekiq::VERSION}"
50
- puts Time.now
35
+ puts Time.now.utc
51
36
  end
52
37
 
53
38
  def overview
@@ -12,10 +12,10 @@ module Sidekiq
12
12
 
13
13
  Sidekiq.redis do |conn|
14
14
  type = conn.type(key)
15
+ rev = opts && opts[:reverse]
15
16
 
16
17
  case type
17
18
  when "zset"
18
- rev = opts && opts[:reverse]
19
19
  total_size, items = conn.multi {
20
20
  conn.zcard(key)
21
21
  if rev
@@ -28,8 +28,13 @@ module Sidekiq
28
28
  when "list"
29
29
  total_size, items = conn.multi {
30
30
  conn.llen(key)
31
- conn.lrange(key, starting, ending)
31
+ if rev
32
+ conn.lrange(key, -ending - 1, -starting - 1)
33
+ else
34
+ conn.lrange(key, starting, ending)
35
+ end
32
36
  }
37
+ items.reverse! if rev
33
38
  [current_page, total_size, items]
34
39
  when "none"
35
40
  [1, 0, []]
@@ -111,16 +111,19 @@ module Sidekiq
111
111
  nil
112
112
  end
113
113
 
114
- def dispatch(job_hash, queue)
114
+ def dispatch(job_hash, queue, jobstr)
115
115
  # since middleware can mutate the job hash
116
- # we clone here so we report the original
116
+ # we need to clone it to report the original
117
117
  # job structure to the Web UI
118
- pristine = cloned(job_hash)
118
+ # or to push back to redis when retrying.
119
+ # To avoid costly and, most of the time, useless cloning here,
120
+ # we pass original String of JSON to respected methods
121
+ # to re-parse it there if we need access to the original, untouched job
119
122
 
120
- @job_logger.with_job_hash_context(job_hash) do
121
- @retrier.global(pristine, queue) do
123
+ @job_logger.prepare(job_hash) do
124
+ @retrier.global(jobstr, queue) do
122
125
  @job_logger.call(job_hash, queue) do
123
- stats(pristine, queue) do
126
+ stats(jobstr, queue) do
124
127
  # Rails 5 requires a Reloader to wrap code execution. In order to
125
128
  # constantize the worker and instantiate an instance, we have to call
126
129
  # the Reloader. It handles code loading, db connection management, etc.
@@ -129,7 +132,7 @@ module Sidekiq
129
132
  klass = constantize(job_hash["class"])
130
133
  worker = klass.new
131
134
  worker.jid = job_hash["jid"]
132
- @retrier.local(worker, pristine, queue) do
135
+ @retrier.local(worker, jobstr, queue) do
133
136
  yield worker
134
137
  end
135
138
  end
@@ -156,9 +159,9 @@ module Sidekiq
156
159
 
157
160
  ack = false
158
161
  begin
159
- dispatch(job_hash, queue) do |worker|
162
+ dispatch(job_hash, queue, jobstr) do |worker|
160
163
  Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
161
- execute_job(worker, cloned(job_hash["args"]))
164
+ execute_job(worker, job_hash["args"])
162
165
  end
163
166
  end
164
167
  ack = true
@@ -247,8 +250,8 @@ module Sidekiq
247
250
  FAILURE = Counter.new
248
251
  WORKER_STATE = SharedWorkerState.new
249
252
 
250
- def stats(job_hash, queue)
251
- WORKER_STATE.set(tid, {queue: queue, payload: job_hash, run_at: Time.now.to_i})
253
+ def stats(jobstr, queue)
254
+ WORKER_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
252
255
 
253
256
  begin
254
257
  yield
@@ -261,21 +264,16 @@ module Sidekiq
261
264
  end
262
265
  end
263
266
 
264
- # Deep clone the arguments passed to the worker so that if
265
- # the job fails, what is pushed back onto Redis hasn't
266
- # been mutated by the worker.
267
- def cloned(thing)
268
- Marshal.load(Marshal.dump(thing))
269
- end
270
-
271
267
  def constantize(str)
268
+ return Object.const_get(str) unless str.include?("::")
269
+
272
270
  names = str.split("::")
273
271
  names.shift if names.empty? || names.first.empty?
274
272
 
275
273
  names.inject(Object) do |constant, name|
276
274
  # the false flag limits search for name to under the constant namespace
277
275
  # which mimics Rails' behaviour
278
- constant.const_defined?(name, false) ? constant.const_get(name, false) : constant.const_missing(name)
276
+ constant.const_get(name, false)
279
277
  end
280
278
  end
281
279
  end
@@ -14,18 +14,19 @@ module Sidekiq
14
14
  # Just check Redis for the set of jobs with a timestamp before now.
15
15
  Sidekiq.redis do |conn|
16
16
  sorted_sets.each do |sorted_set|
17
- # Get the next item in the queue if it's score (time to execute) is <= now.
18
- # We need to go through the list one at a time to reduce the risk of something
19
- # going wrong between the time jobs are popped from the scheduled queue and when
20
- # they are pushed onto a work queue and losing the jobs.
21
- while (job = conn.zrangebyscore(sorted_set, "-inf", now, limit: [0, 1]).first)
22
-
23
- # Pop item off the queue and add it to the work queue. If the job can't be popped from
24
- # the queue, it's because another process already popped it so we can move on to the
25
- # next one.
26
- if conn.zrem(sorted_set, job)
27
- Sidekiq::Client.push(Sidekiq.load_json(job))
28
- Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
17
+ # Get next items in the queue with scores (time to execute) <= now.
18
+ until (jobs = conn.zrangebyscore(sorted_set, "-inf", now, limit: [0, 100])).empty?
19
+ # We need to go through the list one at a time to reduce the risk of something
20
+ # going wrong between the time jobs are popped from the scheduled queue and when
21
+ # they are pushed onto a work queue and losing the jobs.
22
+ jobs.each do |job|
23
+ # Pop item off the queue and add it to the work queue. If the job can't be popped from
24
+ # the queue, it's because another process already popped it so we can move on to the
25
+ # next one.
26
+ if conn.zrem(sorted_set, job)
27
+ Sidekiq::Client.push(Sidekiq.load_json(job))
28
+ Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
29
+ end
29
30
  end
30
31
  end
31
32
  end
@@ -323,6 +323,18 @@ module Sidekiq
323
323
  end
324
324
  end
325
325
  end
326
+
327
+ module TestingExtensions
328
+ def jobs_for(klass)
329
+ jobs.select do |job|
330
+ marshalled = job["args"][0]
331
+ marshalled.index(klass.to_s) && YAML.load(marshalled)[0] == klass
332
+ end
333
+ end
334
+ end
335
+
336
+ Sidekiq::Extensions::DelayedMailer.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedMailer)
337
+ Sidekiq::Extensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedModel)
326
338
  end
327
339
 
328
340
  if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test?
data/lib/sidekiq/util.rb CHANGED
@@ -11,8 +11,6 @@ module Sidekiq
11
11
  module Util
12
12
  include ExceptionHandler
13
13
 
14
- EXPIRY = 60 * 60 * 24
15
-
16
14
  def watchdog(last_words)
17
15
  yield
18
16
  rescue Exception => ex
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "6.0.0"
4
+ VERSION = "6.0.2"
5
5
  end