sidekiq 6.4.0 → 6.5.1
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.
- checksums.yaml +4 -4
- data/Changes.md +54 -1
- data/README.md +6 -1
- data/bin/sidekiq +3 -3
- data/bin/sidekiqload +70 -66
- data/bin/sidekiqmon +1 -1
- data/lib/sidekiq/.DS_Store +0 -0
- data/lib/sidekiq/api.rb +109 -78
- data/lib/sidekiq/cli.rb +47 -38
- data/lib/sidekiq/client.rb +42 -28
- data/lib/sidekiq/component.rb +64 -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 +18 -16
- data/lib/sidekiq/job_logger.rb +15 -27
- data/lib/sidekiq/job_retry.rb +29 -28
- data/lib/sidekiq/job_util.rb +15 -9
- data/lib/sidekiq/launcher.rb +54 -52
- data/lib/sidekiq/logger.rb +8 -18
- data/lib/sidekiq/manager.rb +28 -25
- data/lib/sidekiq/middleware/chain.rb +22 -13
- data/lib/sidekiq/middleware/current_attributes.rb +4 -0
- data/lib/sidekiq/middleware/i18n.rb +6 -4
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +1 -1
- data/lib/sidekiq/paginator.rb +8 -8
- data/lib/sidekiq/processor.rb +38 -38
- data/lib/sidekiq/rails.rb +15 -8
- data/lib/sidekiq/redis_client_adapter.rb +154 -0
- data/lib/sidekiq/redis_connection.rb +81 -48
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +11 -10
- 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/csrf_protection.rb +2 -2
- data/lib/sidekiq/web/helpers.rb +5 -5
- data/lib/sidekiq/web.rb +3 -3
- data/lib/sidekiq/worker.rb +20 -17
- data/lib/sidekiq.rb +98 -30
- data/web/assets/javascripts/application.js +58 -26
- data/web/assets/stylesheets/application.css +1 -0
- data/web/locales/pt-br.yml +27 -9
- data/web/views/_summary.erb +1 -1
- data/web/views/busy.erb +3 -3
- metadata +9 -5
- data/lib/sidekiq/exception_handler.rb +0 -27
- data/lib/sidekiq/util.rb +0 -108
data/lib/sidekiq/processor.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "sidekiq/util"
|
4
3
|
require "sidekiq/fetch"
|
5
4
|
require "sidekiq/job_logger"
|
6
5
|
require "sidekiq/job_retry"
|
@@ -11,33 +10,34 @@ module Sidekiq
|
|
11
10
|
#
|
12
11
|
# 1. fetches a job from Redis
|
13
12
|
# 2. executes the job
|
14
|
-
# a. instantiate the
|
13
|
+
# a. instantiate the job class
|
15
14
|
# b. run the middleware chain
|
16
15
|
# c. call #perform
|
17
16
|
#
|
18
|
-
# A Processor can exit due to shutdown
|
19
|
-
#
|
17
|
+
# A Processor can exit due to shutdown or due to
|
18
|
+
# an error during job execution.
|
20
19
|
#
|
21
20
|
# If an error occurs in the job execution, the
|
22
21
|
# Processor calls the Manager to create a new one
|
23
22
|
# to replace itself and exits.
|
24
23
|
#
|
25
24
|
class Processor
|
26
|
-
include
|
25
|
+
include Sidekiq::Component
|
27
26
|
|
28
27
|
attr_reader :thread
|
29
28
|
attr_reader :job
|
30
29
|
|
31
|
-
def initialize(
|
32
|
-
@
|
30
|
+
def initialize(options, &block)
|
31
|
+
@callback = block
|
33
32
|
@down = false
|
34
33
|
@done = false
|
35
34
|
@job = nil
|
36
35
|
@thread = nil
|
36
|
+
@config = options
|
37
37
|
@strategy = options[:fetch]
|
38
38
|
@reloader = options[:reloader] || proc { |&block| block.call }
|
39
39
|
@job_logger = (options[:job_logger] || Sidekiq::JobLogger).new
|
40
|
-
@retrier = Sidekiq::JobRetry.new
|
40
|
+
@retrier = Sidekiq::JobRetry.new(options)
|
41
41
|
end
|
42
42
|
|
43
43
|
def terminate(wait = false)
|
@@ -66,26 +66,26 @@ module Sidekiq
|
|
66
66
|
|
67
67
|
def run
|
68
68
|
process_one until @done
|
69
|
-
@
|
69
|
+
@callback.call(self)
|
70
70
|
rescue Sidekiq::Shutdown
|
71
|
-
@
|
71
|
+
@callback.call(self)
|
72
72
|
rescue Exception => ex
|
73
|
-
@
|
73
|
+
@callback.call(self, ex)
|
74
74
|
end
|
75
75
|
|
76
|
-
def process_one
|
76
|
+
def process_one(&block)
|
77
77
|
@job = fetch
|
78
78
|
process(@job) if @job
|
79
79
|
@job = nil
|
80
80
|
end
|
81
81
|
|
82
82
|
def get_one
|
83
|
-
|
83
|
+
uow = @strategy.retrieve_work
|
84
84
|
if @down
|
85
85
|
logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
|
86
86
|
@down = nil
|
87
87
|
end
|
88
|
-
|
88
|
+
uow
|
89
89
|
rescue Sidekiq::Shutdown
|
90
90
|
rescue => ex
|
91
91
|
handle_fetch_exception(ex)
|
@@ -130,10 +130,10 @@ module Sidekiq
|
|
130
130
|
# Effectively this block denotes a "unit of work" to Rails.
|
131
131
|
@reloader.call do
|
132
132
|
klass = constantize(job_hash["class"])
|
133
|
-
|
134
|
-
|
135
|
-
@retrier.local(
|
136
|
-
yield
|
133
|
+
inst = klass.new
|
134
|
+
inst.jid = job_hash["jid"]
|
135
|
+
@retrier.local(inst, jobstr, queue) do
|
136
|
+
yield inst
|
137
137
|
end
|
138
138
|
end
|
139
139
|
end
|
@@ -142,9 +142,9 @@ module Sidekiq
|
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
|
-
def process(
|
146
|
-
jobstr =
|
147
|
-
queue =
|
145
|
+
def process(uow)
|
146
|
+
jobstr = uow.job
|
147
|
+
queue = uow.queue_name
|
148
148
|
|
149
149
|
# Treat malformed JSON as a special case: job goes straight to the morgue.
|
150
150
|
job_hash = nil
|
@@ -154,14 +154,14 @@ module Sidekiq
|
|
154
154
|
handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
|
155
155
|
# we can't notify because the job isn't a valid hash payload.
|
156
156
|
DeadSet.new.kill(jobstr, notify_failure: false)
|
157
|
-
return
|
157
|
+
return uow.acknowledge
|
158
158
|
end
|
159
159
|
|
160
160
|
ack = false
|
161
161
|
begin
|
162
|
-
dispatch(job_hash, queue, jobstr) do |
|
163
|
-
|
164
|
-
execute_job(
|
162
|
+
dispatch(job_hash, queue, jobstr) do |inst|
|
163
|
+
@config.server_middleware.invoke(inst, job_hash, queue) do
|
164
|
+
execute_job(inst, job_hash["args"])
|
165
165
|
end
|
166
166
|
end
|
167
167
|
ack = true
|
@@ -186,14 +186,14 @@ module Sidekiq
|
|
186
186
|
if ack
|
187
187
|
# We don't want a shutdown signal to interrupt job acknowledgment.
|
188
188
|
Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
|
189
|
-
|
189
|
+
uow.acknowledge
|
190
190
|
end
|
191
191
|
end
|
192
192
|
end
|
193
193
|
end
|
194
194
|
|
195
|
-
def execute_job(
|
196
|
-
|
195
|
+
def execute_job(inst, cloned_args)
|
196
|
+
inst.perform(*cloned_args)
|
197
197
|
end
|
198
198
|
|
199
199
|
# Ruby doesn't provide atomic counters out of the box so we'll
|
@@ -219,39 +219,39 @@ module Sidekiq
|
|
219
219
|
end
|
220
220
|
|
221
221
|
# jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
|
222
|
-
class
|
222
|
+
class SharedWorkState
|
223
223
|
def initialize
|
224
|
-
@
|
224
|
+
@work_state = {}
|
225
225
|
@lock = Mutex.new
|
226
226
|
end
|
227
227
|
|
228
228
|
def set(tid, hash)
|
229
|
-
@lock.synchronize { @
|
229
|
+
@lock.synchronize { @work_state[tid] = hash }
|
230
230
|
end
|
231
231
|
|
232
232
|
def delete(tid)
|
233
|
-
@lock.synchronize { @
|
233
|
+
@lock.synchronize { @work_state.delete(tid) }
|
234
234
|
end
|
235
235
|
|
236
236
|
def dup
|
237
|
-
@lock.synchronize { @
|
237
|
+
@lock.synchronize { @work_state.dup }
|
238
238
|
end
|
239
239
|
|
240
240
|
def size
|
241
|
-
@lock.synchronize { @
|
241
|
+
@lock.synchronize { @work_state.size }
|
242
242
|
end
|
243
243
|
|
244
244
|
def clear
|
245
|
-
@lock.synchronize { @
|
245
|
+
@lock.synchronize { @work_state.clear }
|
246
246
|
end
|
247
247
|
end
|
248
248
|
|
249
249
|
PROCESSED = Counter.new
|
250
250
|
FAILURE = Counter.new
|
251
|
-
|
251
|
+
WORK_STATE = SharedWorkState.new
|
252
252
|
|
253
253
|
def stats(jobstr, queue)
|
254
|
-
|
254
|
+
WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
|
255
255
|
|
256
256
|
begin
|
257
257
|
yield
|
@@ -259,7 +259,7 @@ module Sidekiq
|
|
259
259
|
FAILURE.incr
|
260
260
|
raise
|
261
261
|
ensure
|
262
|
-
|
262
|
+
WORK_STATE.delete(tid)
|
263
263
|
PROCESSED.incr
|
264
264
|
end
|
265
265
|
end
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "sidekiq/
|
3
|
+
require "sidekiq/job"
|
4
4
|
|
5
5
|
module Sidekiq
|
6
6
|
class Rails < ::Rails::Engine
|
@@ -33,28 +33,35 @@ module Sidekiq
|
|
33
33
|
# end
|
34
34
|
initializer "sidekiq.active_job_integration" do
|
35
35
|
ActiveSupport.on_load(:active_job) do
|
36
|
-
include ::Sidekiq::
|
36
|
+
include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
40
|
initializer "sidekiq.rails_logger" do
|
41
|
-
Sidekiq.configure_server do |
|
42
|
-
# This is the integration code necessary so that if
|
41
|
+
Sidekiq.configure_server do |config|
|
42
|
+
# This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
|
43
43
|
# it will appear in the Sidekiq console with all of the job context. See #5021 and
|
44
44
|
# https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84
|
45
|
-
unless ::Rails.logger ==
|
46
|
-
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(
|
45
|
+
unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
46
|
+
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
config.before_configuration do
|
52
|
+
dep = ActiveSupport::Deprecation.new("7.0", "Sidekiq")
|
53
|
+
dep.deprecate_methods(Sidekiq.singleton_class,
|
54
|
+
default_worker_options: :default_job_options,
|
55
|
+
"default_worker_options=": :default_job_options=)
|
56
|
+
end
|
57
|
+
|
51
58
|
# This hook happens after all initializers are run, just before returning
|
52
59
|
# from config/environment.rb back to sidekiq/cli.rb.
|
53
60
|
#
|
54
61
|
# None of this matters on the client-side, only within the Sidekiq process itself.
|
55
62
|
config.after_initialize do
|
56
|
-
Sidekiq.configure_server do |
|
57
|
-
|
63
|
+
Sidekiq.configure_server do |config|
|
64
|
+
config[:reloader] = Sidekiq::Rails::Reloader.new
|
58
65
|
end
|
59
66
|
end
|
60
67
|
end
|
@@ -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,55 +5,20 @@ require "redis"
|
|
5
5
|
require "uri"
|
6
6
|
|
7
7
|
module Sidekiq
|
8
|
-
|
9
|
-
class
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
size = if symbolized_options[:size]
|
18
|
-
symbolized_options[:size]
|
19
|
-
elsif Sidekiq.server?
|
20
|
-
# Give ourselves plenty of connections. pool is lazy
|
21
|
-
# so we won't create them until we need them.
|
22
|
-
Sidekiq.options[:concurrency] + 5
|
23
|
-
elsif ENV["RAILS_MAX_THREADS"]
|
24
|
-
Integer(ENV["RAILS_MAX_THREADS"])
|
25
|
-
else
|
26
|
-
5
|
27
|
-
end
|
28
|
-
|
29
|
-
verify_sizing(size, Sidekiq.options[:concurrency]) if Sidekiq.server?
|
30
|
-
|
31
|
-
pool_timeout = symbolized_options[:pool_timeout] || 1
|
32
|
-
log_info(symbolized_options)
|
33
|
-
|
34
|
-
ConnectionPool.new(timeout: pool_timeout, size: size) do
|
35
|
-
build_client(symbolized_options)
|
36
|
-
end
|
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
|
37
16
|
end
|
38
17
|
|
39
|
-
|
18
|
+
def new_client
|
19
|
+
namespace = @options[:namespace]
|
40
20
|
|
41
|
-
|
42
|
-
#
|
43
|
-
# We need a connection for each Processor.
|
44
|
-
# We need a connection for Pro's real-time change listener
|
45
|
-
# We need a connection to various features to call Redis every few seconds:
|
46
|
-
# - the process heartbeat.
|
47
|
-
# - enterprise's leader election
|
48
|
-
# - enterprise's cron support
|
49
|
-
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)
|
21
|
+
client = Redis.new client_opts(@options)
|
57
22
|
if namespace
|
58
23
|
begin
|
59
24
|
require "redis/namespace"
|
@@ -68,6 +33,8 @@ module Sidekiq
|
|
68
33
|
end
|
69
34
|
end
|
70
35
|
|
36
|
+
private
|
37
|
+
|
71
38
|
def client_opts(options)
|
72
39
|
opts = options.dup
|
73
40
|
if opts[:namespace]
|
@@ -90,6 +57,72 @@ module Sidekiq
|
|
90
57
|
|
91
58
|
opts
|
92
59
|
end
|
60
|
+
end
|
61
|
+
|
62
|
+
@adapter = RedisAdapter
|
63
|
+
|
64
|
+
class << self
|
65
|
+
attr_reader :adapter
|
66
|
+
|
67
|
+
# RedisConnection.adapter = :redis
|
68
|
+
# RedisConnection.adapter = :redis_client
|
69
|
+
def adapter=(adapter)
|
70
|
+
raise "no" if adapter == self
|
71
|
+
result = case adapter
|
72
|
+
when :redis
|
73
|
+
RedisAdapter
|
74
|
+
when Class
|
75
|
+
adapter
|
76
|
+
else
|
77
|
+
require "sidekiq/#{adapter}_adapter"
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
@adapter = result if result
|
81
|
+
end
|
82
|
+
|
83
|
+
def create(options = {})
|
84
|
+
symbolized_options = options.transform_keys(&:to_sym)
|
85
|
+
|
86
|
+
if !symbolized_options[:url] && (u = determine_redis_provider)
|
87
|
+
symbolized_options[:url] = u
|
88
|
+
end
|
89
|
+
|
90
|
+
size = if symbolized_options[:size]
|
91
|
+
symbolized_options[:size]
|
92
|
+
elsif Sidekiq.server?
|
93
|
+
# Give ourselves plenty of connections. pool is lazy
|
94
|
+
# so we won't create them until we need them.
|
95
|
+
Sidekiq[:concurrency] + 5
|
96
|
+
elsif ENV["RAILS_MAX_THREADS"]
|
97
|
+
Integer(ENV["RAILS_MAX_THREADS"])
|
98
|
+
else
|
99
|
+
5
|
100
|
+
end
|
101
|
+
|
102
|
+
verify_sizing(size, Sidekiq[:concurrency]) if Sidekiq.server?
|
103
|
+
|
104
|
+
pool_timeout = symbolized_options[:pool_timeout] || 1
|
105
|
+
log_info(symbolized_options)
|
106
|
+
|
107
|
+
redis_config = adapter.new(symbolized_options)
|
108
|
+
ConnectionPool.new(timeout: pool_timeout, size: size) do
|
109
|
+
redis_config.new_client
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Sidekiq needs many concurrent Redis connections.
|
116
|
+
#
|
117
|
+
# We need a connection for each Processor.
|
118
|
+
# We need a connection for Pro's real-time change listener
|
119
|
+
# We need a connection to various features to call Redis every few seconds:
|
120
|
+
# - the process heartbeat.
|
121
|
+
# - enterprise's leader election
|
122
|
+
# - enterprise's cron support
|
123
|
+
def verify_sizing(size, concurrency)
|
124
|
+
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)
|
125
|
+
end
|
93
126
|
|
94
127
|
def log_info(options)
|
95
128
|
redacted = "REDACTED"
|
@@ -110,9 +143,9 @@ module Sidekiq
|
|
110
143
|
sentinel[:password] = redacted if sentinel[:password]
|
111
144
|
end
|
112
145
|
if Sidekiq.server?
|
113
|
-
Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with
|
146
|
+
Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with #{adapter.name} options #{scrubbed_options}")
|
114
147
|
else
|
115
|
-
Sidekiq.logger.debug("#{Sidekiq::NAME} client with
|
148
|
+
Sidekiq.logger.debug("#{Sidekiq::NAME} client with #{adapter.name} options #{scrubbed_options}")
|
116
149
|
end
|
117
150
|
end
|
118
151
|
|
@@ -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,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "sidekiq"
|
4
|
-
require "sidekiq/util"
|
5
4
|
require "sidekiq/api"
|
5
|
+
require "sidekiq/component"
|
6
6
|
|
7
7
|
module Sidekiq
|
8
8
|
module Scheduled
|
@@ -52,8 +52,8 @@ module Sidekiq
|
|
52
52
|
@lua_zpopbyscore_sha = raw_conn.script(:load, LUA_ZPOPBYSCORE)
|
53
53
|
end
|
54
54
|
|
55
|
-
conn.evalsha(@lua_zpopbyscore_sha, keys
|
56
|
-
rescue
|
55
|
+
conn.evalsha(@lua_zpopbyscore_sha, keys, argv)
|
56
|
+
rescue RedisConnection.adapter::CommandError => e
|
57
57
|
raise unless e.message.start_with?("NOSCRIPT")
|
58
58
|
|
59
59
|
@lua_zpopbyscore_sha = nil
|
@@ -67,12 +67,13 @@ module Sidekiq
|
|
67
67
|
# just pops the job back onto its original queue so the
|
68
68
|
# workers can pick it up like any other job.
|
69
69
|
class Poller
|
70
|
-
include
|
70
|
+
include Sidekiq::Component
|
71
71
|
|
72
72
|
INITIAL_WAIT = 10
|
73
73
|
|
74
|
-
def initialize
|
75
|
-
@
|
74
|
+
def initialize(options)
|
75
|
+
@config = options
|
76
|
+
@enq = (options[:scheduled_enq] || Sidekiq::Scheduled::Enq).new
|
76
77
|
@sleeper = ConnectionPool::TimedStack.new
|
77
78
|
@done = false
|
78
79
|
@thread = nil
|
@@ -100,7 +101,7 @@ module Sidekiq
|
|
100
101
|
enqueue
|
101
102
|
wait
|
102
103
|
end
|
103
|
-
|
104
|
+
logger.info("Scheduler exiting...")
|
104
105
|
}
|
105
106
|
end
|
106
107
|
|
@@ -171,14 +172,14 @@ module Sidekiq
|
|
171
172
|
#
|
172
173
|
# We only do this if poll_interval_average is unset (the default).
|
173
174
|
def poll_interval_average
|
174
|
-
|
175
|
+
@config[:poll_interval_average] ||= scaled_poll_interval
|
175
176
|
end
|
176
177
|
|
177
178
|
# Calculates an average poll interval based on the number of known Sidekiq processes.
|
178
179
|
# This minimizes a single point of failure by dispersing check-ins but without taxing
|
179
180
|
# Redis if you run many Sidekiq processes.
|
180
181
|
def scaled_poll_interval
|
181
|
-
process_count *
|
182
|
+
process_count * @config[:average_scheduled_poll_interval]
|
182
183
|
end
|
183
184
|
|
184
185
|
def process_count
|
@@ -197,7 +198,7 @@ module Sidekiq
|
|
197
198
|
# to give time for the heartbeat to register (if the poll interval is going to be calculated by the number
|
198
199
|
# of workers), and 5 random seconds to ensure they don't all hit Redis at the same time.
|
199
200
|
total = 0
|
200
|
-
total += INITIAL_WAIT unless
|
201
|
+
total += INITIAL_WAIT unless @config[:poll_interval_average]
|
201
202
|
total += (5 * rand)
|
202
203
|
|
203
204
|
@sleeper.pop(total)
|
@@ -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!
|