sidekiq 6.4.0 → 6.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changes.md +119 -1
- data/README.md +6 -1
- data/bin/sidekiq +3 -3
- data/bin/sidekiqload +70 -66
- data/bin/sidekiqmon +1 -1
- data/lib/sidekiq/api.rb +255 -100
- data/lib/sidekiq/cli.rb +60 -38
- data/lib/sidekiq/client.rb +44 -30
- data/lib/sidekiq/component.rb +65 -0
- data/lib/sidekiq/delay.rb +2 -2
- data/lib/sidekiq/extensions/action_mailer.rb +2 -2
- data/lib/sidekiq/extensions/active_record.rb +2 -2
- data/lib/sidekiq/extensions/class_methods.rb +2 -2
- data/lib/sidekiq/extensions/generic_proxy.rb +3 -3
- data/lib/sidekiq/fetch.rb +20 -18
- data/lib/sidekiq/job_logger.rb +15 -27
- data/lib/sidekiq/job_retry.rb +73 -52
- data/lib/sidekiq/job_util.rb +15 -9
- data/lib/sidekiq/launcher.rb +58 -54
- data/lib/sidekiq/logger.rb +8 -18
- data/lib/sidekiq/manager.rb +28 -25
- data/lib/sidekiq/metrics/deploy.rb +47 -0
- data/lib/sidekiq/metrics/query.rb +153 -0
- data/lib/sidekiq/metrics/shared.rb +94 -0
- data/lib/sidekiq/metrics/tracking.rb +134 -0
- data/lib/sidekiq/middleware/chain.rb +82 -38
- data/lib/sidekiq/middleware/current_attributes.rb +18 -12
- data/lib/sidekiq/middleware/i18n.rb +6 -4
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +2 -2
- data/lib/sidekiq/paginator.rb +17 -9
- data/lib/sidekiq/processor.rb +47 -41
- data/lib/sidekiq/rails.rb +19 -13
- data/lib/sidekiq/redis_client_adapter.rb +154 -0
- data/lib/sidekiq/redis_connection.rb +80 -49
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +53 -24
- data/lib/sidekiq/testing/inline.rb +4 -4
- data/lib/sidekiq/testing.rb +37 -36
- data/lib/sidekiq/transaction_aware_client.rb +45 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +3 -3
- data/lib/sidekiq/web/application.rb +21 -5
- data/lib/sidekiq/web/csrf_protection.rb +2 -2
- data/lib/sidekiq/web/helpers.rb +21 -8
- data/lib/sidekiq/web.rb +8 -4
- data/lib/sidekiq/worker.rb +26 -20
- data/lib/sidekiq.rb +107 -31
- data/sidekiq.gemspec +2 -2
- data/web/assets/javascripts/application.js +59 -26
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard.js +0 -17
- data/web/assets/javascripts/graph.js +16 -0
- data/web/assets/javascripts/metrics.js +262 -0
- data/web/assets/stylesheets/application.css +45 -1
- data/web/locales/el.yml +43 -19
- data/web/locales/en.yml +7 -0
- data/web/locales/ja.yml +7 -0
- data/web/locales/pt-br.yml +27 -9
- data/web/locales/zh-cn.yml +36 -11
- data/web/locales/zh-tw.yml +32 -7
- data/web/views/_nav.erb +1 -1
- data/web/views/_summary.erb +1 -1
- data/web/views/busy.erb +9 -4
- data/web/views/dashboard.erb +1 -0
- data/web/views/metrics.erb +69 -0
- data/web/views/metrics_for_job.erb +87 -0
- data/web/views/queue.erb +5 -1
- metadata +34 -9
- data/lib/sidekiq/exception_handler.rb +0 -27
- data/lib/sidekiq/util.rb +0 -108
@@ -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
|
-
|
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
|
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
|
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
|
-
|
107
|
+
redis_config.new_client
|
36
108
|
end
|
37
109
|
end
|
38
110
|
|
39
111
|
private
|
40
112
|
|
41
|
-
# Sidekiq needs
|
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
|
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
|
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
|
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
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "sidekiq"
|
4
|
-
require "sidekiq/
|
5
|
-
require "sidekiq/api"
|
4
|
+
require "sidekiq/component"
|
6
5
|
|
7
6
|
module Sidekiq
|
8
7
|
module Scheduled
|
@@ -52,8 +51,8 @@ module Sidekiq
|
|
52
51
|
@lua_zpopbyscore_sha = raw_conn.script(:load, LUA_ZPOPBYSCORE)
|
53
52
|
end
|
54
53
|
|
55
|
-
conn.evalsha(@lua_zpopbyscore_sha, keys
|
56
|
-
rescue
|
54
|
+
conn.evalsha(@lua_zpopbyscore_sha, keys, argv)
|
55
|
+
rescue RedisConnection.adapter::CommandError => e
|
57
56
|
raise unless e.message.start_with?("NOSCRIPT")
|
58
57
|
|
59
58
|
@lua_zpopbyscore_sha = nil
|
@@ -67,12 +66,13 @@ module Sidekiq
|
|
67
66
|
# just pops the job back onto its original queue so the
|
68
67
|
# workers can pick it up like any other job.
|
69
68
|
class Poller
|
70
|
-
include
|
69
|
+
include Sidekiq::Component
|
71
70
|
|
72
71
|
INITIAL_WAIT = 10
|
73
72
|
|
74
|
-
def initialize
|
75
|
-
@
|
73
|
+
def initialize(options)
|
74
|
+
@config = options
|
75
|
+
@enq = (options[:scheduled_enq] || Sidekiq::Scheduled::Enq).new
|
76
76
|
@sleeper = ConnectionPool::TimedStack.new
|
77
77
|
@done = false
|
78
78
|
@thread = nil
|
@@ -100,7 +100,7 @@ module Sidekiq
|
|
100
100
|
enqueue
|
101
101
|
wait
|
102
102
|
end
|
103
|
-
|
103
|
+
logger.info("Scheduler exiting...")
|
104
104
|
}
|
105
105
|
end
|
106
106
|
|
@@ -147,13 +147,16 @@ module Sidekiq
|
|
147
147
|
# As we run more processes, the scheduling interval average will approach an even spread
|
148
148
|
# between 0 and poll interval so we don't need this artifical boost.
|
149
149
|
#
|
150
|
-
|
150
|
+
count = process_count
|
151
|
+
interval = poll_interval_average(count)
|
152
|
+
|
153
|
+
if count < 10
|
151
154
|
# For small clusters, calculate a random interval that is ±50% the desired average.
|
152
|
-
|
155
|
+
interval * rand + interval.to_f / 2
|
153
156
|
else
|
154
157
|
# With 10+ processes, we should have enough randomness to get decent polling
|
155
158
|
# across the entire timespan
|
156
|
-
|
159
|
+
interval * rand
|
157
160
|
end
|
158
161
|
end
|
159
162
|
|
@@ -170,38 +173,64 @@ module Sidekiq
|
|
170
173
|
# the same time: the thundering herd problem.
|
171
174
|
#
|
172
175
|
# We only do this if poll_interval_average is unset (the default).
|
173
|
-
def poll_interval_average
|
174
|
-
|
176
|
+
def poll_interval_average(count)
|
177
|
+
@config[:poll_interval_average] || scaled_poll_interval(count)
|
175
178
|
end
|
176
179
|
|
177
180
|
# Calculates an average poll interval based on the number of known Sidekiq processes.
|
178
181
|
# This minimizes a single point of failure by dispersing check-ins but without taxing
|
179
182
|
# Redis if you run many Sidekiq processes.
|
180
|
-
def scaled_poll_interval
|
181
|
-
process_count *
|
183
|
+
def scaled_poll_interval(process_count)
|
184
|
+
process_count * @config[:average_scheduled_poll_interval]
|
182
185
|
end
|
183
186
|
|
184
187
|
def process_count
|
185
|
-
|
186
|
-
# expensive at scale. Cut it down by 90% with this counter.
|
187
|
-
# NB: This method is only called by the scheduler thread so we
|
188
|
-
# don't need to worry about the thread safety of +=.
|
189
|
-
pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
|
188
|
+
pcount = Sidekiq.redis { |conn| conn.scard("processes") }
|
190
189
|
pcount = 1 if pcount == 0
|
191
|
-
@count_calls += 1
|
192
190
|
pcount
|
193
191
|
end
|
194
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
|
+
|
195
219
|
def initial_wait
|
196
|
-
# Have all processes sleep between 5-15 seconds.
|
197
|
-
#
|
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
|
198
222
|
# of workers), and 5 random seconds to ensure they don't all hit Redis at the same time.
|
199
223
|
total = 0
|
200
|
-
total += INITIAL_WAIT unless
|
224
|
+
total += INITIAL_WAIT unless @config[:poll_interval_average]
|
201
225
|
total += (5 * rand)
|
202
226
|
|
203
227
|
@sleeper.pop(total)
|
204
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
|
205
234
|
end
|
206
235
|
end
|
207
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
|
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
|
19
|
-
# include Sidekiq::
|
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
|
-
#
|
27
|
+
# ExternalJob.perform_async
|
28
28
|
# assert_equal 1, $external_variable
|
29
29
|
#
|
30
30
|
Sidekiq::Testing.inline!
|