sidekiq 5.2.9 → 6.4.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 +318 -1
- data/LICENSE +3 -3
- data/README.md +23 -34
- data/bin/sidekiq +27 -3
- data/bin/sidekiqload +67 -61
- data/bin/sidekiqmon +8 -0
- data/lib/generators/sidekiq/job_generator.rb +57 -0
- data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
- data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
- data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
- data/lib/sidekiq/api.rb +335 -267
- data/lib/sidekiq/cli.rb +164 -182
- data/lib/sidekiq/client.rb +58 -61
- data/lib/sidekiq/delay.rb +7 -6
- data/lib/sidekiq/exception_handler.rb +10 -12
- data/lib/sidekiq/extensions/action_mailer.rb +13 -22
- data/lib/sidekiq/extensions/active_record.rb +13 -10
- data/lib/sidekiq/extensions/class_methods.rb +14 -11
- data/lib/sidekiq/extensions/generic_proxy.rb +6 -4
- data/lib/sidekiq/fetch.rb +40 -32
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +33 -7
- data/lib/sidekiq/job_retry.rb +70 -71
- data/lib/sidekiq/job_util.rb +65 -0
- data/lib/sidekiq/launcher.rb +161 -71
- data/lib/sidekiq/logger.rb +170 -0
- data/lib/sidekiq/manager.rb +17 -21
- data/lib/sidekiq/middleware/chain.rb +20 -8
- data/lib/sidekiq/middleware/current_attributes.rb +57 -0
- data/lib/sidekiq/middleware/i18n.rb +5 -7
- data/lib/sidekiq/monitor.rb +133 -0
- data/lib/sidekiq/paginator.rb +20 -16
- data/lib/sidekiq/processor.rb +71 -70
- data/lib/sidekiq/rails.rb +40 -37
- data/lib/sidekiq/redis_connection.rb +48 -48
- data/lib/sidekiq/scheduled.rb +62 -28
- data/lib/sidekiq/sd_notify.rb +149 -0
- data/lib/sidekiq/systemd.rb +24 -0
- data/lib/sidekiq/testing/inline.rb +2 -1
- data/lib/sidekiq/testing.rb +36 -27
- data/lib/sidekiq/util.rb +57 -15
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web/action.rb +15 -11
- data/lib/sidekiq/web/application.rb +88 -75
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +109 -92
- data/lib/sidekiq/web/router.rb +23 -19
- data/lib/sidekiq/web.rb +61 -105
- data/lib/sidekiq/worker.rb +247 -105
- data/lib/sidekiq.rb +77 -44
- data/sidekiq.gemspec +23 -16
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +83 -64
- data/web/assets/javascripts/dashboard.js +54 -73
- data/web/assets/stylesheets/application-dark.css +143 -0
- data/web/assets/stylesheets/application-rtl.css +0 -4
- data/web/assets/stylesheets/application.css +45 -232
- data/web/locales/ar.yml +8 -2
- data/web/locales/de.yml +14 -2
- data/web/locales/en.yml +6 -1
- data/web/locales/es.yml +18 -2
- data/web/locales/fr.yml +10 -3
- data/web/locales/ja.yml +7 -1
- data/web/locales/lt.yml +83 -0
- data/web/locales/pl.yml +4 -4
- data/web/locales/ru.yml +4 -0
- data/web/locales/vi.yml +83 -0
- data/web/views/_footer.erb +1 -1
- data/web/views/_job_info.erb +3 -2
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +54 -20
- data/web/views/dashboard.erb +22 -14
- data/web/views/dead.erb +3 -3
- data/web/views/layout.erb +3 -1
- data/web/views/morgue.erb +9 -6
- data/web/views/queue.erb +19 -10
- data/web/views/queues.erb +10 -2
- data/web/views/retries.erb +11 -8
- data/web/views/retry.erb +3 -3
- data/web/views/scheduled.erb +5 -2
- metadata +34 -64
- data/.circleci/config.yml +0 -61
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -11
- data/.gitignore +0 -15
- data/.travis.yml +0 -11
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/5.0-Upgrade.md +0 -56
- data/COMM-LICENSE +0 -97
- data/Ent-Changes.md +0 -238
- data/Gemfile +0 -23
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-3.0-Upgrade.md +0 -44
- data/Pro-4.0-Upgrade.md +0 -35
- data/Pro-Changes.md +0 -759
- data/Rakefile +0 -9
- data/bin/sidekiqctl +0 -20
- data/code_of_conduct.md +0 -50
- data/lib/generators/sidekiq/worker_generator.rb +0 -49
- data/lib/sidekiq/core_ext.rb +0 -1
- data/lib/sidekiq/ctl.rb +0 -221
- data/lib/sidekiq/logging.rb +0 -122
- data/lib/sidekiq/middleware/server/active_record.rb +0 -23
data/lib/sidekiq/processor.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
2
|
+
|
3
|
+
require "sidekiq/util"
|
4
|
+
require "sidekiq/fetch"
|
5
|
+
require "sidekiq/job_logger"
|
6
|
+
require "sidekiq/job_retry"
|
7
7
|
|
8
8
|
module Sidekiq
|
9
9
|
##
|
@@ -23,33 +23,32 @@ module Sidekiq
|
|
23
23
|
# to replace itself and exits.
|
24
24
|
#
|
25
25
|
class Processor
|
26
|
-
|
27
26
|
include Util
|
28
27
|
|
29
28
|
attr_reader :thread
|
30
29
|
attr_reader :job
|
31
30
|
|
32
|
-
def initialize(mgr)
|
31
|
+
def initialize(mgr, options)
|
33
32
|
@mgr = mgr
|
34
33
|
@down = false
|
35
34
|
@done = false
|
36
35
|
@job = nil
|
37
36
|
@thread = nil
|
38
|
-
@strategy =
|
39
|
-
@reloader =
|
40
|
-
@
|
37
|
+
@strategy = options[:fetch]
|
38
|
+
@reloader = options[:reloader] || proc { |&block| block.call }
|
39
|
+
@job_logger = (options[:job_logger] || Sidekiq::JobLogger).new
|
41
40
|
@retrier = Sidekiq::JobRetry.new
|
42
41
|
end
|
43
42
|
|
44
|
-
def terminate(wait=false)
|
43
|
+
def terminate(wait = false)
|
45
44
|
@done = true
|
46
|
-
return
|
45
|
+
return unless @thread
|
47
46
|
@thread.value if wait
|
48
47
|
end
|
49
48
|
|
50
|
-
def kill(wait=false)
|
49
|
+
def kill(wait = false)
|
51
50
|
@done = true
|
52
|
-
return
|
51
|
+
return unless @thread
|
53
52
|
# unlike the other actors, terminate does not wait
|
54
53
|
# for the thread to finish because we don't know how
|
55
54
|
# long the job will take to finish. Instead we
|
@@ -66,16 +65,12 @@ module Sidekiq
|
|
66
65
|
private unless $TESTING
|
67
66
|
|
68
67
|
def run
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
@mgr.processor_stopped(self)
|
76
|
-
rescue Exception => ex
|
77
|
-
@mgr.processor_died(self, ex)
|
78
|
-
end
|
68
|
+
process_one until @done
|
69
|
+
@mgr.processor_stopped(self)
|
70
|
+
rescue Sidekiq::Shutdown
|
71
|
+
@mgr.processor_stopped(self)
|
72
|
+
rescue Exception => ex
|
73
|
+
@mgr.processor_died(self, ex)
|
79
74
|
end
|
80
75
|
|
81
76
|
def process_one
|
@@ -85,14 +80,15 @@ module Sidekiq
|
|
85
80
|
end
|
86
81
|
|
87
82
|
def get_one
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
rescue Sidekiq::Shutdown
|
93
|
-
rescue => ex
|
94
|
-
handle_fetch_exception(ex)
|
83
|
+
work = @strategy.retrieve_work
|
84
|
+
if @down
|
85
|
+
logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
|
86
|
+
@down = nil
|
95
87
|
end
|
88
|
+
work
|
89
|
+
rescue Sidekiq::Shutdown
|
90
|
+
rescue => ex
|
91
|
+
handle_fetch_exception(ex)
|
96
92
|
end
|
97
93
|
|
98
94
|
def fetch
|
@@ -106,7 +102,7 @@ module Sidekiq
|
|
106
102
|
end
|
107
103
|
|
108
104
|
def handle_fetch_exception(ex)
|
109
|
-
|
105
|
+
unless @down
|
110
106
|
@down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
111
107
|
logger.error("Error fetching job: #{ex}")
|
112
108
|
handle_exception(ex)
|
@@ -115,25 +111,28 @@ module Sidekiq
|
|
115
111
|
nil
|
116
112
|
end
|
117
113
|
|
118
|
-
def dispatch(job_hash, queue)
|
114
|
+
def dispatch(job_hash, queue, jobstr)
|
119
115
|
# since middleware can mutate the job hash
|
120
|
-
# we clone
|
116
|
+
# we need to clone it to report the original
|
121
117
|
# job structure to the Web UI
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
118
|
+
# or to push back to redis when retrying.
|
119
|
+
# To avoid costly and, most of the time, useless cloning here,
|
120
|
+
# we pass original String of JSON to respected methods
|
121
|
+
# to re-parse it there if we need access to the original, untouched job
|
122
|
+
|
123
|
+
@job_logger.prepare(job_hash) do
|
124
|
+
@retrier.global(jobstr, queue) do
|
125
|
+
@job_logger.call(job_hash, queue) do
|
126
|
+
stats(jobstr, queue) do
|
128
127
|
# Rails 5 requires a Reloader to wrap code execution. In order to
|
129
128
|
# constantize the worker and instantiate an instance, we have to call
|
130
129
|
# the Reloader. It handles code loading, db connection management, etc.
|
131
130
|
# Effectively this block denotes a "unit of work" to Rails.
|
132
131
|
@reloader.call do
|
133
|
-
klass
|
132
|
+
klass = constantize(job_hash["class"])
|
134
133
|
worker = klass.new
|
135
|
-
worker.jid = job_hash[
|
136
|
-
@retrier.local(worker,
|
134
|
+
worker.jid = job_hash["jid"]
|
135
|
+
@retrier.local(worker, jobstr, queue) do
|
137
136
|
yield worker
|
138
137
|
end
|
139
138
|
end
|
@@ -152,39 +151,44 @@ module Sidekiq
|
|
152
151
|
begin
|
153
152
|
job_hash = Sidekiq.load_json(jobstr)
|
154
153
|
rescue => ex
|
155
|
-
handle_exception(ex, {
|
154
|
+
handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
|
156
155
|
# we can't notify because the job isn't a valid hash payload.
|
157
156
|
DeadSet.new.kill(jobstr, notify_failure: false)
|
158
157
|
return work.acknowledge
|
159
158
|
end
|
160
159
|
|
161
|
-
ack =
|
160
|
+
ack = false
|
162
161
|
begin
|
163
|
-
dispatch(job_hash, queue) do |worker|
|
162
|
+
dispatch(job_hash, queue, jobstr) do |worker|
|
164
163
|
Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
|
165
|
-
execute_job(worker,
|
164
|
+
execute_job(worker, job_hash["args"])
|
166
165
|
end
|
167
166
|
end
|
167
|
+
ack = true
|
168
168
|
rescue Sidekiq::Shutdown
|
169
169
|
# Had to force kill this job because it didn't finish
|
170
170
|
# within the timeout. Don't acknowledge the work since
|
171
171
|
# we didn't properly finish it.
|
172
|
-
ack = false
|
173
172
|
rescue Sidekiq::JobRetry::Handled => h
|
174
173
|
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
175
174
|
# signals that we created a retry successfully. We can acknowlege the job.
|
176
|
-
|
177
|
-
|
175
|
+
ack = true
|
176
|
+
e = h.cause || h
|
177
|
+
handle_exception(e, {context: "Job raised exception", job: job_hash, jobstr: jobstr})
|
178
178
|
raise e
|
179
179
|
rescue Exception => ex
|
180
180
|
# Unexpected error! This is very bad and indicates an exception that got past
|
181
181
|
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
182
182
|
# so it can be rescued when using Sidekiq Pro.
|
183
|
-
|
184
|
-
|
185
|
-
raise e
|
183
|
+
handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
|
184
|
+
raise ex
|
186
185
|
ensure
|
187
|
-
|
186
|
+
if ack
|
187
|
+
# We don't want a shutdown signal to interrupt job acknowledgment.
|
188
|
+
Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
|
189
|
+
work.acknowledge
|
190
|
+
end
|
191
|
+
end
|
188
192
|
end
|
189
193
|
end
|
190
194
|
|
@@ -201,12 +205,16 @@ module Sidekiq
|
|
201
205
|
@lock = Mutex.new
|
202
206
|
end
|
203
207
|
|
204
|
-
def incr(amount=1)
|
205
|
-
@lock.synchronize { @value
|
208
|
+
def incr(amount = 1)
|
209
|
+
@lock.synchronize { @value += amount }
|
206
210
|
end
|
207
211
|
|
208
212
|
def reset
|
209
|
-
@lock.synchronize {
|
213
|
+
@lock.synchronize {
|
214
|
+
val = @value
|
215
|
+
@value = 0
|
216
|
+
val
|
217
|
+
}
|
210
218
|
end
|
211
219
|
end
|
212
220
|
|
@@ -242,9 +250,8 @@ module Sidekiq
|
|
242
250
|
FAILURE = Counter.new
|
243
251
|
WORKER_STATE = SharedWorkerState.new
|
244
252
|
|
245
|
-
def stats(
|
246
|
-
tid
|
247
|
-
WORKER_STATE.set(tid, {:queue => queue, :payload => job_hash, :run_at => Time.now.to_i })
|
253
|
+
def stats(jobstr, queue)
|
254
|
+
WORKER_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
|
248
255
|
|
249
256
|
begin
|
250
257
|
yield
|
@@ -257,23 +264,17 @@ module Sidekiq
|
|
257
264
|
end
|
258
265
|
end
|
259
266
|
|
260
|
-
# Deep clone the arguments passed to the worker so that if
|
261
|
-
# the job fails, what is pushed back onto Redis hasn't
|
262
|
-
# been mutated by the worker.
|
263
|
-
def cloned(thing)
|
264
|
-
Marshal.load(Marshal.dump(thing))
|
265
|
-
end
|
266
|
-
|
267
267
|
def constantize(str)
|
268
|
-
|
268
|
+
return Object.const_get(str) unless str.include?("::")
|
269
|
+
|
270
|
+
names = str.split("::")
|
269
271
|
names.shift if names.empty? || names.first.empty?
|
270
272
|
|
271
273
|
names.inject(Object) do |constant, name|
|
272
274
|
# the false flag limits search for name to under the constant namespace
|
273
275
|
# which mimics Rails' behaviour
|
274
|
-
constant.
|
276
|
+
constant.const_get(name, false)
|
275
277
|
end
|
276
278
|
end
|
277
|
-
|
278
279
|
end
|
279
280
|
end
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -1,38 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "sidekiq/worker"
|
4
|
+
|
3
5
|
module Sidekiq
|
4
6
|
class Rails < ::Rails::Engine
|
5
|
-
# We need to setup this up before any application configuration which might
|
6
|
-
# change Sidekiq middleware.
|
7
|
-
#
|
8
|
-
# This hook happens after `Rails::Application` is inherited within
|
9
|
-
# config/application.rb and before config is touched, usually within the
|
10
|
-
# class block. Definitely before config/environments/*.rb and
|
11
|
-
# config/initializers/*.rb.
|
12
|
-
config.before_configuration do
|
13
|
-
if ::Rails::VERSION::MAJOR < 5 && defined?(::ActiveRecord)
|
14
|
-
Sidekiq.server_middleware do |chain|
|
15
|
-
require 'sidekiq/middleware/server/active_record'
|
16
|
-
chain.add Sidekiq::Middleware::Server::ActiveRecord
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
config.after_initialize do
|
22
|
-
# This hook happens after all initializers are run, just before returning
|
23
|
-
# from config/environment.rb back to sidekiq/cli.rb.
|
24
|
-
# We have to add the reloader after initialize to see if cache_classes has
|
25
|
-
# been turned on.
|
26
|
-
#
|
27
|
-
# None of this matters on the client-side, only within the Sidekiq process itself.
|
28
|
-
#
|
29
|
-
Sidekiq.configure_server do |_|
|
30
|
-
if ::Rails::VERSION::MAJOR >= 5
|
31
|
-
Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
7
|
class Reloader
|
37
8
|
def initialize(app = ::Rails.application)
|
38
9
|
@app = app
|
@@ -48,11 +19,43 @@ module Sidekiq
|
|
48
19
|
"#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
|
49
20
|
end
|
50
21
|
end
|
51
|
-
end if defined?(::Rails)
|
52
|
-
end
|
53
22
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
23
|
+
# By including the Options module, we allow AJs to directly control sidekiq features
|
24
|
+
# via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
|
25
|
+
# AJ retries don't show up in the Sidekiq UI Retries tab, save any error data, can't be
|
26
|
+
# manually retried, don't automatically die, etc.
|
27
|
+
#
|
28
|
+
# class SomeJob < ActiveJob::Base
|
29
|
+
# queue_as :default
|
30
|
+
# sidekiq_options retry: 3, backtrace: 10
|
31
|
+
# def perform
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
initializer "sidekiq.active_job_integration" do
|
35
|
+
ActiveSupport.on_load(:active_job) do
|
36
|
+
include ::Sidekiq::Worker::Options unless respond_to?(:sidekiq_options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
initializer "sidekiq.rails_logger" do
|
41
|
+
Sidekiq.configure_server do |_|
|
42
|
+
# This is the integration code necessary so that if code uses `Rails.logger.info "Hello"`,
|
43
|
+
# it will appear in the Sidekiq console with all of the job context. See #5021 and
|
44
|
+
# https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84
|
45
|
+
unless ::Rails.logger == ::Sidekiq.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
46
|
+
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(::Sidekiq.logger))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# This hook happens after all initializers are run, just before returning
|
52
|
+
# from config/environment.rb back to sidekiq/cli.rb.
|
53
|
+
#
|
54
|
+
# None of this matters on the client-side, only within the Sidekiq process itself.
|
55
|
+
config.after_initialize do
|
56
|
+
Sidekiq.configure_server do |_|
|
57
|
+
Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
58
61
|
end
|
@@ -1,37 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
2
|
+
|
3
|
+
require "connection_pool"
|
4
|
+
require "redis"
|
5
|
+
require "uri"
|
5
6
|
|
6
7
|
module Sidekiq
|
7
8
|
class RedisConnection
|
8
9
|
class << self
|
10
|
+
def create(options = {})
|
11
|
+
symbolized_options = options.transform_keys(&:to_sym)
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
options[key.to_sym] = options.delete(key)
|
13
|
+
if !symbolized_options[:url] && (u = determine_redis_provider)
|
14
|
+
symbolized_options[:url] = u
|
13
15
|
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
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
|
27
28
|
|
28
29
|
verify_sizing(size, Sidekiq.options[:concurrency]) if Sidekiq.server?
|
29
30
|
|
30
|
-
pool_timeout =
|
31
|
-
log_info(
|
31
|
+
pool_timeout = symbolized_options[:pool_timeout] || 1
|
32
|
+
log_info(symbolized_options)
|
32
33
|
|
33
|
-
ConnectionPool.new(:
|
34
|
-
build_client(
|
34
|
+
ConnectionPool.new(timeout: pool_timeout, size: size) do
|
35
|
+
build_client(symbolized_options)
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
@@ -46,7 +47,7 @@ module Sidekiq
|
|
46
47
|
# - enterprise's leader election
|
47
48
|
# - enterprise's cron support
|
48
49
|
def verify_sizing(size, concurrency)
|
49
|
-
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
|
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)
|
50
51
|
end
|
51
52
|
|
52
53
|
def build_client(options)
|
@@ -55,8 +56,8 @@ module Sidekiq
|
|
55
56
|
client = Redis.new client_opts(options)
|
56
57
|
if namespace
|
57
58
|
begin
|
58
|
-
require
|
59
|
-
Redis::Namespace.new(namespace, :
|
59
|
+
require "redis/namespace"
|
60
|
+
Redis::Namespace.new(namespace, redis: client)
|
60
61
|
rescue LoadError
|
61
62
|
Sidekiq.logger.error("Your Redis configuration uses the namespace '#{namespace}' but the redis-namespace gem is not included in the Gemfile." \
|
62
63
|
"Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.")
|
@@ -78,7 +79,7 @@ module Sidekiq
|
|
78
79
|
opts.delete(:network_timeout)
|
79
80
|
end
|
80
81
|
|
81
|
-
opts[:driver] ||= Redis::Connection.drivers.last ||
|
82
|
+
opts[:driver] ||= Redis::Connection.drivers.last || "ruby"
|
82
83
|
|
83
84
|
# Issue #3303, redis-rb will silently retry an operation.
|
84
85
|
# This can lead to duplicate jobs if Sidekiq::Client's LPUSH
|
@@ -91,9 +92,13 @@ module Sidekiq
|
|
91
92
|
end
|
92
93
|
|
93
94
|
def log_info(options)
|
94
|
-
# Don't log Redis AUTH password
|
95
95
|
redacted = "REDACTED"
|
96
|
-
|
96
|
+
|
97
|
+
# Deep clone so we can muck with these options all we want and exclude
|
98
|
+
# params from dump-and-load that may contain objects that Marshal is
|
99
|
+
# unable to safely dump.
|
100
|
+
keys = options.keys - [:logger, :ssl_params]
|
101
|
+
scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
|
97
102
|
if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
|
98
103
|
uri.password = redacted
|
99
104
|
scrubbed_options[:url] = uri.to_s
|
@@ -101,6 +106,9 @@ module Sidekiq
|
|
101
106
|
if scrubbed_options[:password]
|
102
107
|
scrubbed_options[:password] = redacted
|
103
108
|
end
|
109
|
+
scrubbed_options[:sentinels]&.each do |sentinel|
|
110
|
+
sentinel[:password] = redacted if sentinel[:password]
|
111
|
+
end
|
104
112
|
if Sidekiq.server?
|
105
113
|
Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with redis options #{scrubbed_options}")
|
106
114
|
else
|
@@ -115,30 +123,22 @@ module Sidekiq
|
|
115
123
|
# REDIS_PROVIDER=MY_REDIS_URL
|
116
124
|
# and Sidekiq will find your custom URL variable with no custom
|
117
125
|
# initialization code at all.
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
REDISTOGO_URL=redis://somehost.example.com:6379/4
|
129
|
-
|
130
|
-
Use REDIS_URL if you wish to point Sidekiq to a URL directly.
|
131
|
-
|
132
|
-
This configuration error will crash starting in Sidekiq 5.3.
|
133
|
-
|
134
|
-
#################################################################################
|
135
|
-
EOM
|
126
|
+
#
|
127
|
+
p = ENV["REDIS_PROVIDER"]
|
128
|
+
if p && p =~ /:/
|
129
|
+
raise <<~EOM
|
130
|
+
REDIS_PROVIDER should be set to the name of the variable which contains the Redis URL, not a URL itself.
|
131
|
+
Platforms like Heroku will sell addons that publish a *_URL variable. You need to tell Sidekiq with REDIS_PROVIDER, e.g.:
|
132
|
+
|
133
|
+
REDISTOGO_URL=redis://somehost.example.com:6379/4
|
134
|
+
REDIS_PROVIDER=REDISTOGO_URL
|
135
|
+
EOM
|
136
136
|
end
|
137
|
+
|
137
138
|
ENV[
|
138
|
-
|
139
|
+
p || "REDIS_URL"
|
139
140
|
]
|
140
141
|
end
|
141
|
-
|
142
142
|
end
|
143
143
|
end
|
144
144
|
end
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -1,35 +1,64 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
2
|
+
|
3
|
+
require "sidekiq"
|
4
|
+
require "sidekiq/util"
|
5
|
+
require "sidekiq/api"
|
5
6
|
|
6
7
|
module Sidekiq
|
7
8
|
module Scheduled
|
8
|
-
SETS = %w
|
9
|
+
SETS = %w[retry schedule]
|
9
10
|
|
10
11
|
class Enq
|
11
|
-
|
12
|
+
LUA_ZPOPBYSCORE = <<~LUA
|
13
|
+
local key, now = KEYS[1], ARGV[1]
|
14
|
+
local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1)
|
15
|
+
if jobs[1] then
|
16
|
+
redis.call("zrem", key, jobs[1])
|
17
|
+
return jobs[1]
|
18
|
+
end
|
19
|
+
LUA
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@done = false
|
23
|
+
@lua_zpopbyscore_sha = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def enqueue_jobs(sorted_sets = SETS)
|
12
27
|
# A job's "score" in Redis is the time at which it should be processed.
|
13
28
|
# Just check Redis for the set of jobs with a timestamp before now.
|
14
29
|
Sidekiq.redis do |conn|
|
15
30
|
sorted_sets.each do |sorted_set|
|
16
|
-
# Get
|
31
|
+
# Get next item in the queue with score (time to execute) <= now.
|
17
32
|
# We need to go through the list one at a time to reduce the risk of something
|
18
33
|
# going wrong between the time jobs are popped from the scheduled queue and when
|
19
34
|
# they are pushed onto a work queue and losing the jobs.
|
20
|
-
while job = conn
|
21
|
-
|
22
|
-
|
23
|
-
# the queue, it's because another process already popped it so we can move on to the
|
24
|
-
# next one.
|
25
|
-
if conn.zrem(sorted_set, job)
|
26
|
-
Sidekiq::Client.push(Sidekiq.load_json(job))
|
27
|
-
Sidekiq::Logging.logger.debug { "enqueued #{sorted_set}: #{job}" }
|
28
|
-
end
|
35
|
+
while !@done && (job = zpopbyscore(conn, keys: [sorted_set], argv: [Time.now.to_f.to_s]))
|
36
|
+
Sidekiq::Client.push(Sidekiq.load_json(job))
|
37
|
+
Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
|
29
38
|
end
|
30
39
|
end
|
31
40
|
end
|
32
41
|
end
|
42
|
+
|
43
|
+
def terminate
|
44
|
+
@done = true
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def zpopbyscore(conn, keys: nil, argv: nil)
|
50
|
+
if @lua_zpopbyscore_sha.nil?
|
51
|
+
raw_conn = conn.respond_to?(:redis) ? conn.redis : conn
|
52
|
+
@lua_zpopbyscore_sha = raw_conn.script(:load, LUA_ZPOPBYSCORE)
|
53
|
+
end
|
54
|
+
|
55
|
+
conn.evalsha(@lua_zpopbyscore_sha, keys: keys, argv: argv)
|
56
|
+
rescue Redis::CommandError => e
|
57
|
+
raise unless e.message.start_with?("NOSCRIPT")
|
58
|
+
|
59
|
+
@lua_zpopbyscore_sha = nil
|
60
|
+
retry
|
61
|
+
end
|
33
62
|
end
|
34
63
|
|
35
64
|
##
|
@@ -47,11 +76,14 @@ module Sidekiq
|
|
47
76
|
@sleeper = ConnectionPool::TimedStack.new
|
48
77
|
@done = false
|
49
78
|
@thread = nil
|
79
|
+
@count_calls = 0
|
50
80
|
end
|
51
81
|
|
52
82
|
# Shut down this instance, will pause until the thread is dead.
|
53
83
|
def terminate
|
54
84
|
@done = true
|
85
|
+
@enq.terminate if @enq.respond_to?(:terminate)
|
86
|
+
|
55
87
|
if @thread
|
56
88
|
t = @thread
|
57
89
|
@thread = nil
|
@@ -61,26 +93,24 @@ module Sidekiq
|
|
61
93
|
end
|
62
94
|
|
63
95
|
def start
|
64
|
-
@thread ||= safe_thread("scheduler")
|
96
|
+
@thread ||= safe_thread("scheduler") {
|
65
97
|
initial_wait
|
66
98
|
|
67
|
-
|
99
|
+
until @done
|
68
100
|
enqueue
|
69
101
|
wait
|
70
102
|
end
|
71
103
|
Sidekiq.logger.info("Scheduler exiting...")
|
72
|
-
|
104
|
+
}
|
73
105
|
end
|
74
106
|
|
75
107
|
def enqueue
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
handle_exception(ex)
|
83
|
-
end
|
108
|
+
@enq.enqueue_jobs
|
109
|
+
rescue => ex
|
110
|
+
# Most likely a problem with redis networking.
|
111
|
+
# Punt and try again at the next interval
|
112
|
+
logger.error ex.message
|
113
|
+
handle_exception(ex)
|
84
114
|
end
|
85
115
|
|
86
116
|
private
|
@@ -152,8 +182,13 @@ module Sidekiq
|
|
152
182
|
end
|
153
183
|
|
154
184
|
def process_count
|
155
|
-
|
185
|
+
# The work buried within Sidekiq::ProcessSet#cleanup can be
|
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
|
156
190
|
pcount = 1 if pcount == 0
|
191
|
+
@count_calls += 1
|
157
192
|
pcount
|
158
193
|
end
|
159
194
|
|
@@ -168,7 +203,6 @@ module Sidekiq
|
|
168
203
|
@sleeper.pop(total)
|
169
204
|
rescue Timeout::Error
|
170
205
|
end
|
171
|
-
|
172
206
|
end
|
173
207
|
end
|
174
208
|
end
|