sidekiq 4.2.4 → 5.2.0

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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/.github/issue_template.md +8 -1
  3. data/.gitignore +1 -0
  4. data/.travis.yml +5 -3
  5. data/5.0-Upgrade.md +56 -0
  6. data/COMM-LICENSE +1 -1
  7. data/Changes.md +151 -0
  8. data/Ent-Changes.md +77 -2
  9. data/Gemfile +10 -25
  10. data/LICENSE +1 -1
  11. data/Pro-4.0-Upgrade.md +35 -0
  12. data/Pro-Changes.md +156 -2
  13. data/README.md +9 -6
  14. data/Rakefile +1 -2
  15. data/bin/sidekiqctl +1 -1
  16. data/bin/sidekiqload +15 -33
  17. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
  18. data/lib/generators/sidekiq/templates/worker_test.rb.erb +1 -1
  19. data/lib/sidekiq/api.rb +157 -67
  20. data/lib/sidekiq/cli.rb +71 -26
  21. data/lib/sidekiq/client.rb +25 -18
  22. data/lib/sidekiq/core_ext.rb +1 -106
  23. data/lib/sidekiq/delay.rb +42 -0
  24. data/lib/sidekiq/exception_handler.rb +2 -4
  25. data/lib/sidekiq/extensions/generic_proxy.rb +7 -1
  26. data/lib/sidekiq/fetch.rb +1 -1
  27. data/lib/sidekiq/job_logger.rb +25 -0
  28. data/lib/sidekiq/job_retry.rb +241 -0
  29. data/lib/sidekiq/launcher.rb +45 -37
  30. data/lib/sidekiq/logging.rb +18 -2
  31. data/lib/sidekiq/manager.rb +3 -4
  32. data/lib/sidekiq/middleware/server/active_record.rb +10 -0
  33. data/lib/sidekiq/processor.rb +91 -34
  34. data/lib/sidekiq/rails.rb +15 -51
  35. data/lib/sidekiq/redis_connection.rb +31 -5
  36. data/lib/sidekiq/scheduled.rb +35 -8
  37. data/lib/sidekiq/testing.rb +24 -7
  38. data/lib/sidekiq/util.rb +6 -2
  39. data/lib/sidekiq/version.rb +1 -1
  40. data/lib/sidekiq/web/action.rb +2 -6
  41. data/lib/sidekiq/web/application.rb +28 -21
  42. data/lib/sidekiq/web/helpers.rb +67 -23
  43. data/lib/sidekiq/web/router.rb +14 -10
  44. data/lib/sidekiq/web.rb +4 -4
  45. data/lib/sidekiq/worker.rb +97 -14
  46. data/lib/sidekiq.rb +23 -24
  47. data/sidekiq.gemspec +7 -10
  48. data/web/assets/javascripts/application.js +0 -0
  49. data/web/assets/javascripts/dashboard.js +18 -13
  50. data/web/assets/stylesheets/application-rtl.css +246 -0
  51. data/web/assets/stylesheets/application.css +336 -4
  52. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  53. data/web/assets/stylesheets/bootstrap.css +2 -2
  54. data/web/locales/ar.yml +80 -0
  55. data/web/locales/en.yml +1 -0
  56. data/web/locales/es.yml +4 -3
  57. data/web/locales/fa.yml +80 -0
  58. data/web/locales/he.yml +79 -0
  59. data/web/locales/ja.yml +5 -3
  60. data/web/locales/ur.yml +80 -0
  61. data/web/views/_footer.erb +5 -2
  62. data/web/views/_job_info.erb +1 -1
  63. data/web/views/_nav.erb +1 -1
  64. data/web/views/_paging.erb +1 -1
  65. data/web/views/busy.erb +9 -5
  66. data/web/views/dashboard.erb +3 -3
  67. data/web/views/layout.erb +11 -2
  68. data/web/views/morgue.erb +14 -10
  69. data/web/views/queue.erb +10 -10
  70. data/web/views/queues.erb +4 -2
  71. data/web/views/retries.erb +13 -11
  72. data/web/views/retry.erb +1 -1
  73. data/web/views/scheduled.erb +2 -2
  74. metadata +26 -160
  75. data/lib/sidekiq/middleware/server/logging.rb +0 -40
  76. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
  77. data/test/config.yml +0 -9
  78. data/test/env_based_config.yml +0 -11
  79. data/test/fake_env.rb +0 -1
  80. data/test/fixtures/en.yml +0 -2
  81. data/test/helper.rb +0 -75
  82. data/test/test_actors.rb +0 -138
  83. data/test/test_api.rb +0 -528
  84. data/test/test_cli.rb +0 -418
  85. data/test/test_client.rb +0 -266
  86. data/test/test_exception_handler.rb +0 -56
  87. data/test/test_extensions.rb +0 -127
  88. data/test/test_fetch.rb +0 -50
  89. data/test/test_launcher.rb +0 -95
  90. data/test/test_logging.rb +0 -35
  91. data/test/test_manager.rb +0 -50
  92. data/test/test_middleware.rb +0 -158
  93. data/test/test_processor.rb +0 -235
  94. data/test/test_rails.rb +0 -22
  95. data/test/test_redis_connection.rb +0 -132
  96. data/test/test_retry.rb +0 -326
  97. data/test/test_retry_exhausted.rb +0 -149
  98. data/test/test_scheduled.rb +0 -115
  99. data/test/test_scheduling.rb +0 -58
  100. data/test/test_sidekiq.rb +0 -107
  101. data/test/test_testing.rb +0 -143
  102. data/test/test_testing_fake.rb +0 -357
  103. data/test/test_testing_inline.rb +0 -94
  104. data/test/test_util.rb +0 -13
  105. data/test/test_web.rb +0 -726
  106. data/test/test_web_helpers.rb +0 -54
data/bin/sidekiqload CHANGED
@@ -12,22 +12,9 @@ require_relative '../lib/sidekiq/launcher'
12
12
 
13
13
  include Sidekiq::Util
14
14
 
15
- # brew tap shopify/shopify
16
- # brew install toxiproxy
17
- # gem install toxiproxy
18
- require 'toxiproxy'
19
- # simulate a non-localhost network for realer-world conditions.
20
- # adding 1ms of network latency has an ENORMOUS impact on benchmarks
21
- Toxiproxy.populate([{
22
- "name": "redis",
23
- "listen": "127.0.0.1:6380",
24
- "upstream": "127.0.0.1:6379"
25
- }])
26
-
27
-
28
15
  Sidekiq.configure_server do |config|
29
16
  #config.options[:concurrency] = 1
30
- config.redis = { driver: :hiredis, db: 13, port: 6380 }
17
+ config.redis = { db: 13 }
31
18
  config.options[:queues] << 'default'
32
19
  config.logger.level = Logger::ERROR
33
20
  config.average_scheduled_poll_interval = 2
@@ -49,17 +36,17 @@ end
49
36
  # brew tap shopify/shopify
50
37
  # brew install toxiproxy
51
38
  # gem install toxiproxy
52
- require 'toxiproxy'
39
+ #require 'toxiproxy'
53
40
  # simulate a non-localhost network for realer-world conditions.
54
41
  # adding 1ms of network latency has an ENORMOUS impact on benchmarks
55
- Toxiproxy.populate([{
56
- "name": "redis",
57
- "listen": "127.0.0.1:6380",
58
- "upstream": "127.0.0.1:6379"
59
- }])
42
+ #Toxiproxy.populate([{
43
+ #"name": "redis",
44
+ #"listen": "127.0.0.1:6380",
45
+ #"upstream": "127.0.0.1:6379"
46
+ #}])
60
47
 
61
48
  self_read, self_write = IO.pipe
62
- %w(INT TERM USR1 USR2 TTIN).each do |sig|
49
+ %w(INT TERM TSTP TTIN).each do |sig|
63
50
  begin
64
51
  trap sig do
65
52
  self_write.puts(sig)
@@ -80,17 +67,12 @@ def handle_signal(launcher, sig)
80
67
  when 'TERM'
81
68
  # Heroku sends TERM and then waits 10 seconds for process to exit.
82
69
  raise Interrupt
83
- when 'USR1'
84
- Sidekiq.logger.info "Received USR1, no longer accepting new work"
70
+ when 'TSTP'
71
+ Sidekiq.logger.info "Received TSTP, no longer accepting new work"
85
72
  launcher.quiet
86
- when 'USR2'
87
- if Sidekiq.options[:logfile]
88
- Sidekiq.logger.info "Received USR2, reopening log file"
89
- Sidekiq::Logging.reopen_logs
90
- end
91
73
  when 'TTIN'
92
74
  Thread.list.each do |thread|
93
- Sidekiq.logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
75
+ Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread['label']}"
94
76
  if thread.backtrace
95
77
  Sidekiq.logger.warn thread.backtrace.join("\n")
96
78
  else
@@ -121,7 +103,7 @@ Sidekiq.logger.error "Created #{count*iter} jobs"
121
103
  Monitoring = Thread.new do
122
104
  watchdog("monitor thread") do
123
105
  while true
124
- sleep 2
106
+ sleep 1
125
107
  qsize, retries = Sidekiq.redis do |conn|
126
108
  conn.pipelined do
127
109
  conn.llen "queue:default"
@@ -143,8 +125,8 @@ begin
143
125
  #RubyProf::exclude_threads = [ Monitoring ]
144
126
  #RubyProf.start
145
127
  fire_event(:startup)
146
- Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis"
147
- Toxiproxy[:redis].downstream(:latency, latency: 1).apply do
128
+ #Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis"
129
+ #Toxiproxy[:redis].downstream(:latency, latency: 1).apply do
148
130
  launcher = Sidekiq::Launcher.new(Sidekiq.options)
149
131
  launcher.run
150
132
 
@@ -152,7 +134,7 @@ begin
152
134
  signal = readable_io.first[0].gets.strip
153
135
  handle_signal(launcher, signal)
154
136
  end
155
- end
137
+ #end
156
138
  rescue SystemExit => e
157
139
  #Sidekiq.logger.error("Profiling...")
158
140
  #result = RubyProf.stop
@@ -1,6 +1,6 @@
1
1
  require 'rails_helper'
2
2
  <% module_namespacing do -%>
3
3
  RSpec.describe <%= class_name %>Worker, type: :worker do
4
- pending "add some examples to (or delete) #{__FILE__}"
4
+ pending "add some examples to (or delete) #{__FILE__}"
5
5
  end
6
6
  <% end -%>
@@ -1,4 +1,4 @@
1
- require_relative 'test_helper'
1
+ require 'test_helper'
2
2
  <% module_namespacing do -%>
3
3
  class <%= class_name %>WorkerTest < <% if defined? Minitest::Test %>Minitest::Test<% else %>MiniTest::Unit::TestCase<% end %>
4
4
  def test_example
data/lib/sidekiq/api.rb CHANGED
@@ -1,9 +1,24 @@
1
1
  # frozen_string_literal: true
2
- # encoding: utf-8
3
2
  require 'sidekiq'
4
3
 
5
4
  module Sidekiq
5
+
6
+ module RedisScanner
7
+ def sscan(conn, key)
8
+ cursor = '0'
9
+ result = []
10
+ loop do
11
+ cursor, values = conn.sscan(key, cursor)
12
+ result.push(*values)
13
+ break if cursor == '0'
14
+ end
15
+ result
16
+ end
17
+ end
18
+
6
19
  class Stats
20
+ include RedisScanner
21
+
7
22
  def initialize
8
23
  fetch_stats!
9
24
  end
@@ -51,31 +66,40 @@ module Sidekiq
51
66
  def fetch_stats!
52
67
  pipe1_res = Sidekiq.redis do |conn|
53
68
  conn.pipelined do
54
- conn.get('stat:processed'.freeze)
55
- conn.get('stat:failed'.freeze)
56
- conn.zcard('schedule'.freeze)
57
- conn.zcard('retry'.freeze)
58
- conn.zcard('dead'.freeze)
59
- conn.scard('processes'.freeze)
60
- conn.lrange('queue:default'.freeze, -1, -1)
61
- conn.smembers('processes'.freeze)
62
- conn.smembers('queues'.freeze)
69
+ conn.get('stat:processed')
70
+ conn.get('stat:failed')
71
+ conn.zcard('schedule')
72
+ conn.zcard('retry')
73
+ conn.zcard('dead')
74
+ conn.scard('processes')
75
+ conn.lrange('queue:default', -1, -1)
63
76
  end
64
77
  end
65
78
 
79
+ processes = Sidekiq.redis do |conn|
80
+ sscan(conn, 'processes')
81
+ end
82
+
83
+ queues = Sidekiq.redis do |conn|
84
+ sscan(conn, 'queues')
85
+ end
86
+
66
87
  pipe2_res = Sidekiq.redis do |conn|
67
88
  conn.pipelined do
68
- pipe1_res[7].each {|key| conn.hget(key, 'busy'.freeze) }
69
- pipe1_res[8].each {|queue| conn.llen("queue:#{queue}") }
89
+ processes.each {|key| conn.hget(key, 'busy') }
90
+ queues.each {|queue| conn.llen("queue:#{queue}") }
70
91
  end
71
92
  end
72
93
 
73
- s = pipe1_res[7].size
94
+ s = processes.size
74
95
  workers_size = pipe2_res[0...s].map(&:to_i).inject(0, &:+)
75
96
  enqueued = pipe2_res[s..-1].map(&:to_i).inject(0, &:+)
76
97
 
77
98
  default_queue_latency = if (entry = pipe1_res[6].first)
78
- Time.now.to_f - Sidekiq.load_json(entry)['enqueued_at'.freeze]
99
+ job = Sidekiq.load_json(entry) rescue {}
100
+ now = Time.now.to_f
101
+ thence = job['enqueued_at'] || now
102
+ now - thence
79
103
  else
80
104
  0
81
105
  end
@@ -114,9 +138,11 @@ module Sidekiq
114
138
  end
115
139
 
116
140
  class Queues
141
+ include RedisScanner
142
+
117
143
  def lengths
118
144
  Sidekiq.redis do |conn|
119
- queues = conn.smembers('queues'.freeze)
145
+ queues = sscan(conn, 'queues')
120
146
 
121
147
  lengths = conn.pipelined do
122
148
  queues.each do |queue|
@@ -143,11 +169,11 @@ module Sidekiq
143
169
  end
144
170
 
145
171
  def processed
146
- date_stat_hash("processed")
172
+ @processed ||= date_stat_hash("processed")
147
173
  end
148
174
 
149
175
  def failed
150
- date_stat_hash("failed")
176
+ @failed ||= date_stat_hash("failed")
151
177
  end
152
178
 
153
179
  private
@@ -160,16 +186,21 @@ module Sidekiq
160
186
 
161
187
  while i < @days_previous
162
188
  date = @start_date - i
163
- datestr = date.strftime("%Y-%m-%d".freeze)
189
+ datestr = date.strftime("%Y-%m-%d")
164
190
  keys << "stat:#{stat}:#{datestr}"
165
191
  dates << datestr
166
192
  i += 1
167
193
  end
168
194
 
169
- Sidekiq.redis do |conn|
170
- conn.mget(keys).each_with_index do |value, idx|
171
- stat_hash[dates[idx]] = value ? value.to_i : 0
195
+ begin
196
+ Sidekiq.redis do |conn|
197
+ conn.mget(keys).each_with_index do |value, idx|
198
+ stat_hash[dates[idx]] = value ? value.to_i : 0
199
+ end
172
200
  end
201
+ rescue Redis::CommandError
202
+ # mget will trigger a CROSSSLOT error when run against a Cluster
203
+ # TODO Someone want to add Cluster support?
173
204
  end
174
205
 
175
206
  stat_hash
@@ -191,18 +222,19 @@ module Sidekiq
191
222
  #
192
223
  class Queue
193
224
  include Enumerable
225
+ extend RedisScanner
194
226
 
195
227
  ##
196
228
  # Return all known queues within Redis.
197
229
  #
198
230
  def self.all
199
- Sidekiq.redis { |c| c.smembers('queues'.freeze) }.sort.map { |q| Sidekiq::Queue.new(q) }
231
+ Sidekiq.redis { |c| sscan(c, 'queues') }.sort.map { |q| Sidekiq::Queue.new(q) }
200
232
  end
201
233
 
202
234
  attr_reader :name
203
235
 
204
236
  def initialize(name="default")
205
- @name = name
237
+ @name = name.to_s
206
238
  @rname = "queue:#{name}"
207
239
  end
208
240
 
@@ -225,7 +257,10 @@ module Sidekiq
225
257
  conn.lrange(@rname, -1, -1)
226
258
  end.first
227
259
  return 0 unless entry
228
- Time.now.to_f - Sidekiq.load_json(entry)['enqueued_at']
260
+ job = Sidekiq.load_json(entry)
261
+ now = Time.now.to_f
262
+ thence = job['enqueued_at'] || now
263
+ now - thence
229
264
  end
230
265
 
231
266
  def each
@@ -262,7 +297,7 @@ module Sidekiq
262
297
  Sidekiq.redis do |conn|
263
298
  conn.multi do
264
299
  conn.del(@rname)
265
- conn.srem("queues".freeze, name)
300
+ conn.srem("queues", name)
266
301
  end
267
302
  end
268
303
  end
@@ -281,13 +316,25 @@ module Sidekiq
281
316
  attr_reader :value
282
317
 
283
318
  def initialize(item, queue_name=nil)
319
+ @args = nil
284
320
  @value = item
285
- @item = item.is_a?(Hash) ? item : Sidekiq.load_json(item)
321
+ @item = item.is_a?(Hash) ? item : parse(item)
286
322
  @queue = queue_name || @item['queue']
287
323
  end
288
324
 
325
+ def parse(item)
326
+ Sidekiq.load_json(item)
327
+ rescue JSON::ParserError
328
+ # If the job payload in Redis is invalid JSON, we'll load
329
+ # the item as an empty hash and store the invalid JSON as
330
+ # the job 'args' for display in the Web UI.
331
+ @invalid = true
332
+ @args = [item]
333
+ {}
334
+ end
335
+
289
336
  def klass
290
- @item['class']
337
+ self['class']
291
338
  end
292
339
 
293
340
  def display_class
@@ -312,38 +359,42 @@ module Sidekiq
312
359
 
313
360
  def display_args
314
361
  # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
315
- @args ||= case klass
362
+ @display_args ||= case klass
316
363
  when /\ASidekiq::Extensions::Delayed/
317
364
  safe_load(args[0], args) do |_, _, arg|
318
365
  arg
319
366
  end
320
367
  when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
321
- job_args = @item['wrapped'] ? args[0]["arguments"] : []
322
- if 'ActionMailer::DeliveryJob' == (@item['wrapped'] || args[0])
323
- # remove MailerClass, mailer_method and 'deliver_now'
324
- job_args.drop(3)
368
+ job_args = self['wrapped'] ? args[0]["arguments"] : []
369
+ if 'ActionMailer::DeliveryJob' == (self['wrapped'] || args[0])
370
+ # remove MailerClass, mailer_method and 'deliver_now'
371
+ job_args.drop(3)
325
372
  else
326
- job_args
373
+ job_args
327
374
  end
328
375
  else
376
+ if self['encrypt']
377
+ # no point in showing 150+ bytes of random garbage
378
+ args[-1] = '[encrypted data]'
379
+ end
329
380
  args
330
381
  end
331
382
  end
332
383
 
333
384
  def args
334
- @item['args']
385
+ @args || @item['args']
335
386
  end
336
387
 
337
388
  def jid
338
- @item['jid']
389
+ self['jid']
339
390
  end
340
391
 
341
392
  def enqueued_at
342
- @item['enqueued_at'] ? Time.at(@item['enqueued_at']).utc : nil
393
+ self['enqueued_at'] ? Time.at(self['enqueued_at']).utc : nil
343
394
  end
344
395
 
345
396
  def created_at
346
- Time.at(@item['created_at'] || @item['enqueued_at'] || 0).utc
397
+ Time.at(self['created_at'] || self['enqueued_at'] || 0).utc
347
398
  end
348
399
 
349
400
  def queue
@@ -351,7 +402,8 @@ module Sidekiq
351
402
  end
352
403
 
353
404
  def latency
354
- Time.now.to_f - (@item['enqueued_at'] || @item['created_at'])
405
+ now = Time.now.to_f
406
+ now - (@item['enqueued_at'] || @item['created_at'] || now)
355
407
  end
356
408
 
357
409
  ##
@@ -364,7 +416,10 @@ module Sidekiq
364
416
  end
365
417
 
366
418
  def [](name)
367
- @item[name]
419
+ # nil will happen if the JSON fails to parse.
420
+ # We don't guarantee Sidekiq will work with bad job JSON but we should
421
+ # make a best effort to minimize the damage.
422
+ @item ? @item[name] : nil
368
423
  end
369
424
 
370
425
  private
@@ -416,10 +471,9 @@ module Sidekiq
416
471
  end
417
472
 
418
473
  def retry
419
- raise "Retry not available on jobs which have not failed" unless item["failed_at"]
420
474
  remove_job do |message|
421
475
  msg = Sidekiq.load_json(message)
422
- msg['retry_count'] -= 1
476
+ msg['retry_count'] -= 1 if msg['retry_count']
423
477
  Sidekiq::Client.push(msg)
424
478
  end
425
479
  end
@@ -427,20 +481,15 @@ module Sidekiq
427
481
  ##
428
482
  # Place job in the dead set
429
483
  def kill
430
- raise 'Kill not available on jobs which have not failed' unless item['failed_at']
431
484
  remove_job do |message|
432
- Sidekiq.logger.info { "Killing job #{message['jid']}" }
433
- now = Time.now.to_f
434
- Sidekiq.redis do |conn|
435
- conn.multi do
436
- conn.zadd('dead', now, message)
437
- conn.zremrangebyscore('dead', '-inf', now - DeadSet.timeout)
438
- conn.zremrangebyrank('dead', 0, - DeadSet.max_jobs)
439
- end
440
- end
485
+ DeadSet.new.kill(message)
441
486
  end
442
487
  end
443
488
 
489
+ def error?
490
+ !!item['error_class']
491
+ end
492
+
444
493
  private
445
494
 
446
495
  def remove_job
@@ -523,7 +572,7 @@ module Sidekiq
523
572
  end
524
573
  break if elements.empty?
525
574
  page -= 1
526
- elements.each do |element, score|
575
+ elements.reverse.each do |element, score|
527
576
  yield SortedEntry.new(self, score, element)
528
577
  end
529
578
  offset_size = initial_size - @_size
@@ -585,13 +634,13 @@ module Sidekiq
585
634
  # Allows enumeration of scheduled jobs within Sidekiq.
586
635
  # Based on this, you can search/filter for jobs. Here's an
587
636
  # example where I'm selecting all jobs of a certain type
588
- # and deleting them from the retry queue.
637
+ # and deleting them from the schedule queue.
589
638
  #
590
639
  # r = Sidekiq::ScheduledSet.new
591
- # r.select do |retri|
592
- # retri.klass == 'Sidekiq::Extensions::DelayedClass' &&
593
- # retri.args[0] == 'User' &&
594
- # retri.args[1] == 'setup_new_subscriber'
640
+ # r.select do |scheduled|
641
+ # scheduled.klass == 'Sidekiq::Extensions::DelayedClass' &&
642
+ # scheduled.args[0] == 'User' &&
643
+ # scheduled.args[1] == 'setup_new_subscriber'
595
644
  # end.map(&:delete)
596
645
  class ScheduledSet < JobSet
597
646
  def initialize
@@ -631,6 +680,27 @@ module Sidekiq
631
680
  super 'dead'
632
681
  end
633
682
 
683
+ def kill(message, opts={})
684
+ now = Time.now.to_f
685
+ Sidekiq.redis do |conn|
686
+ conn.multi do
687
+ conn.zadd(name, now.to_s, message)
688
+ conn.zremrangebyscore(name, '-inf', now - self.class.timeout)
689
+ conn.zremrangebyrank(name, 0, - self.class.max_jobs)
690
+ end
691
+ end
692
+
693
+ if opts[:notify_failure] != false
694
+ job = Sidekiq.load_json(message)
695
+ r = RuntimeError.new("Job killed by API")
696
+ r.set_backtrace(caller)
697
+ Sidekiq.death_handlers.each do |handle|
698
+ handle.call(job, r)
699
+ end
700
+ end
701
+ true
702
+ end
703
+
634
704
  def retry_all
635
705
  while size > 0
636
706
  each(&:retry)
@@ -655,17 +725,18 @@ module Sidekiq
655
725
  #
656
726
  class ProcessSet
657
727
  include Enumerable
728
+ include RedisScanner
658
729
 
659
730
  def initialize(clean_plz=true)
660
- self.class.cleanup if clean_plz
731
+ cleanup if clean_plz
661
732
  end
662
733
 
663
734
  # Cleans up dead processes recorded in Redis.
664
735
  # Returns the number of processes cleaned.
665
- def self.cleanup
736
+ def cleanup
666
737
  count = 0
667
738
  Sidekiq.redis do |conn|
668
- procs = conn.smembers('processes').sort
739
+ procs = sscan(conn, 'processes').sort
669
740
  heartbeats = conn.pipelined do
670
741
  procs.each do |key|
671
742
  conn.hget(key, 'info')
@@ -685,7 +756,7 @@ module Sidekiq
685
756
  end
686
757
 
687
758
  def each
688
- procs = Sidekiq.redis { |conn| conn.smembers('processes') }.sort
759
+ procs = Sidekiq.redis { |conn| sscan(conn, 'processes') }.sort
689
760
 
690
761
  Sidekiq.redis do |conn|
691
762
  # We're making a tradeoff here between consuming more memory instead of
@@ -698,6 +769,11 @@ module Sidekiq
698
769
  end
699
770
 
700
771
  result.each do |info, busy, at_s, quiet|
772
+ # If a process is stopped between when we query Redis for `procs` and
773
+ # when we query for `result`, we will have an item in `result` that is
774
+ # composed of `nil` values.
775
+ next if info.nil?
776
+
701
777
  hash = Sidekiq.load_json(info)
702
778
  yield Process.new(hash.merge('busy' => busy.to_i, 'beat' => at_s.to_f, 'quiet' => quiet))
703
779
  end
@@ -713,6 +789,18 @@ module Sidekiq
713
789
  def size
714
790
  Sidekiq.redis { |conn| conn.scard('processes') }
715
791
  end
792
+
793
+ # Returns the identity of the current cluster leader or "" if no leader.
794
+ # This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
795
+ # or Sidekiq Pro.
796
+ def leader
797
+ @leader ||= begin
798
+ x = Sidekiq.redis {|c| c.get("dear-leader") }
799
+ # need a non-falsy value so we can memoize
800
+ x = "" unless x
801
+ x
802
+ end
803
+ end
716
804
  end
717
805
 
718
806
  #
@@ -747,8 +835,12 @@ module Sidekiq
747
835
  @attribs[key]
748
836
  end
749
837
 
838
+ def identity
839
+ self['identity']
840
+ end
841
+
750
842
  def quiet!
751
- signal('USR1')
843
+ signal('TSTP')
752
844
  end
753
845
 
754
846
  def stop!
@@ -775,9 +867,6 @@ module Sidekiq
775
867
  end
776
868
  end
777
869
 
778
- def identity
779
- self['identity']
780
- end
781
870
  end
782
871
 
783
872
  ##
@@ -802,10 +891,11 @@ module Sidekiq
802
891
  #
803
892
  class Workers
804
893
  include Enumerable
894
+ include RedisScanner
805
895
 
806
896
  def each
807
897
  Sidekiq.redis do |conn|
808
- procs = conn.smembers('processes')
898
+ procs = sscan(conn, 'processes')
809
899
  procs.sort.each do |key|
810
900
  valid, workers = conn.pipelined do
811
901
  conn.exists(key)
@@ -827,7 +917,7 @@ module Sidekiq
827
917
  # which can easily get out of sync with crashy processes.
828
918
  def size
829
919
  Sidekiq.redis do |conn|
830
- procs = conn.smembers('processes')
920
+ procs = sscan(conn, 'processes')
831
921
  if procs.empty?
832
922
  0
833
923
  else