sidekiq 6.3.1 → 6.5.9

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

Potentially problematic release.


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

Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +134 -0
  3. data/LICENSE +3 -3
  4. data/README.md +7 -2
  5. data/bin/sidekiq +3 -3
  6. data/bin/sidekiqload +70 -66
  7. data/bin/sidekiqmon +1 -1
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +261 -104
  13. data/lib/sidekiq/cli.rb +62 -38
  14. data/lib/sidekiq/client.rb +47 -67
  15. data/lib/sidekiq/{util.rb → component.rb} +12 -42
  16. data/lib/sidekiq/delay.rb +3 -1
  17. data/lib/sidekiq/extensions/generic_proxy.rb +1 -1
  18. data/lib/sidekiq/fetch.rb +20 -18
  19. data/lib/sidekiq/job_logger.rb +15 -27
  20. data/lib/sidekiq/job_retry.rb +78 -55
  21. data/lib/sidekiq/job_util.rb +71 -0
  22. data/lib/sidekiq/launcher.rb +58 -54
  23. data/lib/sidekiq/logger.rb +8 -18
  24. data/lib/sidekiq/manager.rb +35 -34
  25. data/lib/sidekiq/metrics/deploy.rb +47 -0
  26. data/lib/sidekiq/metrics/query.rb +153 -0
  27. data/lib/sidekiq/metrics/shared.rb +94 -0
  28. data/lib/sidekiq/metrics/tracking.rb +134 -0
  29. data/lib/sidekiq/middleware/chain.rb +82 -38
  30. data/lib/sidekiq/middleware/current_attributes.rb +19 -8
  31. data/lib/sidekiq/middleware/i18n.rb +6 -4
  32. data/lib/sidekiq/middleware/modules.rb +21 -0
  33. data/lib/sidekiq/monitor.rb +2 -2
  34. data/lib/sidekiq/paginator.rb +17 -9
  35. data/lib/sidekiq/processor.rb +47 -41
  36. data/lib/sidekiq/rails.rb +15 -8
  37. data/lib/sidekiq/redis_client_adapter.rb +154 -0
  38. data/lib/sidekiq/redis_connection.rb +80 -49
  39. data/lib/sidekiq/ring_buffer.rb +29 -0
  40. data/lib/sidekiq/scheduled.rb +66 -27
  41. data/lib/sidekiq/testing/inline.rb +4 -4
  42. data/lib/sidekiq/testing.rb +37 -36
  43. data/lib/sidekiq/transaction_aware_client.rb +45 -0
  44. data/lib/sidekiq/version.rb +1 -1
  45. data/lib/sidekiq/web/action.rb +3 -3
  46. data/lib/sidekiq/web/application.rb +26 -7
  47. data/lib/sidekiq/web/csrf_protection.rb +2 -2
  48. data/lib/sidekiq/web/helpers.rb +21 -8
  49. data/lib/sidekiq/web.rb +8 -4
  50. data/lib/sidekiq/worker.rb +78 -19
  51. data/lib/sidekiq.rb +111 -30
  52. data/sidekiq.gemspec +2 -2
  53. data/web/assets/javascripts/application.js +58 -26
  54. data/web/assets/javascripts/chart.min.js +13 -0
  55. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  56. data/web/assets/javascripts/dashboard.js +0 -17
  57. data/web/assets/javascripts/graph.js +16 -0
  58. data/web/assets/javascripts/metrics.js +262 -0
  59. data/web/assets/stylesheets/application-dark.css +13 -17
  60. data/web/assets/stylesheets/application.css +48 -6
  61. data/web/locales/el.yml +43 -19
  62. data/web/locales/en.yml +7 -0
  63. data/web/locales/ja.yml +7 -0
  64. data/web/locales/pt-br.yml +27 -9
  65. data/web/locales/zh-cn.yml +36 -11
  66. data/web/locales/zh-tw.yml +32 -7
  67. data/web/views/_nav.erb +1 -1
  68. data/web/views/_summary.erb +1 -1
  69. data/web/views/busy.erb +9 -4
  70. data/web/views/dashboard.erb +1 -0
  71. data/web/views/metrics.erb +69 -0
  72. data/web/views/metrics_for_job.erb +87 -0
  73. data/web/views/queue.erb +5 -1
  74. metadata +39 -13
  75. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  76. data/lib/sidekiq/exception_handler.rb +0 -27
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "connection_pool"
4
+ require "redis_client"
5
+ require "redis_client/decorator"
6
+ require "uri"
7
+
8
+ module Sidekiq
9
+ class RedisClientAdapter
10
+ BaseError = RedisClient::Error
11
+ CommandError = RedisClient::CommandError
12
+
13
+ module CompatMethods
14
+ def info
15
+ @client.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h }
16
+ end
17
+
18
+ def evalsha(sha, keys, argv)
19
+ @client.call("EVALSHA", sha, keys.size, *keys, *argv)
20
+ end
21
+
22
+ def brpoplpush(*args)
23
+ @client.blocking_call(false, "BRPOPLPUSH", *args)
24
+ end
25
+
26
+ def brpop(*args)
27
+ @client.blocking_call(false, "BRPOP", *args)
28
+ end
29
+
30
+ def set(*args)
31
+ @client.call("SET", *args) { |r| r == "OK" }
32
+ end
33
+ ruby2_keywords :set if respond_to?(:ruby2_keywords, true)
34
+
35
+ def sismember(*args)
36
+ @client.call("SISMEMBER", *args) { |c| c > 0 }
37
+ end
38
+
39
+ def exists?(key)
40
+ @client.call("EXISTS", key) { |c| c > 0 }
41
+ end
42
+
43
+ private
44
+
45
+ def method_missing(*args, &block)
46
+ @client.call(*args, *block)
47
+ end
48
+ ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
49
+
50
+ def respond_to_missing?(name, include_private = false)
51
+ super # Appease the linter. We can't tell what is a valid command.
52
+ end
53
+ end
54
+
55
+ CompatClient = RedisClient::Decorator.create(CompatMethods)
56
+
57
+ class CompatClient
58
+ %i[scan sscan zscan hscan].each do |method|
59
+ alias_method :"#{method}_each", method
60
+ undef_method method
61
+ end
62
+
63
+ def disconnect!
64
+ @client.close
65
+ end
66
+
67
+ def connection
68
+ {id: @client.id}
69
+ end
70
+
71
+ def redis
72
+ self
73
+ end
74
+
75
+ def _client
76
+ @client
77
+ end
78
+
79
+ def message
80
+ yield nil, @queue.pop
81
+ end
82
+
83
+ # NB: this method does not return
84
+ def subscribe(chan)
85
+ @queue = ::Queue.new
86
+
87
+ pubsub = @client.pubsub
88
+ pubsub.call("subscribe", chan)
89
+
90
+ loop do
91
+ evt = pubsub.next_event
92
+ next if evt.nil?
93
+ next unless evt[0] == "message" && evt[1] == chan
94
+
95
+ (_, _, msg) = evt
96
+ @queue << msg
97
+ yield self
98
+ end
99
+ end
100
+ end
101
+
102
+ def initialize(options)
103
+ opts = client_opts(options)
104
+ @config = if opts.key?(:sentinels)
105
+ RedisClient.sentinel(**opts)
106
+ else
107
+ RedisClient.config(**opts)
108
+ end
109
+ end
110
+
111
+ def new_client
112
+ CompatClient.new(@config.new_client)
113
+ end
114
+
115
+ private
116
+
117
+ def client_opts(options)
118
+ opts = options.dup
119
+
120
+ if opts[:namespace]
121
+ Sidekiq.logger.error("Your Redis configuration uses the namespace '#{opts[:namespace]}' but this feature isn't supported by redis-client. " \
122
+ "Either use the redis adapter or remove the namespace.")
123
+ Kernel.exit(-127)
124
+ end
125
+
126
+ opts.delete(:size)
127
+ opts.delete(:pool_timeout)
128
+
129
+ if opts[:network_timeout]
130
+ opts[:timeout] = opts[:network_timeout]
131
+ opts.delete(:network_timeout)
132
+ end
133
+
134
+ if opts[:driver]
135
+ opts[:driver] = opts[:driver].to_sym
136
+ end
137
+
138
+ opts[:name] = opts.delete(:master_name) if opts.key?(:master_name)
139
+ opts[:role] = opts[:role].to_sym if opts.key?(:role)
140
+ opts.delete(:url) if opts.key?(:sentinels)
141
+
142
+ # Issue #3303, redis-rb will silently retry an operation.
143
+ # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
144
+ # is performed twice but I believe this is much, much rarer
145
+ # than the reconnect silently fixing a problem; we keep it
146
+ # on by default.
147
+ opts[:reconnect_attempts] ||= 1
148
+
149
+ opts
150
+ end
151
+ end
152
+ end
153
+
154
+ Sidekiq::RedisConnection.adapter = Sidekiq::RedisClientAdapter
@@ -5,8 +5,79 @@ require "redis"
5
5
  require "uri"
6
6
 
7
7
  module Sidekiq
8
- class RedisConnection
8
+ module RedisConnection
9
+ class RedisAdapter
10
+ BaseError = Redis::BaseError
11
+ CommandError = Redis::CommandError
12
+
13
+ def initialize(options)
14
+ warn("Usage of the 'redis' gem within Sidekiq itself is deprecated, Sidekiq 7.0 will only use the new, simpler 'redis-client' gem", caller) if ENV["SIDEKIQ_REDIS_CLIENT"] == "1"
15
+ @options = options
16
+ end
17
+
18
+ def new_client
19
+ namespace = @options[:namespace]
20
+
21
+ client = Redis.new client_opts(@options)
22
+ if namespace
23
+ begin
24
+ require "redis/namespace"
25
+ Redis::Namespace.new(namespace, redis: client)
26
+ rescue LoadError
27
+ Sidekiq.logger.error("Your Redis configuration uses the namespace '#{namespace}' but the redis-namespace gem is not included in the Gemfile." \
28
+ "Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.")
29
+ exit(-127)
30
+ end
31
+ else
32
+ client
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def client_opts(options)
39
+ opts = options.dup
40
+ if opts[:namespace]
41
+ opts.delete(:namespace)
42
+ end
43
+
44
+ if opts[:network_timeout]
45
+ opts[:timeout] = opts[:network_timeout]
46
+ opts.delete(:network_timeout)
47
+ end
48
+
49
+ # Issue #3303, redis-rb will silently retry an operation.
50
+ # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
51
+ # is performed twice but I believe this is much, much rarer
52
+ # than the reconnect silently fixing a problem; we keep it
53
+ # on by default.
54
+ opts[:reconnect_attempts] ||= 1
55
+
56
+ opts
57
+ end
58
+ end
59
+
60
+ @adapter = RedisAdapter
61
+
9
62
  class << self
63
+ attr_reader :adapter
64
+
65
+ # RedisConnection.adapter = :redis
66
+ # RedisConnection.adapter = :redis_client
67
+ def adapter=(adapter)
68
+ raise "no" if adapter == self
69
+ result = case adapter
70
+ when :redis
71
+ RedisAdapter
72
+ when Class
73
+ adapter
74
+ else
75
+ require "sidekiq/#{adapter}_adapter"
76
+ nil
77
+ end
78
+ @adapter = result if result
79
+ end
80
+
10
81
  def create(options = {})
11
82
  symbolized_options = options.transform_keys(&:to_sym)
12
83
 
@@ -19,26 +90,27 @@ module Sidekiq
19
90
  elsif Sidekiq.server?
20
91
  # Give ourselves plenty of connections. pool is lazy
21
92
  # so we won't create them until we need them.
22
- Sidekiq.options[:concurrency] + 5
93
+ Sidekiq[:concurrency] + 5
23
94
  elsif ENV["RAILS_MAX_THREADS"]
24
95
  Integer(ENV["RAILS_MAX_THREADS"])
25
96
  else
26
97
  5
27
98
  end
28
99
 
29
- verify_sizing(size, Sidekiq.options[:concurrency]) if Sidekiq.server?
100
+ verify_sizing(size, Sidekiq[:concurrency]) if Sidekiq.server?
30
101
 
31
102
  pool_timeout = symbolized_options[:pool_timeout] || 1
32
103
  log_info(symbolized_options)
33
104
 
105
+ redis_config = adapter.new(symbolized_options)
34
106
  ConnectionPool.new(timeout: pool_timeout, size: size) do
35
- build_client(symbolized_options)
107
+ redis_config.new_client
36
108
  end
37
109
  end
38
110
 
39
111
  private
40
112
 
41
- # Sidekiq needs a lot of concurrent Redis connections.
113
+ # Sidekiq needs many concurrent Redis connections.
42
114
  #
43
115
  # We need a connection for each Processor.
44
116
  # We need a connection for Pro's real-time change listener
@@ -47,48 +119,7 @@ module Sidekiq
47
119
  # - enterprise's leader election
48
120
  # - enterprise's cron support
49
121
  def verify_sizing(size, concurrency)
50
- raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size < (concurrency + 2)
51
- end
52
-
53
- def build_client(options)
54
- namespace = options[:namespace]
55
-
56
- client = Redis.new client_opts(options)
57
- if namespace
58
- begin
59
- require "redis/namespace"
60
- Redis::Namespace.new(namespace, redis: client)
61
- rescue LoadError
62
- Sidekiq.logger.error("Your Redis configuration uses the namespace '#{namespace}' but the redis-namespace gem is not included in the Gemfile." \
63
- "Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.")
64
- exit(-127)
65
- end
66
- else
67
- client
68
- end
69
- end
70
-
71
- def client_opts(options)
72
- opts = options.dup
73
- if opts[:namespace]
74
- opts.delete(:namespace)
75
- end
76
-
77
- if opts[:network_timeout]
78
- opts[:timeout] = opts[:network_timeout]
79
- opts.delete(:network_timeout)
80
- end
81
-
82
- opts[:driver] ||= Redis::Connection.drivers.last || "ruby"
83
-
84
- # Issue #3303, redis-rb will silently retry an operation.
85
- # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
86
- # is performed twice but I believe this is much, much rarer
87
- # than the reconnect silently fixing a problem; we keep it
88
- # on by default.
89
- opts[:reconnect_attempts] ||= 1
90
-
91
- opts
122
+ raise ArgumentError, "Your Redis connection pool is too small for Sidekiq. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size < (concurrency + 2)
92
123
  end
93
124
 
94
125
  def log_info(options)
@@ -110,9 +141,9 @@ module Sidekiq
110
141
  sentinel[:password] = redacted if sentinel[:password]
111
142
  end
112
143
  if Sidekiq.server?
113
- Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with redis options #{scrubbed_options}")
144
+ Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with #{adapter.name} options #{scrubbed_options}")
114
145
  else
115
- Sidekiq.logger.debug("#{Sidekiq::NAME} client with redis options #{scrubbed_options}")
146
+ Sidekiq.logger.debug("#{Sidekiq::NAME} client with #{adapter.name} options #{scrubbed_options}")
116
147
  end
117
148
  end
118
149
 
@@ -0,0 +1,29 @@
1
+ require "forwardable"
2
+
3
+ module Sidekiq
4
+ class RingBuffer
5
+ include Enumerable
6
+ extend Forwardable
7
+ def_delegators :@buf, :[], :each, :size
8
+
9
+ def initialize(size, default = 0)
10
+ @size = size
11
+ @buf = Array.new(size, default)
12
+ @index = 0
13
+ end
14
+
15
+ def <<(element)
16
+ @buf[@index % @size] = element
17
+ @index += 1
18
+ element
19
+ end
20
+
21
+ def buffer
22
+ @buf
23
+ end
24
+
25
+ def reset(default = 0)
26
+ @buf.fill(default)
27
+ end
28
+ end
29
+ end
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq"
4
- require "sidekiq/util"
5
- require "sidekiq/api"
4
+ require "sidekiq/component"
6
5
 
7
6
  module Sidekiq
8
7
  module Scheduled
@@ -19,10 +18,11 @@ module Sidekiq
19
18
  LUA
20
19
 
21
20
  def initialize
21
+ @done = false
22
22
  @lua_zpopbyscore_sha = nil
23
23
  end
24
24
 
25
- def enqueue_jobs(now = Time.now.to_f.to_s, sorted_sets = SETS)
25
+ def enqueue_jobs(sorted_sets = SETS)
26
26
  # A job's "score" in Redis is the time at which it should be processed.
27
27
  # Just check Redis for the set of jobs with a timestamp before now.
28
28
  Sidekiq.redis do |conn|
@@ -31,7 +31,7 @@ module Sidekiq
31
31
  # We need to go through the list one at a time to reduce the risk of something
32
32
  # going wrong between the time jobs are popped from the scheduled queue and when
33
33
  # they are pushed onto a work queue and losing the jobs.
34
- while (job = zpopbyscore(conn, keys: [sorted_set], argv: [now]))
34
+ while !@done && (job = zpopbyscore(conn, keys: [sorted_set], argv: [Time.now.to_f.to_s]))
35
35
  Sidekiq::Client.push(Sidekiq.load_json(job))
36
36
  Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
37
37
  end
@@ -39,13 +39,20 @@ module Sidekiq
39
39
  end
40
40
  end
41
41
 
42
+ def terminate
43
+ @done = true
44
+ end
45
+
42
46
  private
43
47
 
44
48
  def zpopbyscore(conn, keys: nil, argv: nil)
45
- @lua_zpopbyscore_sha = conn.script(:load, LUA_ZPOPBYSCORE) if @lua_zpopbyscore_sha.nil?
49
+ if @lua_zpopbyscore_sha.nil?
50
+ raw_conn = conn.respond_to?(:redis) ? conn.redis : conn
51
+ @lua_zpopbyscore_sha = raw_conn.script(:load, LUA_ZPOPBYSCORE)
52
+ end
46
53
 
47
- conn.evalsha(@lua_zpopbyscore_sha, keys: keys, argv: argv)
48
- rescue Redis::CommandError => e
54
+ conn.evalsha(@lua_zpopbyscore_sha, keys, argv)
55
+ rescue RedisConnection.adapter::CommandError => e
49
56
  raise unless e.message.start_with?("NOSCRIPT")
50
57
 
51
58
  @lua_zpopbyscore_sha = nil
@@ -59,12 +66,13 @@ module Sidekiq
59
66
  # just pops the job back onto its original queue so the
60
67
  # workers can pick it up like any other job.
61
68
  class Poller
62
- include Util
69
+ include Sidekiq::Component
63
70
 
64
71
  INITIAL_WAIT = 10
65
72
 
66
- def initialize
67
- @enq = (Sidekiq.options[:scheduled_enq] || Sidekiq::Scheduled::Enq).new
73
+ def initialize(options)
74
+ @config = options
75
+ @enq = (options[:scheduled_enq] || Sidekiq::Scheduled::Enq).new
68
76
  @sleeper = ConnectionPool::TimedStack.new
69
77
  @done = false
70
78
  @thread = nil
@@ -74,6 +82,8 @@ module Sidekiq
74
82
  # Shut down this instance, will pause until the thread is dead.
75
83
  def terminate
76
84
  @done = true
85
+ @enq.terminate if @enq.respond_to?(:terminate)
86
+
77
87
  if @thread
78
88
  t = @thread
79
89
  @thread = nil
@@ -90,7 +100,7 @@ module Sidekiq
90
100
  enqueue
91
101
  wait
92
102
  end
93
- Sidekiq.logger.info("Scheduler exiting...")
103
+ logger.info("Scheduler exiting...")
94
104
  }
95
105
  end
96
106
 
@@ -137,13 +147,16 @@ module Sidekiq
137
147
  # As we run more processes, the scheduling interval average will approach an even spread
138
148
  # between 0 and poll interval so we don't need this artifical boost.
139
149
  #
140
- if process_count < 10
150
+ count = process_count
151
+ interval = poll_interval_average(count)
152
+
153
+ if count < 10
141
154
  # For small clusters, calculate a random interval that is ±50% the desired average.
142
- poll_interval_average * rand + poll_interval_average.to_f / 2
155
+ interval * rand + interval.to_f / 2
143
156
  else
144
157
  # With 10+ processes, we should have enough randomness to get decent polling
145
158
  # across the entire timespan
146
- poll_interval_average * rand
159
+ interval * rand
147
160
  end
148
161
  end
149
162
 
@@ -160,38 +173,64 @@ module Sidekiq
160
173
  # the same time: the thundering herd problem.
161
174
  #
162
175
  # We only do this if poll_interval_average is unset (the default).
163
- def poll_interval_average
164
- Sidekiq.options[:poll_interval_average] ||= scaled_poll_interval
176
+ def poll_interval_average(count)
177
+ @config[:poll_interval_average] || scaled_poll_interval(count)
165
178
  end
166
179
 
167
180
  # Calculates an average poll interval based on the number of known Sidekiq processes.
168
181
  # This minimizes a single point of failure by dispersing check-ins but without taxing
169
182
  # Redis if you run many Sidekiq processes.
170
- def scaled_poll_interval
171
- process_count * Sidekiq.options[:average_scheduled_poll_interval]
183
+ def scaled_poll_interval(process_count)
184
+ process_count * @config[:average_scheduled_poll_interval]
172
185
  end
173
186
 
174
187
  def process_count
175
- # The work buried within Sidekiq::ProcessSet#cleanup can be
176
- # expensive at scale. Cut it down by 90% with this counter.
177
- # NB: This method is only called by the scheduler thread so we
178
- # don't need to worry about the thread safety of +=.
179
- pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
188
+ pcount = Sidekiq.redis { |conn| conn.scard("processes") }
180
189
  pcount = 1 if pcount == 0
181
- @count_calls += 1
182
190
  pcount
183
191
  end
184
192
 
193
+ # A copy of Sidekiq::ProcessSet#cleanup because server
194
+ # should never depend on sidekiq/api.
195
+ def cleanup
196
+ # dont run cleanup more than once per minute
197
+ return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1", nx: true, ex: 60) }
198
+
199
+ count = 0
200
+ Sidekiq.redis do |conn|
201
+ procs = conn.sscan_each("processes").to_a
202
+ heartbeats = conn.pipelined { |pipeline|
203
+ procs.each do |key|
204
+ pipeline.hget(key, "info")
205
+ end
206
+ }
207
+
208
+ # the hash named key has an expiry of 60 seconds.
209
+ # if it's not found, that means the process has not reported
210
+ # in to Redis and probably died.
211
+ to_prune = procs.select.with_index { |proc, i|
212
+ heartbeats[i].nil?
213
+ }
214
+ count = conn.srem("processes", to_prune) unless to_prune.empty?
215
+ end
216
+ count
217
+ end
218
+
185
219
  def initial_wait
186
- # Have all processes sleep between 5-15 seconds. 10 seconds
187
- # to give time for the heartbeat to register (if the poll interval is going to be calculated by the number
220
+ # Have all processes sleep between 5-15 seconds. 10 seconds to give time for
221
+ # the heartbeat to register (if the poll interval is going to be calculated by the number
188
222
  # of workers), and 5 random seconds to ensure they don't all hit Redis at the same time.
189
223
  total = 0
190
- total += INITIAL_WAIT unless Sidekiq.options[:poll_interval_average]
224
+ total += INITIAL_WAIT unless @config[:poll_interval_average]
191
225
  total += (5 * rand)
192
226
 
193
227
  @sleeper.pop(total)
194
228
  rescue Timeout::Error
229
+ ensure
230
+ # periodically clean out the `processes` set in Redis which can collect
231
+ # references to dead processes over time. The process count affects how
232
+ # often we scan for scheduled jobs.
233
+ cleanup
195
234
  end
196
235
  end
197
236
  end
@@ -4,7 +4,7 @@ require "sidekiq/testing"
4
4
 
5
5
  ##
6
6
  # The Sidekiq inline infrastructure overrides perform_async so that it
7
- # actually calls perform instead. This allows workers to be run inline in a
7
+ # actually calls perform instead. This allows jobs to be run inline in a
8
8
  # testing environment.
9
9
  #
10
10
  # This is similar to `Resque.inline = true` functionality.
@@ -15,8 +15,8 @@ require "sidekiq/testing"
15
15
  #
16
16
  # $external_variable = 0
17
17
  #
18
- # class ExternalWorker
19
- # include Sidekiq::Worker
18
+ # class ExternalJob
19
+ # include Sidekiq::Job
20
20
  #
21
21
  # def perform
22
22
  # $external_variable = 1
@@ -24,7 +24,7 @@ require "sidekiq/testing"
24
24
  # end
25
25
  #
26
26
  # assert_equal 0, $external_variable
27
- # ExternalWorker.perform_async
27
+ # ExternalJob.perform_async
28
28
  # assert_equal 1, $external_variable
29
29
  #
30
30
  Sidekiq::Testing.inline!