sidekiq 7.0.9 → 7.3.10
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 +263 -0
- data/README.md +5 -5
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiqload +41 -14
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/sidekiq/api.rb +170 -52
- data/lib/sidekiq/capsule.rb +8 -3
- data/lib/sidekiq/cli.rb +6 -2
- data/lib/sidekiq/client.rb +58 -22
- data/lib/sidekiq/component.rb +23 -1
- data/lib/sidekiq/config.rb +52 -11
- data/lib/sidekiq/deploy.rb +4 -2
- data/lib/sidekiq/embedded.rb +2 -0
- data/lib/sidekiq/fetch.rb +2 -1
- data/lib/sidekiq/iterable_job.rb +55 -0
- data/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +294 -0
- data/lib/sidekiq/job.rb +15 -8
- data/lib/sidekiq/job_logger.rb +7 -6
- data/lib/sidekiq/job_retry.rb +30 -8
- data/lib/sidekiq/job_util.rb +6 -2
- data/lib/sidekiq/launcher.rb +8 -6
- data/lib/sidekiq/logger.rb +1 -1
- data/lib/sidekiq/metrics/query.rb +6 -1
- data/lib/sidekiq/metrics/shared.rb +16 -5
- data/lib/sidekiq/metrics/tracking.rb +20 -8
- data/lib/sidekiq/middleware/current_attributes.rb +88 -16
- data/lib/sidekiq/middleware/i18n.rb +2 -0
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +2 -1
- data/lib/sidekiq/paginator.rb +8 -2
- data/lib/sidekiq/processor.rb +44 -33
- data/lib/sidekiq/rails.rb +28 -7
- data/lib/sidekiq/redis_client_adapter.rb +30 -31
- data/lib/sidekiq/redis_connection.rb +49 -9
- data/lib/sidekiq/ring_buffer.rb +3 -0
- data/lib/sidekiq/scheduled.rb +3 -3
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/testing.rb +32 -13
- data/lib/sidekiq/transaction_aware_client.rb +20 -5
- data/lib/sidekiq/version.rb +5 -1
- data/lib/sidekiq/web/action.rb +29 -7
- data/lib/sidekiq/web/application.rb +64 -25
- data/lib/sidekiq/web/csrf_protection.rb +9 -6
- data/lib/sidekiq/web/helpers.rb +95 -35
- data/lib/sidekiq/web/router.rb +5 -2
- data/lib/sidekiq/web.rb +67 -3
- data/lib/sidekiq.rb +5 -3
- data/sidekiq.gemspec +5 -13
- data/web/assets/javascripts/application.js +27 -0
- data/web/assets/javascripts/dashboard-charts.js +40 -12
- data/web/assets/javascripts/dashboard.js +14 -10
- data/web/assets/javascripts/metrics.js +34 -0
- data/web/assets/stylesheets/application-rtl.css +10 -0
- data/web/assets/stylesheets/application.css +38 -3
- data/web/locales/en.yml +5 -1
- data/web/locales/fr.yml +13 -0
- data/web/locales/gd.yml +0 -1
- data/web/locales/it.yml +32 -1
- data/web/locales/ja.yml +0 -1
- data/web/locales/pt-br.yml +20 -1
- data/web/locales/tr.yml +100 -0
- data/web/locales/uk.yml +24 -1
- data/web/locales/zh-cn.yml +0 -1
- data/web/locales/zh-tw.yml +0 -1
- data/web/views/_footer.erb +12 -1
- data/web/views/_job_info.erb +1 -1
- data/web/views/_metrics_period_select.erb +1 -1
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +7 -7
- data/web/views/dashboard.erb +29 -36
- data/web/views/filtering.erb +6 -0
- data/web/views/layout.erb +6 -6
- data/web/views/metrics.erb +38 -30
- data/web/views/metrics_for_job.erb +30 -39
- data/web/views/morgue.erb +2 -2
- data/web/views/queue.erb +1 -1
- data/web/views/queues.erb +6 -2
- metadata +52 -21
|
@@ -31,11 +31,11 @@ module Sidekiq
|
|
|
31
31
|
# We don't track time for failed jobs as they can have very unpredictable
|
|
32
32
|
# execution times. more important to know average time for successful jobs so we
|
|
33
33
|
# can better recognize when a perf regression is introduced.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
track_time(klass, time_ms)
|
|
35
|
+
rescue JobRetry::Skip
|
|
36
|
+
# This is raised when iterable job is interrupted.
|
|
37
|
+
track_time(klass, time_ms)
|
|
38
|
+
raise
|
|
39
39
|
rescue Exception
|
|
40
40
|
@lock.synchronize {
|
|
41
41
|
@jobs["#{klass}|f"] += 1
|
|
@@ -100,15 +100,27 @@ module Sidekiq
|
|
|
100
100
|
|
|
101
101
|
private
|
|
102
102
|
|
|
103
|
+
def track_time(klass, time_ms)
|
|
104
|
+
@lock.synchronize {
|
|
105
|
+
@grams[klass].record_time(time_ms)
|
|
106
|
+
@jobs["#{klass}|ms"] += time_ms
|
|
107
|
+
@totals["ms"] += time_ms
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
|
|
103
111
|
def reset
|
|
104
112
|
@lock.synchronize {
|
|
105
113
|
array = [@totals, @jobs, @grams]
|
|
106
|
-
|
|
107
|
-
@jobs = Hash.new(0)
|
|
108
|
-
@grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
|
|
114
|
+
reset_instance_variables
|
|
109
115
|
array
|
|
110
116
|
}
|
|
111
117
|
end
|
|
118
|
+
|
|
119
|
+
def reset_instance_variables
|
|
120
|
+
@totals = Hash.new(0)
|
|
121
|
+
@jobs = Hash.new(0)
|
|
122
|
+
@grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
|
|
123
|
+
end
|
|
112
124
|
end
|
|
113
125
|
|
|
114
126
|
class Middleware
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "active_support/current_attributes"
|
|
2
4
|
|
|
3
5
|
module Sidekiq
|
|
@@ -7,50 +9,120 @@ module Sidekiq
|
|
|
7
9
|
# This can be useful for multi-tenancy, i18n locale, timezone, any implicit
|
|
8
10
|
# per-request attribute. See +ActiveSupport::CurrentAttributes+.
|
|
9
11
|
#
|
|
12
|
+
# For multiple current attributes, pass an array of current attributes.
|
|
13
|
+
#
|
|
10
14
|
# @example
|
|
11
15
|
#
|
|
12
16
|
# # in your initializer
|
|
13
17
|
# require "sidekiq/middleware/current_attributes"
|
|
14
18
|
# Sidekiq::CurrentAttributes.persist("Myapp::Current")
|
|
19
|
+
# # or multiple current attributes
|
|
20
|
+
# Sidekiq::CurrentAttributes.persist(["Myapp::Current", "Myapp::OtherCurrent"])
|
|
15
21
|
#
|
|
16
22
|
module CurrentAttributes
|
|
17
23
|
class Save
|
|
18
24
|
include Sidekiq::ClientMiddleware
|
|
19
25
|
|
|
20
|
-
def initialize(
|
|
21
|
-
@
|
|
26
|
+
def initialize(cattrs)
|
|
27
|
+
@cattrs = cattrs
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
def call(_, job, _, _)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
@cattrs.each do |(key, strklass)|
|
|
32
|
+
if !job.has_key?(key)
|
|
33
|
+
attrs = strklass.constantize.attributes
|
|
34
|
+
# Retries can push the job N times, we don't
|
|
35
|
+
# want retries to reset cattr. #5692, #5090
|
|
36
|
+
if attrs.any?
|
|
37
|
+
# Older rails has a bug that `CurrentAttributes#attributes` always returns
|
|
38
|
+
# the same hash instance. We need to dup it to avoid being accidentally mutated.
|
|
39
|
+
job[key] = if returns_same_object?
|
|
40
|
+
attrs.dup
|
|
41
|
+
else
|
|
42
|
+
attrs
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
30
46
|
end
|
|
31
47
|
yield
|
|
32
48
|
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def returns_same_object?
|
|
53
|
+
ActiveSupport::VERSION::MAJOR < 8 ||
|
|
54
|
+
(ActiveSupport::VERSION::MAJOR == 8 && ActiveSupport::VERSION::MINOR == 0)
|
|
55
|
+
end
|
|
33
56
|
end
|
|
34
57
|
|
|
35
58
|
class Load
|
|
36
59
|
include Sidekiq::ServerMiddleware
|
|
37
60
|
|
|
38
|
-
def initialize(
|
|
39
|
-
@
|
|
61
|
+
def initialize(cattrs)
|
|
62
|
+
@cattrs = cattrs
|
|
40
63
|
end
|
|
41
64
|
|
|
42
65
|
def call(_, job, _, &block)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
66
|
+
klass_attrs = {}
|
|
67
|
+
|
|
68
|
+
@cattrs.each do |(key, strklass)|
|
|
69
|
+
next unless job.has_key?(key)
|
|
70
|
+
|
|
71
|
+
klass_attrs[strklass.constantize] = job[key]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
wrap(klass_attrs.to_a, &block)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def wrap(klass_attrs, &block)
|
|
80
|
+
klass, attrs = klass_attrs.shift
|
|
81
|
+
return block.call unless klass
|
|
82
|
+
|
|
83
|
+
retried = false
|
|
84
|
+
|
|
85
|
+
begin
|
|
86
|
+
klass.set(attrs) do
|
|
87
|
+
wrap(klass_attrs, &block)
|
|
88
|
+
end
|
|
89
|
+
rescue NoMethodError
|
|
90
|
+
raise if retried
|
|
91
|
+
|
|
92
|
+
# It is possible that the `CurrentAttributes` definition
|
|
93
|
+
# was changed before the job started processing.
|
|
94
|
+
attrs = attrs.select { |attr| klass.respond_to?(attr) }
|
|
95
|
+
retried = true
|
|
96
|
+
retry
|
|
47
97
|
end
|
|
48
98
|
end
|
|
49
99
|
end
|
|
50
100
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
101
|
+
class << self
|
|
102
|
+
def persist(klass_or_array, config = Sidekiq.default_configuration)
|
|
103
|
+
cattrs = build_cattrs_hash(klass_or_array)
|
|
104
|
+
|
|
105
|
+
config.client_middleware.add Save, cattrs
|
|
106
|
+
config.server_middleware.prepend Load, cattrs
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def build_cattrs_hash(klass_or_array)
|
|
112
|
+
if klass_or_array.is_a?(Array)
|
|
113
|
+
{}.tap do |hash|
|
|
114
|
+
klass_or_array.each_with_index do |klass, index|
|
|
115
|
+
hash[key_at(index)] = klass.to_s
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
else
|
|
119
|
+
{key_at(0) => klass_or_array.to_s}
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def key_at(index)
|
|
124
|
+
(index == 0) ? "cattr" : "cattr_#{index}"
|
|
125
|
+
end
|
|
54
126
|
end
|
|
55
127
|
end
|
|
56
128
|
end
|
|
@@ -11,6 +11,7 @@ module Sidekiq::Middleware::I18n
|
|
|
11
11
|
# to be sent to Sidekiq.
|
|
12
12
|
class Client
|
|
13
13
|
include Sidekiq::ClientMiddleware
|
|
14
|
+
|
|
14
15
|
def call(_jobclass, job, _queue, _redis)
|
|
15
16
|
job["locale"] ||= I18n.locale
|
|
16
17
|
yield
|
|
@@ -20,6 +21,7 @@ module Sidekiq::Middleware::I18n
|
|
|
20
21
|
# Pull the msg locale out and set the current thread to use it.
|
|
21
22
|
class Server
|
|
22
23
|
include Sidekiq::ServerMiddleware
|
|
24
|
+
|
|
23
25
|
def call(_jobclass, job, _queue, &block)
|
|
24
26
|
I18n.with_locale(job.fetch("locale", I18n.default_locale), &block)
|
|
25
27
|
end
|
data/lib/sidekiq/monitor.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
require "fileutils"
|
|
4
5
|
require "sidekiq/api"
|
|
@@ -98,7 +99,7 @@ class Sidekiq::Monitor
|
|
|
98
99
|
pad = opts[:pad] || 0
|
|
99
100
|
max_length = opts[:max_length] || (80 - pad)
|
|
100
101
|
out = []
|
|
101
|
-
line = ""
|
|
102
|
+
line = +""
|
|
102
103
|
values.each do |value|
|
|
103
104
|
if (line.length + value.length) > max_length
|
|
104
105
|
out << line
|
data/lib/sidekiq/paginator.rb
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Sidekiq
|
|
4
4
|
module Paginator
|
|
5
|
+
TYPE_CACHE = {
|
|
6
|
+
"dead" => "zset",
|
|
7
|
+
"retry" => "zset",
|
|
8
|
+
"schedule" => "zset"
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
def page(key, pageidx = 1, page_size = 25, opts = nil)
|
|
6
12
|
current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
|
|
7
13
|
pageidx = current_page - 1
|
|
@@ -19,9 +25,9 @@ module Sidekiq
|
|
|
19
25
|
total_size, items = conn.multi { |transaction|
|
|
20
26
|
transaction.zcard(key)
|
|
21
27
|
if rev
|
|
22
|
-
transaction.
|
|
28
|
+
transaction.zrange(key, starting, ending, "REV", "withscores")
|
|
23
29
|
else
|
|
24
|
-
transaction.zrange(key, starting, ending, withscores
|
|
30
|
+
transaction.zrange(key, starting, ending, "withscores")
|
|
25
31
|
end
|
|
26
32
|
}
|
|
27
33
|
[current_page, total_size, items]
|
data/lib/sidekiq/processor.rb
CHANGED
|
@@ -36,7 +36,7 @@ module Sidekiq
|
|
|
36
36
|
@job = nil
|
|
37
37
|
@thread = nil
|
|
38
38
|
@reloader = Sidekiq.default_configuration[:reloader]
|
|
39
|
-
@job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(
|
|
39
|
+
@job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(capsule.config)
|
|
40
40
|
@retrier = Sidekiq::JobRetry.new(capsule)
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -58,6 +58,10 @@ module Sidekiq
|
|
|
58
58
|
@thread.value if wait
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
def stopping?
|
|
62
|
+
@done
|
|
63
|
+
end
|
|
64
|
+
|
|
61
65
|
def start
|
|
62
66
|
@thread ||= safe_thread("#{config.name}/processor", &method(:run))
|
|
63
67
|
end
|
|
@@ -134,10 +138,11 @@ module Sidekiq
|
|
|
134
138
|
# Effectively this block denotes a "unit of work" to Rails.
|
|
135
139
|
@reloader.call do
|
|
136
140
|
klass = Object.const_get(job_hash["class"])
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
instance = klass.new
|
|
142
|
+
instance.jid = job_hash["jid"]
|
|
143
|
+
instance._context = self
|
|
144
|
+
@retrier.local(instance, jobstr, queue) do
|
|
145
|
+
yield instance
|
|
141
146
|
end
|
|
142
147
|
end
|
|
143
148
|
end
|
|
@@ -148,6 +153,8 @@ module Sidekiq
|
|
|
148
153
|
|
|
149
154
|
IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :never}
|
|
150
155
|
private_constant :IGNORE_SHUTDOWN_INTERRUPTS
|
|
156
|
+
ALLOW_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :immediate}
|
|
157
|
+
private_constant :ALLOW_SHUTDOWN_INTERRUPTS
|
|
151
158
|
|
|
152
159
|
def process(uow)
|
|
153
160
|
jobstr = uow.job
|
|
@@ -171,42 +178,46 @@ module Sidekiq
|
|
|
171
178
|
end
|
|
172
179
|
|
|
173
180
|
ack = false
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
181
|
+
Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
|
|
182
|
+
Thread.handle_interrupt(ALLOW_SHUTDOWN_INTERRUPTS) do
|
|
183
|
+
dispatch(job_hash, queue, jobstr) do |instance|
|
|
184
|
+
config.server_middleware.invoke(instance, job_hash, queue) do
|
|
185
|
+
execute_job(instance, job_hash["args"])
|
|
186
|
+
end
|
|
178
187
|
end
|
|
188
|
+
ack = true
|
|
189
|
+
rescue Sidekiq::Shutdown
|
|
190
|
+
# Had to force kill this job because it didn't finish
|
|
191
|
+
# within the timeout. Don't acknowledge the work since
|
|
192
|
+
# we didn't properly finish it.
|
|
193
|
+
rescue Sidekiq::JobRetry::Skip => s
|
|
194
|
+
# Skip means we handled this error elsewhere. We don't
|
|
195
|
+
# need to log or report the error.
|
|
196
|
+
ack = true
|
|
197
|
+
raise s
|
|
198
|
+
rescue Sidekiq::JobRetry::Handled => h
|
|
199
|
+
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
|
200
|
+
# signals that we created a retry successfully. We can acknowledge the job.
|
|
201
|
+
ack = true
|
|
202
|
+
e = h.cause || h
|
|
203
|
+
handle_exception(e, {context: "Job raised exception", job: job_hash})
|
|
204
|
+
raise e
|
|
205
|
+
rescue Exception => ex
|
|
206
|
+
# Unexpected error! This is very bad and indicates an exception that got past
|
|
207
|
+
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
|
208
|
+
# so it can be rescued when using Sidekiq Pro.
|
|
209
|
+
handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
|
|
210
|
+
raise ex
|
|
179
211
|
end
|
|
180
|
-
ack = true
|
|
181
|
-
rescue Sidekiq::Shutdown
|
|
182
|
-
# Had to force kill this job because it didn't finish
|
|
183
|
-
# within the timeout. Don't acknowledge the work since
|
|
184
|
-
# we didn't properly finish it.
|
|
185
|
-
rescue Sidekiq::JobRetry::Handled => h
|
|
186
|
-
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
|
187
|
-
# signals that we created a retry successfully. We can acknowlege the job.
|
|
188
|
-
ack = true
|
|
189
|
-
e = h.cause || h
|
|
190
|
-
handle_exception(e, {context: "Job raised exception", job: job_hash})
|
|
191
|
-
raise e
|
|
192
|
-
rescue Exception => ex
|
|
193
|
-
# Unexpected error! This is very bad and indicates an exception that got past
|
|
194
|
-
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
|
195
|
-
# so it can be rescued when using Sidekiq Pro.
|
|
196
|
-
handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
|
|
197
|
-
raise ex
|
|
198
212
|
ensure
|
|
199
213
|
if ack
|
|
200
|
-
|
|
201
|
-
Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
|
|
202
|
-
uow.acknowledge
|
|
203
|
-
end
|
|
214
|
+
uow.acknowledge
|
|
204
215
|
end
|
|
205
216
|
end
|
|
206
217
|
end
|
|
207
218
|
|
|
208
|
-
def execute_job(
|
|
209
|
-
|
|
219
|
+
def execute_job(instance, cloned_args)
|
|
220
|
+
instance.perform(*cloned_args)
|
|
210
221
|
end
|
|
211
222
|
|
|
212
223
|
# Ruby doesn't provide atomic counters out of the box so we'll
|
data/lib/sidekiq/rails.rb
CHANGED
|
@@ -4,6 +4,17 @@ require "sidekiq/job"
|
|
|
4
4
|
require "rails"
|
|
5
5
|
|
|
6
6
|
module Sidekiq
|
|
7
|
+
module ActiveJob
|
|
8
|
+
# @api private
|
|
9
|
+
class Wrapper
|
|
10
|
+
include Sidekiq::Job
|
|
11
|
+
|
|
12
|
+
def perform(job_data)
|
|
13
|
+
::ActiveJob::Base.execute(job_data.merge("provider_job_id" => jid))
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
7
18
|
class Rails < ::Rails::Engine
|
|
8
19
|
class Reloader
|
|
9
20
|
def initialize(app = ::Rails.application)
|
|
@@ -20,6 +31,10 @@ module Sidekiq
|
|
|
20
31
|
def inspect
|
|
21
32
|
"#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
|
|
22
33
|
end
|
|
34
|
+
|
|
35
|
+
def to_hash
|
|
36
|
+
{app: @app.class.name}
|
|
37
|
+
end
|
|
23
38
|
end
|
|
24
39
|
|
|
25
40
|
# By including the Options module, we allow AJs to directly control sidekiq features
|
|
@@ -35,18 +50,14 @@ module Sidekiq
|
|
|
35
50
|
# end
|
|
36
51
|
initializer "sidekiq.active_job_integration" do
|
|
37
52
|
ActiveSupport.on_load(:active_job) do
|
|
53
|
+
require_relative "../active_job/queue_adapters/sidekiq_adapter"
|
|
38
54
|
include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
|
|
39
55
|
end
|
|
40
56
|
end
|
|
41
57
|
|
|
42
|
-
initializer "sidekiq.
|
|
58
|
+
initializer "sidekiq.backtrace_cleaner" do
|
|
43
59
|
Sidekiq.configure_server do |config|
|
|
44
|
-
|
|
45
|
-
# it will appear in the Sidekiq console with all of the job context. See #5021 and
|
|
46
|
-
# https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84
|
|
47
|
-
unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
|
48
|
-
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
|
49
|
-
end
|
|
60
|
+
config[:backtrace_cleaner] = ->(backtrace) { ::Rails.backtrace_cleaner.clean(backtrace) }
|
|
50
61
|
end
|
|
51
62
|
end
|
|
52
63
|
|
|
@@ -57,6 +68,16 @@ module Sidekiq
|
|
|
57
68
|
config.after_initialize do
|
|
58
69
|
Sidekiq.configure_server do |config|
|
|
59
70
|
config[:reloader] = Sidekiq::Rails::Reloader.new
|
|
71
|
+
|
|
72
|
+
# This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
|
|
73
|
+
# it will appear in the Sidekiq console with all of the job context.
|
|
74
|
+
unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
|
75
|
+
if ::Rails.logger.respond_to?(:broadcast_to)
|
|
76
|
+
::Rails.logger.broadcast_to(config.logger)
|
|
77
|
+
else
|
|
78
|
+
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
60
81
|
end
|
|
61
82
|
end
|
|
62
83
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "set"
|
|
3
4
|
require "redis_client"
|
|
4
5
|
require "redis_client/decorator"
|
|
5
6
|
|
|
@@ -8,22 +9,40 @@ module Sidekiq
|
|
|
8
9
|
BaseError = RedisClient::Error
|
|
9
10
|
CommandError = RedisClient::CommandError
|
|
10
11
|
|
|
12
|
+
# You can add/remove items or clear the whole thing if you don't want deprecation warnings.
|
|
13
|
+
DEPRECATED_COMMANDS = %i[rpoplpush zrangebyscore zrevrange zrevrangebyscore getset hmset setex setnx].to_set
|
|
14
|
+
|
|
11
15
|
module CompatMethods
|
|
12
|
-
# TODO Deprecate and remove this
|
|
13
16
|
def info
|
|
14
17
|
@client.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h }
|
|
15
18
|
end
|
|
16
19
|
|
|
17
|
-
# TODO Deprecate and remove this
|
|
18
20
|
def evalsha(sha, keys, argv)
|
|
19
21
|
@client.call("EVALSHA", sha, keys.size, *keys, *argv)
|
|
20
22
|
end
|
|
21
23
|
|
|
24
|
+
# this is the set of Redis commands used by Sidekiq. Not guaranteed
|
|
25
|
+
# to be comprehensive, we use this as a performance enhancement to
|
|
26
|
+
# avoid calling method_missing on most commands
|
|
27
|
+
USED_COMMANDS = %w[bitfield bitfield_ro del exists expire flushdb
|
|
28
|
+
get hdel hget hgetall hincrby hlen hmget hset hsetnx incr incrby
|
|
29
|
+
lindex llen lmove lpop lpush lrange lrem mget mset ping pttl
|
|
30
|
+
publish rpop rpush sadd scard script set sismember smembers
|
|
31
|
+
srem ttl type unlink zadd zcard zincrby zrange zrem
|
|
32
|
+
zremrangebyrank zremrangebyscore]
|
|
33
|
+
|
|
34
|
+
USED_COMMANDS.each do |name|
|
|
35
|
+
define_method(name) do |*args, **kwargs|
|
|
36
|
+
@client.call(name, *args, **kwargs)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
22
40
|
private
|
|
23
41
|
|
|
24
42
|
# this allows us to use methods like `conn.hmset(...)` instead of having to use
|
|
25
43
|
# redis-client's native `conn.call("hmset", ...)`
|
|
26
44
|
def method_missing(*args, &block)
|
|
45
|
+
warn("[sidekiq#5788] Redis has deprecated the `#{args.first}`command, called at #{caller(1..1)}") if DEPRECATED_COMMANDS.include?(args.first)
|
|
27
46
|
@client.call(*args, *block)
|
|
28
47
|
end
|
|
29
48
|
ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
|
|
@@ -39,34 +58,19 @@ module Sidekiq
|
|
|
39
58
|
def config
|
|
40
59
|
@client.config
|
|
41
60
|
end
|
|
42
|
-
|
|
43
|
-
def message
|
|
44
|
-
yield nil, @queue.pop
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# NB: this method does not return
|
|
48
|
-
def subscribe(chan)
|
|
49
|
-
@queue = ::Queue.new
|
|
50
|
-
|
|
51
|
-
pubsub = @client.pubsub
|
|
52
|
-
pubsub.call("subscribe", chan)
|
|
53
|
-
|
|
54
|
-
loop do
|
|
55
|
-
evt = pubsub.next_event
|
|
56
|
-
next if evt.nil?
|
|
57
|
-
next unless evt[0] == "message" && evt[1] == chan
|
|
58
|
-
|
|
59
|
-
(_, _, msg) = evt
|
|
60
|
-
@queue << msg
|
|
61
|
-
yield self
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
61
|
end
|
|
65
62
|
|
|
66
63
|
def initialize(options)
|
|
67
64
|
opts = client_opts(options)
|
|
68
65
|
@config = if opts.key?(:sentinels)
|
|
69
66
|
RedisClient.sentinel(**opts)
|
|
67
|
+
elsif opts.key?(:nodes)
|
|
68
|
+
# Sidekiq does not support Redis clustering but Sidekiq Enterprise's
|
|
69
|
+
# rate limiters are cluster-safe so we can scale to millions
|
|
70
|
+
# of rate limiters using a Redis cluster. This requires the
|
|
71
|
+
# `redis-cluster-client` gem.
|
|
72
|
+
# Sidekiq::Limiter.redis = { nodes: [...] }
|
|
73
|
+
RedisClient.cluster(**opts)
|
|
70
74
|
else
|
|
71
75
|
RedisClient.config(**opts)
|
|
72
76
|
end
|
|
@@ -82,8 +86,7 @@ module Sidekiq
|
|
|
82
86
|
opts = options.dup
|
|
83
87
|
|
|
84
88
|
if opts[:namespace]
|
|
85
|
-
raise ArgumentError, "Your Redis configuration uses the namespace '#{opts[:namespace]}' but this feature
|
|
86
|
-
"Either use the redis adapter or remove the namespace."
|
|
89
|
+
raise ArgumentError, "Your Redis configuration uses the namespace '#{opts[:namespace]}' but this feature is no longer supported in Sidekiq 7+. See https://github.com/sidekiq/sidekiq/blob/main/docs/7.0-Upgrade.md#redis-namespace."
|
|
87
90
|
end
|
|
88
91
|
|
|
89
92
|
opts.delete(:size)
|
|
@@ -94,13 +97,9 @@ module Sidekiq
|
|
|
94
97
|
opts.delete(:network_timeout)
|
|
95
98
|
end
|
|
96
99
|
|
|
97
|
-
if opts[:driver]
|
|
98
|
-
opts[:driver] = opts[:driver].to_sym
|
|
99
|
-
end
|
|
100
|
-
|
|
101
100
|
opts[:name] = opts.delete(:master_name) if opts.key?(:master_name)
|
|
102
101
|
opts[:role] = opts[:role].to_sym if opts.key?(:role)
|
|
103
|
-
opts
|
|
102
|
+
opts[:driver] = opts[:driver].to_sym if opts.key?(:driver)
|
|
104
103
|
|
|
105
104
|
# Issue #3303, redis-rb will silently retry an operation.
|
|
106
105
|
# This can lead to duplicate jobs if Sidekiq::Client's LPUSH
|
|
@@ -8,16 +8,30 @@ module Sidekiq
|
|
|
8
8
|
module RedisConnection
|
|
9
9
|
class << self
|
|
10
10
|
def create(options = {})
|
|
11
|
-
symbolized_options = options
|
|
11
|
+
symbolized_options = deep_symbolize_keys(options)
|
|
12
12
|
symbolized_options[:url] ||= determine_redis_provider
|
|
13
|
+
symbolized_options[:password] = wrap(symbolized_options[:password]) if symbolized_options.key?(:password)
|
|
14
|
+
symbolized_options[:sentinel_password] = wrap(symbolized_options[:sentinel_password]) if symbolized_options.key?(:sentinel_password)
|
|
13
15
|
|
|
14
16
|
logger = symbolized_options.delete(:logger)
|
|
15
17
|
logger&.info { "Sidekiq #{Sidekiq::VERSION} connecting to Redis with options #{scrub(symbolized_options)}" }
|
|
16
18
|
|
|
19
|
+
raise "Sidekiq 7+ does not support Redis protocol 2" if symbolized_options[:protocol] == 2
|
|
20
|
+
|
|
21
|
+
safe = !!symbolized_options.delete(:cluster_safe)
|
|
22
|
+
raise ":nodes not allowed, Sidekiq is not safe to run on Redis Cluster" if !safe && symbolized_options.key?(:nodes)
|
|
23
|
+
|
|
17
24
|
size = symbolized_options.delete(:size) || 5
|
|
18
25
|
pool_timeout = symbolized_options.delete(:pool_timeout) || 1
|
|
19
26
|
pool_name = symbolized_options.delete(:pool_name)
|
|
20
27
|
|
|
28
|
+
# Default timeout in redis-client is 1 second, which can be too aggressive
|
|
29
|
+
# if the Sidekiq process is CPU-bound. With 10-15 threads and a thread quantum of 100ms,
|
|
30
|
+
# it can be easy to get the occasional ReadTimeoutError. You can still provide
|
|
31
|
+
# a smaller timeout explicitly:
|
|
32
|
+
# config.redis = { url: "...", timeout: 1 }
|
|
33
|
+
symbolized_options[:timeout] ||= 3
|
|
34
|
+
|
|
21
35
|
redis_config = Sidekiq::RedisClientAdapter.new(symbolized_options)
|
|
22
36
|
ConnectionPool.new(timeout: pool_timeout, size: size, name: pool_name) do
|
|
23
37
|
redis_config.new_client
|
|
@@ -26,23 +40,51 @@ module Sidekiq
|
|
|
26
40
|
|
|
27
41
|
private
|
|
28
42
|
|
|
43
|
+
# Wrap hard-coded passwords in a Proc to avoid logging the value
|
|
44
|
+
def wrap(pwd)
|
|
45
|
+
if pwd.is_a?(String)
|
|
46
|
+
->(username) { pwd }
|
|
47
|
+
else
|
|
48
|
+
pwd
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def deep_symbolize_keys(object)
|
|
53
|
+
case object
|
|
54
|
+
when Hash
|
|
55
|
+
object.each_with_object({}) do |(key, value), result|
|
|
56
|
+
result[key.to_sym] = deep_symbolize_keys(value)
|
|
57
|
+
end
|
|
58
|
+
when Array
|
|
59
|
+
object.map { |e| deep_symbolize_keys(e) }
|
|
60
|
+
else
|
|
61
|
+
object
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
29
65
|
def scrub(options)
|
|
30
66
|
redacted = "REDACTED"
|
|
31
67
|
|
|
32
68
|
# Deep clone so we can muck with these options all we want and exclude
|
|
33
69
|
# params from dump-and-load that may contain objects that Marshal is
|
|
34
70
|
# unable to safely dump.
|
|
35
|
-
keys = options.keys - [:logger, :ssl_params]
|
|
71
|
+
keys = options.keys - [:logger, :ssl_params, :password, :sentinel_password]
|
|
36
72
|
scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
|
|
37
73
|
if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
|
|
38
74
|
uri.password = redacted
|
|
39
75
|
scrubbed_options[:url] = uri.to_s
|
|
40
76
|
end
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
end
|
|
77
|
+
scrubbed_options[:password] = redacted if options.key?(:password)
|
|
78
|
+
scrubbed_options[:sentinel_password] = redacted if options.key?(:sentinel_password)
|
|
44
79
|
scrubbed_options[:sentinels]&.each do |sentinel|
|
|
45
|
-
|
|
80
|
+
if sentinel.is_a?(String)
|
|
81
|
+
if (uri = URI(sentinel)) && uri.password
|
|
82
|
+
uri.password = redacted
|
|
83
|
+
sentinel.replace(uri.to_s)
|
|
84
|
+
end
|
|
85
|
+
elsif sentinel[:password]
|
|
86
|
+
sentinel[:password] = redacted
|
|
87
|
+
end
|
|
46
88
|
end
|
|
47
89
|
scrubbed_options
|
|
48
90
|
end
|
|
@@ -66,9 +108,7 @@ module Sidekiq
|
|
|
66
108
|
EOM
|
|
67
109
|
end
|
|
68
110
|
|
|
69
|
-
ENV[
|
|
70
|
-
p || "REDIS_URL"
|
|
71
|
-
]
|
|
111
|
+
ENV[p.to_s] || ENV["REDIS_URL"]
|
|
72
112
|
end
|
|
73
113
|
end
|
|
74
114
|
end
|
data/lib/sidekiq/ring_buffer.rb
CHANGED