sidekiq 6.0.0 → 6.0.4

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 (50) 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 +97 -1
  5. data/Ent-Changes.md +7 -1
  6. data/Gemfile +1 -1
  7. data/Gemfile.lock +76 -73
  8. data/Pro-Changes.md +9 -1
  9. data/README.md +3 -1
  10. data/bin/sidekiqload +8 -4
  11. data/bin/sidekiqmon +4 -5
  12. data/lib/generators/sidekiq/worker_generator.rb +11 -1
  13. data/lib/sidekiq/api.rb +122 -89
  14. data/lib/sidekiq/cli.rb +24 -17
  15. data/lib/sidekiq/client.rb +18 -4
  16. data/lib/sidekiq/fetch.rb +7 -7
  17. data/lib/sidekiq/job_logger.rb +11 -3
  18. data/lib/sidekiq/job_retry.rb +23 -10
  19. data/lib/sidekiq/launcher.rb +1 -3
  20. data/lib/sidekiq/logger.rb +107 -11
  21. data/lib/sidekiq/middleware/chain.rb +11 -2
  22. data/lib/sidekiq/monitor.rb +1 -16
  23. data/lib/sidekiq/paginator.rb +7 -2
  24. data/lib/sidekiq/processor.rb +17 -19
  25. data/lib/sidekiq/redis_connection.rb +3 -0
  26. data/lib/sidekiq/scheduled.rb +13 -12
  27. data/lib/sidekiq/testing.rb +12 -0
  28. data/lib/sidekiq/util.rb +0 -2
  29. data/lib/sidekiq/version.rb +1 -1
  30. data/lib/sidekiq/web/application.rb +17 -14
  31. data/lib/sidekiq/web/helpers.rb +22 -10
  32. data/lib/sidekiq/worker.rb +4 -4
  33. data/lib/sidekiq.rb +12 -0
  34. data/sidekiq.gemspec +1 -1
  35. data/web/assets/javascripts/dashboard.js +2 -2
  36. data/web/assets/stylesheets/application-dark.css +125 -0
  37. data/web/assets/stylesheets/application.css +9 -0
  38. data/web/locales/de.yml +14 -2
  39. data/web/locales/en.yml +2 -0
  40. data/web/views/_job_info.erb +2 -1
  41. data/web/views/busy.erb +4 -1
  42. data/web/views/dead.erb +2 -2
  43. data/web/views/layout.erb +1 -0
  44. data/web/views/morgue.erb +4 -1
  45. data/web/views/queue.erb +10 -1
  46. data/web/views/queues.erb +8 -0
  47. data/web/views/retries.erb +4 -1
  48. data/web/views/retry.erb +2 -2
  49. data/web/views/scheduled.erb +4 -1
  50. metadata +5 -4
@@ -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
@@ -182,13 +189,13 @@ module Sidekiq
182
189
  handle_exception(e, {context: "Error calling retries_exhausted", job: msg})
183
190
  end
184
191
 
192
+ send_to_morgue(msg) unless msg["dead"] == false
193
+
185
194
  Sidekiq.death_handlers.each do |handler|
186
195
  handler.call(msg, exception)
187
196
  rescue => e
188
197
  handle_exception(e, {context: "Error calling death handler", job: msg})
189
198
  end
190
-
191
- send_to_morgue(msg) unless msg["dead"] == false
192
199
  end
193
200
 
194
201
  def send_to_morgue(msg)
@@ -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
@@ -103,6 +103,9 @@ module Sidekiq
103
103
  if scrubbed_options[:password]
104
104
  scrubbed_options[:password] = redacted
105
105
  end
106
+ scrubbed_options[:sentinels]&.each do |sentinel|
107
+ sentinel[:password] = redacted if sentinel[:password]
108
+ end
106
109
  if Sidekiq.server?
107
110
  Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with redis options #{scrubbed_options}")
108
111
  else
@@ -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.4"
5
5
  end
@@ -5,7 +5,6 @@ module Sidekiq
5
5
  extend WebRouter
6
6
 
7
7
  CONTENT_LENGTH = "Content-Length"
8
- CONTENT_TYPE = "Content-Type"
9
8
  REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
10
9
  CSP_HEADER = [
11
10
  "default-src 'self' https: http:",
@@ -84,14 +83,22 @@ module Sidekiq
84
83
 
85
84
  @count = (params["count"] || 25).to_i
86
85
  @queue = Sidekiq::Queue.new(@name)
87
- (@current_page, @total_size, @messages) = page("queue:#{@name}", params["page"], @count)
86
+ (@current_page, @total_size, @messages) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
88
87
  @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
89
88
 
90
89
  erb(:queue)
91
90
  end
92
91
 
93
92
  post "/queues/:name" do
94
- Sidekiq::Queue.new(route_params[:name]).clear
93
+ queue = Sidekiq::Queue.new(route_params[:name])
94
+
95
+ if Sidekiq.pro? && params["pause"]
96
+ queue.pause!
97
+ elsif Sidekiq.pro? && params["unpause"]
98
+ queue.unpause!
99
+ else
100
+ queue.clear
101
+ end
95
102
 
96
103
  redirect "#{root_path}queues"
97
104
  end
@@ -283,17 +290,13 @@ module Sidekiq
283
290
  action = self.class.match(env)
284
291
  return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless action
285
292
 
286
- resp = catch(:halt) {
287
- app = @klass
293
+ app = @klass
294
+ resp = catch(:halt) do # rubocop:disable Standard/SemanticBlocks
288
295
  self.class.run_befores(app, action)
289
- begin
290
- resp = action.instance_exec env, &action.block
291
- ensure
292
- self.class.run_afters(app, action)
293
- end
294
-
295
- resp
296
- }
296
+ action.instance_exec env, &action.block
297
+ ensure
298
+ self.class.run_afters(app, action)
299
+ end
297
300
 
298
301
  resp = case resp
299
302
  when Array
@@ -311,7 +314,7 @@ module Sidekiq
311
314
 
312
315
  resp[1] = resp[1].dup
313
316
 
314
- resp[1][CONTENT_LENGTH] = resp[2].inject(0) { |l, p| l + p.bytesize }.to_s
317
+ resp[1][CONTENT_LENGTH] = resp[2].sum(&:bytesize).to_s
315
318
 
316
319
  resp
317
320
  end
@@ -65,7 +65,10 @@ module Sidekiq
65
65
 
66
66
  def poll_path
67
67
  if current_path != "" && params["poll"]
68
- root_path + current_path
68
+ path = root_path + current_path
69
+ query_string = to_query_string(params.slice(*params.keys - %w[page poll]))
70
+ path += "?#{query_string}" unless query_string.empty?
71
+ path
69
72
  else
70
73
  ""
71
74
  end
@@ -112,6 +115,13 @@ module Sidekiq
112
115
  end
113
116
  end
114
117
 
118
+ # within is used by Sidekiq Pro
119
+ def display_tags(job, within = nil)
120
+ job.tags.map { |tag|
121
+ "<span class='jobtag label label-info'>#{::Rack::Utils.escape_html(tag)}</span>"
122
+ }.join(" ")
123
+ end
124
+
115
125
  # mperham/sidekiq#3243
116
126
  def unfiltered?
117
127
  yield unless env["PATH_INFO"].start_with?("/filter/")
@@ -130,6 +140,10 @@ module Sidekiq
130
140
  end
131
141
  end
132
142
 
143
+ def sort_direction_label
144
+ params[:direction] == "asc" ? "&uarr;" : "&darr;"
145
+ end
146
+
133
147
  def workers
134
148
  @workers ||= Sidekiq::Workers.new
135
149
  end
@@ -142,12 +156,6 @@ module Sidekiq
142
156
  @stats ||= Sidekiq::Stats.new
143
157
  end
144
158
 
145
- def retries_with_score(score)
146
- Sidekiq.redis { |conn|
147
- conn.zrangebyscore("retry", score, score)
148
- }.map { |msg| Sidekiq.load_json(msg) }
149
- end
150
-
151
159
  def redis_connection
152
160
  Sidekiq.redis do |conn|
153
161
  c = conn.connection
@@ -189,7 +197,7 @@ module Sidekiq
189
197
  [score.to_f, jid]
190
198
  end
191
199
 
192
- SAFE_QPARAMS = %w[page poll]
200
+ SAFE_QPARAMS = %w[page poll direction]
193
201
 
194
202
  # Merge options with current params, filter safe params, and stringify to query string
195
203
  def qparams(options)
@@ -198,7 +206,11 @@ module Sidekiq
198
206
  options[key.to_s] = options.delete(key)
199
207
  end
200
208
 
201
- params.merge(options).map { |key, value|
209
+ to_query_string(params.merge(options))
210
+ end
211
+
212
+ def to_query_string(params)
213
+ params.map { |key, value|
202
214
  SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
203
215
  }.compact.join("&")
204
216
  end
@@ -238,7 +250,7 @@ module Sidekiq
238
250
  queue class args retry_count retried_at failed_at
239
251
  jid error_message error_class backtrace
240
252
  error_backtrace enqueued_at retry wrapped
241
- created_at
253
+ created_at tags
242
254
  ])
243
255
 
244
256
  def retry_extra_items(retry_job)
@@ -171,9 +171,9 @@ module Sidekiq
171
171
  now = Time.now.to_f
172
172
  ts = (int < 1_000_000_000 ? now + int : int)
173
173
 
174
- payload = @opts.merge("class" => @klass, "args" => args, "at" => ts)
174
+ payload = @opts.merge("class" => @klass, "args" => args)
175
175
  # Optimization to enqueue something now that is scheduled to go out now or in the past
176
- payload.delete("at") if ts <= now
176
+ payload["at"] = ts if ts > now
177
177
  @klass.client_push(payload)
178
178
  end
179
179
  alias_method :perform_at, :perform_in
@@ -207,10 +207,10 @@ module Sidekiq
207
207
  now = Time.now.to_f
208
208
  ts = (int < 1_000_000_000 ? now + int : int)
209
209
 
210
- item = {"class" => self, "args" => args, "at" => ts}
210
+ item = {"class" => self, "args" => args}
211
211
 
212
212
  # Optimization to enqueue something now that is scheduled to go out now or in the past
213
- item.delete("at") if ts <= now
213
+ item["at"] = ts if ts > now
214
214
 
215
215
  client_push(item)
216
216
  end