sidekiq 6.4.1 → 6.5.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +107 -1
  3. data/README.md +1 -1
  4. data/bin/sidekiqload +18 -12
  5. data/lib/sidekiq/api.rb +222 -71
  6. data/lib/sidekiq/cli.rb +51 -37
  7. data/lib/sidekiq/client.rb +27 -28
  8. data/lib/sidekiq/component.rb +65 -0
  9. data/lib/sidekiq/delay.rb +1 -1
  10. data/lib/sidekiq/extensions/generic_proxy.rb +1 -1
  11. data/lib/sidekiq/fetch.rb +18 -16
  12. data/lib/sidekiq/job_retry.rb +73 -52
  13. data/lib/sidekiq/job_util.rb +15 -9
  14. data/lib/sidekiq/launcher.rb +37 -33
  15. data/lib/sidekiq/logger.rb +5 -19
  16. data/lib/sidekiq/manager.rb +28 -25
  17. data/lib/sidekiq/metrics/deploy.rb +47 -0
  18. data/lib/sidekiq/metrics/query.rb +153 -0
  19. data/lib/sidekiq/metrics/shared.rb +94 -0
  20. data/lib/sidekiq/metrics/tracking.rb +134 -0
  21. data/lib/sidekiq/middleware/chain.rb +82 -38
  22. data/lib/sidekiq/middleware/current_attributes.rb +18 -12
  23. data/lib/sidekiq/middleware/i18n.rb +6 -4
  24. data/lib/sidekiq/middleware/modules.rb +21 -0
  25. data/lib/sidekiq/monitor.rb +2 -2
  26. data/lib/sidekiq/paginator.rb +11 -3
  27. data/lib/sidekiq/processor.rb +47 -41
  28. data/lib/sidekiq/rails.rb +25 -8
  29. data/lib/sidekiq/redis_client_adapter.rb +154 -0
  30. data/lib/sidekiq/redis_connection.rb +80 -49
  31. data/lib/sidekiq/ring_buffer.rb +29 -0
  32. data/lib/sidekiq/scheduled.rb +53 -24
  33. data/lib/sidekiq/testing/inline.rb +4 -4
  34. data/lib/sidekiq/testing.rb +37 -36
  35. data/lib/sidekiq/transaction_aware_client.rb +45 -0
  36. data/lib/sidekiq/version.rb +1 -1
  37. data/lib/sidekiq/web/action.rb +3 -3
  38. data/lib/sidekiq/web/application.rb +21 -5
  39. data/lib/sidekiq/web/csrf_protection.rb +2 -2
  40. data/lib/sidekiq/web/helpers.rb +20 -7
  41. data/lib/sidekiq/web.rb +5 -1
  42. data/lib/sidekiq/worker.rb +24 -16
  43. data/lib/sidekiq.rb +106 -31
  44. data/sidekiq.gemspec +2 -2
  45. data/web/assets/javascripts/application.js +59 -26
  46. data/web/assets/javascripts/chart.min.js +13 -0
  47. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  48. data/web/assets/javascripts/dashboard.js +0 -17
  49. data/web/assets/javascripts/graph.js +16 -0
  50. data/web/assets/javascripts/metrics.js +262 -0
  51. data/web/assets/stylesheets/application.css +45 -3
  52. data/web/locales/el.yml +43 -19
  53. data/web/locales/en.yml +7 -0
  54. data/web/locales/ja.yml +7 -0
  55. data/web/locales/pt-br.yml +27 -9
  56. data/web/locales/zh-cn.yml +36 -11
  57. data/web/locales/zh-tw.yml +32 -7
  58. data/web/views/_nav.erb +1 -1
  59. data/web/views/_summary.erb +1 -1
  60. data/web/views/busy.erb +9 -4
  61. data/web/views/dashboard.erb +1 -0
  62. data/web/views/metrics.erb +69 -0
  63. data/web/views/metrics_for_job.erb +87 -0
  64. data/web/views/queue.erb +5 -1
  65. metadata +34 -9
  66. data/lib/sidekiq/exception_handler.rb +0 -27
  67. data/lib/sidekiq/util.rb +0 -108
@@ -60,7 +60,23 @@ module Sidekiq
60
60
  erb(:dashboard)
61
61
  end
62
62
 
63
+ get "/metrics" do
64
+ q = Sidekiq::Metrics::Query.new
65
+ @query_result = q.top_jobs
66
+ erb(:metrics)
67
+ end
68
+
69
+ get "/metrics/:name" do
70
+ @name = route_params[:name]
71
+ q = Sidekiq::Metrics::Query.new
72
+ @query_result = q.for_job(@name)
73
+ erb(:metrics_for_job)
74
+ end
75
+
63
76
  get "/busy" do
77
+ @count = (params["count"] || 100).to_i
78
+ (@current_page, @total_size, @workset) = page_items(workset, params["page"], @count)
79
+
64
80
  erb(:busy)
65
81
  end
66
82
 
@@ -299,7 +315,7 @@ module Sidekiq
299
315
 
300
316
  def call(env)
301
317
  action = self.class.match(env)
302
- return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless action
318
+ return [404, {"content-type" => "text/plain", "x-cascade" => "pass"}, ["Not Found"]] unless action
303
319
 
304
320
  app = @klass
305
321
  resp = catch(:halt) do
@@ -316,10 +332,10 @@ module Sidekiq
316
332
  else
317
333
  # rendered content goes here
318
334
  headers = {
319
- "Content-Type" => "text/html",
320
- "Cache-Control" => "private, no-store",
321
- "Content-Language" => action.locale,
322
- "Content-Security-Policy" => CSP_HEADER
335
+ "content-type" => "text/html",
336
+ "cache-control" => "private, no-store",
337
+ "content-language" => action.locale,
338
+ "content-security-policy" => CSP_HEADER
323
339
  }
324
340
  # we'll let Rack calculate Content-Length for us.
325
341
  [200, headers, [resp]]
@@ -143,7 +143,7 @@ module Sidekiq
143
143
  one_time_pad = SecureRandom.random_bytes(token.length)
144
144
  encrypted_token = xor_byte_strings(one_time_pad, token)
145
145
  masked_token = one_time_pad + encrypted_token
146
- Base64.strict_encode64(masked_token)
146
+ Base64.urlsafe_encode64(masked_token)
147
147
  end
148
148
 
149
149
  # Essentially the inverse of +mask_token+.
@@ -169,7 +169,7 @@ module Sidekiq
169
169
  end
170
170
 
171
171
  def decode_token(token)
172
- Base64.strict_decode64(token)
172
+ Base64.urlsafe_decode64(token)
173
173
  end
174
174
 
175
175
  def xor_byte_strings(s1, s2)
@@ -15,7 +15,7 @@ module Sidekiq
15
15
  # so extensions can be localized
16
16
  @strings[lang] ||= settings.locales.each_with_object({}) do |path, global|
17
17
  find_locale_files(lang).each do |file|
18
- strs = YAML.load(File.open(file))
18
+ strs = YAML.safe_load(File.open(file))
19
19
  global.merge!(strs[lang])
20
20
  end
21
21
  end
@@ -137,17 +137,30 @@ module Sidekiq
137
137
  end
138
138
 
139
139
  def sort_direction_label
140
- params[:direction] == "asc" ? "↑" : "↓"
140
+ (params[:direction] == "asc") ? "↑" : "↓"
141
141
  end
142
142
 
143
- def workers
144
- @workers ||= Sidekiq::Workers.new
143
+ def workset
144
+ @work ||= Sidekiq::WorkSet.new
145
145
  end
146
146
 
147
147
  def processes
148
148
  @processes ||= Sidekiq::ProcessSet.new
149
149
  end
150
150
 
151
+ # Sorts processes by hostname following the natural sort order
152
+ def sorted_processes
153
+ @sorted_processes ||= begin
154
+ return processes unless processes.all? { |p| p["hostname"] }
155
+
156
+ processes.to_a.sort_by do |process|
157
+ # Kudos to `shurikk` on StackOverflow
158
+ # https://stackoverflow.com/a/15170063/575547
159
+ process["hostname"].split(/(\d+)/).map { |a| /\d+/.match?(a) ? a.to_i : a }
160
+ end
161
+ end
162
+ end
163
+
151
164
  def stats
152
165
  @stats ||= Sidekiq::Stats.new
153
166
  end
@@ -175,7 +188,7 @@ module Sidekiq
175
188
  end
176
189
 
177
190
  def current_status
178
- workers.size == 0 ? "idle" : "active"
191
+ (workset.size == 0) ? "idle" : "active"
179
192
  end
180
193
 
181
194
  def relative_time(time)
@@ -208,7 +221,7 @@ module Sidekiq
208
221
  end
209
222
 
210
223
  def truncate(text, truncate_after_chars = 2000)
211
- truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text
224
+ (truncate_after_chars && text.size > truncate_after_chars) ? "#{text[0..truncate_after_chars]}..." : text
212
225
  end
213
226
 
214
227
  def display_args(args, truncate_after_chars = 2000)
@@ -301,7 +314,7 @@ module Sidekiq
301
314
  end
302
315
 
303
316
  def environment_title_prefix
304
- environment = Sidekiq.options[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
317
+ environment = Sidekiq[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
305
318
 
306
319
  "[#{environment.upcase}] " unless environment == "production"
307
320
  end
data/lib/sidekiq/web.rb CHANGED
@@ -33,6 +33,10 @@ module Sidekiq
33
33
  "Dead" => "morgue"
34
34
  }
35
35
 
36
+ if ENV["SIDEKIQ_METRICS_BETA"] == "1"
37
+ DEFAULT_TABS["Metrics"] = "metrics"
38
+ end
39
+
36
40
  class << self
37
41
  def settings
38
42
  self
@@ -144,7 +148,7 @@ module Sidekiq
144
148
  m = middlewares
145
149
 
146
150
  rules = []
147
- rules = [[:all, {"Cache-Control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
151
+ rules = [[:all, {"cache-control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
148
152
 
149
153
  ::Rack::Builder.new do
150
154
  use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
@@ -82,7 +82,7 @@ module Sidekiq
82
82
  end
83
83
 
84
84
  def get_sidekiq_options # :nodoc:
85
- self.sidekiq_options_hash ||= Sidekiq.default_worker_options
85
+ self.sidekiq_options_hash ||= Sidekiq.default_job_options
86
86
  end
87
87
 
88
88
  def sidekiq_class_attribute(*attrs)
@@ -175,16 +175,18 @@ module Sidekiq
175
175
 
176
176
  def initialize(klass, opts)
177
177
  @klass = klass
178
- @opts = opts
178
+ # NB: the internal hash always has stringified keys
179
+ @opts = opts.transform_keys(&:to_s)
179
180
 
180
181
  # ActiveJob compatibility
181
- interval = @opts.delete(:wait_until) || @opts.delete(:wait)
182
+ interval = @opts.delete("wait_until") || @opts.delete("wait")
182
183
  at(interval) if interval
183
184
  end
184
185
 
185
186
  def set(options)
186
- interval = options.delete(:wait_until) || options.delete(:wait)
187
- @opts.merge!(options)
187
+ hash = options.transform_keys(&:to_s)
188
+ interval = hash.delete("wait_until") || @opts.delete("wait")
189
+ @opts.merge!(hash)
188
190
  at(interval) if interval
189
191
  self
190
192
  end
@@ -200,7 +202,7 @@ module Sidekiq
200
202
  # Explicit inline execution of a job. Returns nil if the job did not
201
203
  # execute, true otherwise.
202
204
  def perform_inline(*args)
203
- raw = @opts.merge("args" => args, "class" => @klass).transform_keys(&:to_s)
205
+ raw = @opts.merge("args" => args, "class" => @klass)
204
206
 
205
207
  # validate and normalize payload
206
208
  item = normalize_item(raw)
@@ -235,11 +237,9 @@ module Sidekiq
235
237
  alias_method :perform_sync, :perform_inline
236
238
 
237
239
  def perform_bulk(args, batch_size: 1_000)
238
- hash = @opts.transform_keys(&:to_s)
239
- pool = Thread.current[:sidekiq_via_pool] || @klass.get_sidekiq_options["pool"] || Sidekiq.redis_pool
240
- client = Sidekiq::Client.new(pool)
240
+ client = @klass.build_client
241
241
  result = args.each_slice(batch_size).flat_map do |slice|
242
- client.push_bulk(hash.merge("class" => @klass, "args" => slice))
242
+ client.push_bulk(@opts.merge("class" => @klass, "args" => slice))
243
243
  end
244
244
 
245
245
  result.is_a?(Enumerator::Lazy) ? result.force : result
@@ -257,7 +257,7 @@ module Sidekiq
257
257
  def at(interval)
258
258
  int = interval.to_f
259
259
  now = Time.now.to_f
260
- ts = (int < 1_000_000_000 ? now + int : int)
260
+ ts = ((int < 1_000_000_000) ? now + int : int)
261
261
  # Optimization to enqueue something now that is scheduled to go out now or in the past
262
262
  @opts["at"] = ts if ts > now
263
263
  self
@@ -293,6 +293,7 @@ module Sidekiq
293
293
  def perform_inline(*args)
294
294
  Setter.new(self, {}).perform_inline(*args)
295
295
  end
296
+ alias_method :perform_sync, :perform_inline
296
297
 
297
298
  ##
298
299
  # Push a large number of jobs to Redis, while limiting the batch of
@@ -323,7 +324,7 @@ module Sidekiq
323
324
  def perform_in(interval, *args)
324
325
  int = interval.to_f
325
326
  now = Time.now.to_f
326
- ts = (int < 1_000_000_000 ? now + int : int)
327
+ ts = ((int < 1_000_000_000) ? now + int : int)
327
328
 
328
329
  item = {"class" => self, "args" => args}
329
330
 
@@ -339,7 +340,7 @@ module Sidekiq
339
340
  # Legal options:
340
341
  #
341
342
  # queue - use a named queue for this Worker, default 'default'
342
- # retry - enable the RetryJobs middleware for this Worker, *true* to use the default
343
+ # retry - enable retries via JobRetry, *true* to use the default
343
344
  # or *Integer* count
344
345
  # backtrace - whether to save any error backtrace in the retry payload to display in web UI,
345
346
  # can be true, false or an integer number of lines to save, default *false*
@@ -347,15 +348,22 @@ module Sidekiq
347
348
  #
348
349
  # In practice, any option is allowed. This is the main mechanism to configure the
349
350
  # options for a specific job.
351
+ #
352
+ # These options will be saved into the serialized job when enqueued by
353
+ # the client.
350
354
  def sidekiq_options(opts = {})
351
355
  super
352
356
  end
353
357
 
354
358
  def client_push(item) # :nodoc:
355
- pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
356
- stringified_item = item.transform_keys(&:to_s)
359
+ raise ArgumentError, "Job payloads should contain no Symbols: #{item}" if item.any? { |k, v| k.is_a?(::Symbol) }
360
+ build_client.push(item)
361
+ end
357
362
 
358
- Sidekiq::Client.new(pool).push(stringified_item)
363
+ def build_client # :nodoc:
364
+ pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
365
+ client_class = get_sidekiq_options["client_class"] || Sidekiq::Client
366
+ client_class.new(pool)
359
367
  end
360
368
  end
361
369
  end
data/lib/sidekiq.rb CHANGED
@@ -5,6 +5,7 @@ fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.5.0." i
5
5
 
6
6
  require "sidekiq/logger"
7
7
  require "sidekiq/client"
8
+ require "sidekiq/transaction_aware_client"
8
9
  require "sidekiq/worker"
9
10
  require "sidekiq/job"
10
11
  require "sidekiq/redis_connection"
@@ -33,18 +34,16 @@ module Sidekiq
33
34
  startup: [],
34
35
  quiet: [],
35
36
  shutdown: [],
36
- heartbeat: []
37
+ # triggers when we fire the first heartbeat on startup OR repairing a network partition
38
+ heartbeat: [],
39
+ # triggers on EVERY heartbeat call, every 10 seconds
40
+ beat: []
37
41
  },
38
42
  dead_max_jobs: 10_000,
39
43
  dead_timeout_in_seconds: 180 * 24 * 60 * 60, # 6 months
40
44
  reloader: proc { |&block| block.call }
41
45
  }
42
46
 
43
- DEFAULT_WORKER_OPTIONS = {
44
- "retry" => true,
45
- "queue" => "default"
46
- }
47
-
48
47
  FAKE_INFO = {
49
48
  "redis_version" => "9.9.9",
50
49
  "uptime_in_days" => "9999",
@@ -57,19 +56,84 @@ module Sidekiq
57
56
  puts "Calm down, yo."
58
57
  end
59
58
 
59
+ # config.concurrency = 5
60
+ def self.concurrency=(val)
61
+ self[:concurrency] = Integer(val)
62
+ end
63
+
64
+ # config.queues = %w( high default low ) # strict
65
+ # config.queues = %w( high,3 default,2 low,1 ) # weighted
66
+ # config.queues = %w( feature1,1 feature2,1 feature3,1 ) # random
67
+ #
68
+ # With weighted priority, queue will be checked first (weight / total) of the time.
69
+ # high will be checked first (3/6) or 50% of the time.
70
+ # I'd recommend setting weights between 1-10. Weights in the hundreds or thousands
71
+ # are ridiculous and unnecessarily expensive. You can get random queue ordering
72
+ # by explicitly setting all weights to 1.
73
+ def self.queues=(val)
74
+ self[:queues] = Array(val).each_with_object([]) do |qstr, memo|
75
+ name, weight = qstr.split(",")
76
+ self[:strict] = false if weight.to_i > 0
77
+ [weight.to_i, 1].max.times do
78
+ memo << name
79
+ end
80
+ end
81
+ end
82
+
83
+ ### Private APIs
84
+ def self.default_error_handler(ex, ctx)
85
+ logger.warn(dump_json(ctx)) unless ctx.empty?
86
+ logger.warn("#{ex.class.name}: #{ex.message}")
87
+ logger.warn(ex.backtrace.join("\n")) unless ex.backtrace.nil?
88
+ end
89
+
90
+ # DEFAULT_ERROR_HANDLER is a constant that allows the default error handler to
91
+ # be referenced. It must be defined here, after the default_error_handler
92
+ # method is defined.
93
+ DEFAULT_ERROR_HANDLER = method(:default_error_handler)
94
+
95
+ @config = DEFAULTS.dup
60
96
  def self.options
61
- @options ||= DEFAULTS.dup
97
+ logger.warn "`config.options[:key] = value` is deprecated, use `config[:key] = value`: #{caller(1..2)}"
98
+ @config
62
99
  end
63
100
 
64
101
  def self.options=(opts)
65
- @options = opts
102
+ logger.warn "config.options = hash` is deprecated, use `config.merge!(hash)`: #{caller(1..2)}"
103
+ @config = opts
66
104
  end
67
105
 
106
+ def self.[](key)
107
+ @config[key]
108
+ end
109
+
110
+ def self.[]=(key, val)
111
+ @config[key] = val
112
+ end
113
+
114
+ def self.merge!(hash)
115
+ @config.merge!(hash)
116
+ end
117
+
118
+ def self.fetch(*args, &block)
119
+ @config.fetch(*args, &block)
120
+ end
121
+
122
+ def self.handle_exception(ex, ctx = {})
123
+ self[:error_handlers].each do |handler|
124
+ handler.call(ex, ctx)
125
+ rescue => ex
126
+ logger.error "!!! ERROR HANDLER THREW AN ERROR !!!"
127
+ logger.error ex
128
+ logger.error ex.backtrace.join("\n") unless ex.backtrace.nil?
129
+ end
130
+ end
131
+ ###
132
+
68
133
  ##
69
134
  # Configuration for Sidekiq server, use like:
70
135
  #
71
136
  # Sidekiq.configure_server do |config|
72
- # config.redis = { :namespace => 'myapp', :size => 25, :url => 'redis://myhost:8877/0' }
73
137
  # config.server_middleware do |chain|
74
138
  # chain.add MyServerHook
75
139
  # end
@@ -82,7 +146,7 @@ module Sidekiq
82
146
  # Configuration for Sidekiq client, use like:
83
147
  #
84
148
  # Sidekiq.configure_client do |config|
85
- # config.redis = { :namespace => 'myapp', :size => 1, :url => 'redis://myhost:8877/0' }
149
+ # config.redis = { size: 1, url: 'redis://myhost:8877/0' }
86
150
  # end
87
151
  def self.configure_client
88
152
  yield self unless server?
@@ -98,7 +162,7 @@ module Sidekiq
98
162
  retryable = true
99
163
  begin
100
164
  yield conn
101
- rescue Redis::BaseError => ex
165
+ rescue RedisConnection.adapter::BaseError => ex
102
166
  # 2550 Failover can cause the server to become a replica, need
103
167
  # to disconnect and reopen the socket to get back to the primary.
104
168
  # 4495 Use the same logic if we have a "Not enough replicas" error from the primary
@@ -123,7 +187,7 @@ module Sidekiq
123
187
  else
124
188
  conn.info
125
189
  end
126
- rescue Redis::CommandError => ex
190
+ rescue RedisConnection.adapter::CommandError => ex
127
191
  # 2850 return fake version when INFO command has (probably) been renamed
128
192
  raise unless /unknown command/.match?(ex.message)
129
193
  FAKE_INFO
@@ -131,19 +195,19 @@ module Sidekiq
131
195
  end
132
196
 
133
197
  def self.redis_pool
134
- @redis ||= Sidekiq::RedisConnection.create
198
+ @redis ||= RedisConnection.create
135
199
  end
136
200
 
137
201
  def self.redis=(hash)
138
202
  @redis = if hash.is_a?(ConnectionPool)
139
203
  hash
140
204
  else
141
- Sidekiq::RedisConnection.create(hash)
205
+ RedisConnection.create(hash)
142
206
  end
143
207
  end
144
208
 
145
209
  def self.client_middleware
146
- @client_chain ||= Middleware::Chain.new
210
+ @client_chain ||= Middleware::Chain.new(self)
147
211
  yield @client_chain if block_given?
148
212
  @client_chain
149
213
  end
@@ -155,16 +219,23 @@ module Sidekiq
155
219
  end
156
220
 
157
221
  def self.default_server_middleware
158
- Middleware::Chain.new
222
+ Middleware::Chain.new(self)
223
+ end
224
+
225
+ def self.default_worker_options=(hash) # deprecated
226
+ @default_job_options = default_job_options.merge(hash.transform_keys(&:to_s))
159
227
  end
160
228
 
161
- def self.default_worker_options=(hash)
162
- # stringify
163
- @default_worker_options = default_worker_options.merge(hash.transform_keys(&:to_s))
229
+ def self.default_job_options=(hash)
230
+ @default_job_options = default_job_options.merge(hash.transform_keys(&:to_s))
164
231
  end
165
232
 
166
- def self.default_worker_options
167
- defined?(@default_worker_options) ? @default_worker_options : DEFAULT_WORKER_OPTIONS
233
+ def self.default_worker_options # deprecated
234
+ @default_job_options ||= {"retry" => true, "queue" => "default"}
235
+ end
236
+
237
+ def self.default_job_options
238
+ @default_job_options ||= {"retry" => true, "queue" => "default"}
168
239
  end
169
240
 
170
241
  ##
@@ -177,7 +248,7 @@ module Sidekiq
177
248
  # end
178
249
  # end
179
250
  def self.death_handlers
180
- options[:death_handlers]
251
+ self[:death_handlers]
181
252
  end
182
253
 
183
254
  def self.load_json(string)
@@ -202,7 +273,7 @@ module Sidekiq
202
273
  end
203
274
 
204
275
  def self.logger
205
- @logger ||= Sidekiq::Logger.new($stdout, level: Logger::INFO)
276
+ @logger ||= Sidekiq::Logger.new($stdout, level: :info)
206
277
  end
207
278
 
208
279
  def self.logger=(logger)
@@ -220,13 +291,17 @@ module Sidekiq
220
291
  defined?(Sidekiq::Pro)
221
292
  end
222
293
 
294
+ def self.ent?
295
+ defined?(Sidekiq::Enterprise)
296
+ end
297
+
223
298
  # How frequently Redis should be checked by a random Sidekiq process for
224
299
  # scheduled and retriable jobs. Each individual process will take turns by
225
300
  # waiting some multiple of this value.
226
301
  #
227
302
  # See sidekiq/scheduled.rb for an in-depth explanation of this value
228
303
  def self.average_scheduled_poll_interval=(interval)
229
- options[:average_scheduled_poll_interval] = interval
304
+ self[:average_scheduled_poll_interval] = interval
230
305
  end
231
306
 
232
307
  # Register a proc to handle any error which occurs within the Sidekiq process.
@@ -237,7 +312,7 @@ module Sidekiq
237
312
  #
238
313
  # The default error handler logs errors to Sidekiq.logger.
239
314
  def self.error_handlers
240
- options[:error_handlers]
315
+ self[:error_handlers]
241
316
  end
242
317
 
243
318
  # Register a block to run at a point in the Sidekiq lifecycle.
@@ -250,20 +325,20 @@ module Sidekiq
250
325
  # end
251
326
  def self.on(event, &block)
252
327
  raise ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol)
253
- raise ArgumentError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event)
254
- options[:lifecycle_events][event] << block
328
+ raise ArgumentError, "Invalid event name: #{event}" unless self[:lifecycle_events].key?(event)
329
+ self[:lifecycle_events][event] << block
255
330
  end
256
331
 
257
332
  def self.strict_args!(mode = :raise)
258
- options[:on_complex_arguments] = mode
333
+ self[:on_complex_arguments] = mode
259
334
  end
260
335
 
261
- # We are shutting down Sidekiq but what about workers that
336
+ # We are shutting down Sidekiq but what about threads that
262
337
  # are working on some long job? This error is
263
- # raised in workers that have not finished within the hard
338
+ # raised in jobs that have not finished within the hard
264
339
  # timeout limit. This is needed to rollback db transactions,
265
340
  # otherwise Ruby's Thread#kill will commit. See #377.
266
- # DO NOT RESCUE THIS ERROR IN YOUR WORKERS
341
+ # DO NOT RESCUE THIS ERROR IN YOUR JOBS
267
342
  class Shutdown < Interrupt; end
268
343
  end
269
344
 
data/sidekiq.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |gem|
22
22
  "source_code_uri" => "https://github.com/mperham/sidekiq"
23
23
  }
24
24
 
25
- gem.add_dependency "redis", ">= 4.2.0"
26
- gem.add_dependency "connection_pool", ">= 2.2.2"
25
+ gem.add_dependency "redis", ["<5", ">= 4.5.0"]
26
+ gem.add_dependency "connection_pool", ["<3", ">= 2.2.5"]
27
27
  gem.add_dependency "rack", "~> 2.0"
28
28
  end
@@ -9,7 +9,9 @@ var ready = (callback) => {
9
9
  else document.addEventListener("DOMContentLoaded", callback);
10
10
  }
11
11
 
12
- ready(() => {
12
+ ready(addListeners)
13
+
14
+ function addListeners() {
13
15
  document.querySelectorAll(".check_all").forEach(node => {
14
16
  node.addEventListener("click", event => {
15
17
  node.closest('table').querySelectorAll('input[type=checkbox]').forEach(inp => { inp.checked = !!node.checked; });
@@ -26,42 +28,48 @@ ready(() => {
26
28
  })
27
29
 
28
30
  document.querySelectorAll("[data-toggle]").forEach(node => {
29
- node.addEventListener("click", event => {
30
- var targName = node.getAttribute("data-toggle");
31
- var full = document.getElementById(targName + "_full");
32
- if (full.style.display == "block") {
33
- full.style.display = 'none';
34
- } else {
35
- full.style.display = 'block';
36
- }
37
- })
31
+ node.addEventListener("click", addDataToggleListeners)
38
32
  })
39
33
 
40
34
  updateFuzzyTimes();
35
+ setLivePollFromUrl();
41
36
 
42
37
  var buttons = document.querySelectorAll(".live-poll");
43
38
  if (buttons.length > 0) {
44
39
  buttons.forEach(node => {
45
- node.addEventListener("click", event => {
46
- if (localStorage.sidekiqLivePoll == "enabled") {
47
- localStorage.sidekiqLivePoll = "disabled";
48
- clearTimeout(livePollTimer);
49
- livePollTimer = null;
50
- } else {
51
- localStorage.sidekiqLivePoll = "enabled";
52
- livePollCallback();
53
- }
54
-
55
- updateLivePollButton();
56
- })
40
+ node.addEventListener("click", addPollingListeners)
57
41
  });
58
42
 
59
43
  updateLivePollButton();
60
- if (localStorage.sidekiqLivePoll == "enabled") {
44
+ if (localStorage.sidekiqLivePoll == "enabled" && !livePollTimer) {
61
45
  scheduleLivePoll();
62
46
  }
63
47
  }
64
- })
48
+ }
49
+
50
+ function addPollingListeners(_event) {
51
+ if (localStorage.sidekiqLivePoll == "enabled") {
52
+ localStorage.sidekiqLivePoll = "disabled";
53
+ clearTimeout(livePollTimer);
54
+ livePollTimer = null;
55
+ } else {
56
+ localStorage.sidekiqLivePoll = "enabled";
57
+ livePollCallback();
58
+ }
59
+
60
+ updateLivePollButton();
61
+ }
62
+
63
+ function addDataToggleListeners(event) {
64
+ var source = event.target || event.srcElement;
65
+ var targName = source.getAttribute("data-toggle");
66
+ var full = document.getElementById(targName);
67
+ if (full.style.display == "block") {
68
+ full.style.display = 'none';
69
+ } else {
70
+ full.style.display = 'block';
71
+ }
72
+ }
65
73
 
66
74
  function updateFuzzyTimes() {
67
75
  var locale = document.body.getAttribute("data-locale");
@@ -76,6 +84,14 @@ function updateFuzzyTimes() {
76
84
  t.cancel();
77
85
  }
78
86
 
87
+ function setLivePollFromUrl() {
88
+ var url_params = new URL(window.location.href).searchParams
89
+
90
+ if (url_params.get("poll") == "true") {
91
+ localStorage.sidekiqLivePoll = "enabled";
92
+ }
93
+ }
94
+
79
95
  function updateLivePollButton() {
80
96
  if (localStorage.sidekiqLivePoll == "enabled") {
81
97
  document.querySelectorAll('.live-poll-stop').forEach(box => { box.style.display = "inline-block" })
@@ -89,11 +105,24 @@ function updateLivePollButton() {
89
105
  function livePollCallback() {
90
106
  clearTimeout(livePollTimer);
91
107
 
92
- fetch(window.location.href).then(resp => resp.text()).then(replacePage).finally(scheduleLivePoll)
108
+ fetch(window.location.href)
109
+ .then(checkResponse)
110
+ .then(resp => resp.text())
111
+ .then(replacePage)
112
+ .catch(showError)
113
+ .finally(scheduleLivePoll)
114
+ }
115
+
116
+ function checkResponse(resp) {
117
+ if (!resp.ok) {
118
+ throw response.error();
119
+ }
120
+ return resp
93
121
  }
94
122
 
95
123
  function scheduleLivePoll() {
96
124
  let ti = parseInt(localStorage.sidekiqTimeInterval) || 5000;
125
+ if (ti < 2000) { ti = 2000 }
97
126
  livePollTimer = setTimeout(livePollCallback, ti);
98
127
  }
99
128
 
@@ -107,5 +136,9 @@ function replacePage(text) {
107
136
  var header_status = doc.querySelector('.status')
108
137
  document.querySelector('.status').replaceWith(header_status)
109
138
 
110
- updateFuzzyTimes();
139
+ addListeners();
140
+ }
141
+
142
+ function showError(error) {
143
+ console.error(error)
111
144
  }