sidekiq 7.3.10 → 8.0.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 +135 -4
- data/README.md +16 -13
- data/bin/sidekiqload +10 -10
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +104 -58
- data/lib/generators/sidekiq/templates/job.rb.erb +1 -1
- data/lib/sidekiq/api.rb +152 -44
- data/lib/sidekiq/capsule.rb +10 -6
- data/lib/sidekiq/cli.rb +30 -23
- data/lib/sidekiq/client.rb +28 -17
- data/lib/sidekiq/component.rb +42 -3
- data/lib/sidekiq/config.rb +32 -22
- data/lib/sidekiq/embedded.rb +2 -1
- data/lib/sidekiq/iterable_job.rb +1 -0
- data/lib/sidekiq/job/iterable.rb +46 -18
- data/lib/sidekiq/job.rb +2 -2
- data/lib/sidekiq/job_logger.rb +8 -6
- data/lib/sidekiq/job_retry.rb +33 -10
- data/lib/sidekiq/job_util.rb +5 -1
- data/lib/sidekiq/launcher.rb +20 -10
- data/lib/sidekiq/loader.rb +57 -0
- data/lib/sidekiq/logger.rb +25 -69
- data/lib/sidekiq/manager.rb +0 -1
- data/lib/sidekiq/metrics/query.rb +71 -45
- data/lib/sidekiq/metrics/shared.rb +8 -5
- data/lib/sidekiq/metrics/tracking.rb +12 -7
- data/lib/sidekiq/middleware/current_attributes.rb +11 -19
- data/lib/sidekiq/monitor.rb +4 -8
- data/lib/sidekiq/paginator.rb +8 -1
- data/lib/sidekiq/processor.rb +21 -14
- data/lib/sidekiq/profiler.rb +73 -0
- data/lib/sidekiq/rails.rb +46 -67
- data/lib/sidekiq/redis_client_adapter.rb +0 -1
- data/lib/sidekiq/redis_connection.rb +2 -2
- data/lib/sidekiq/scheduled.rb +4 -4
- data/lib/sidekiq/testing.rb +3 -3
- data/lib/sidekiq/version.rb +2 -2
- data/lib/sidekiq/web/action.rb +146 -83
- data/lib/sidekiq/web/application.rb +361 -330
- data/lib/sidekiq/web/config.rb +120 -0
- data/lib/sidekiq/web/helpers.rb +58 -36
- data/lib/sidekiq/web/router.rb +60 -76
- data/lib/sidekiq/web.rb +51 -156
- data/lib/sidekiq.rb +6 -1
- data/sidekiq.gemspec +6 -6
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status.png +0 -0
- data/web/assets/javascripts/application.js +42 -26
- data/web/assets/javascripts/base-charts.js +30 -16
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/dashboard.js +1 -1
- data/web/assets/javascripts/metrics.js +16 -34
- data/web/assets/stylesheets/style.css +776 -0
- data/web/locales/ar.yml +2 -0
- data/web/locales/cs.yml +2 -0
- data/web/locales/da.yml +2 -0
- data/web/locales/de.yml +2 -0
- data/web/locales/el.yml +2 -0
- data/web/locales/en.yml +7 -0
- data/web/locales/es.yml +25 -2
- data/web/locales/fa.yml +2 -0
- data/web/locales/fr.yml +3 -1
- data/web/locales/gd.yml +2 -0
- data/web/locales/he.yml +2 -0
- data/web/locales/hi.yml +2 -0
- data/web/locales/it.yml +9 -0
- data/web/locales/ja.yml +2 -0
- data/web/locales/ko.yml +2 -0
- data/web/locales/lt.yml +2 -0
- data/web/locales/nb.yml +2 -0
- data/web/locales/nl.yml +2 -0
- data/web/locales/pl.yml +2 -0
- data/web/locales/{pt-br.yml → pt-BR.yml} +3 -1
- data/web/locales/pt.yml +2 -0
- data/web/locales/ru.yml +2 -0
- data/web/locales/sv.yml +2 -0
- data/web/locales/ta.yml +2 -0
- data/web/locales/tr.yml +2 -0
- data/web/locales/uk.yml +7 -5
- data/web/locales/ur.yml +2 -0
- data/web/locales/vi.yml +2 -0
- data/web/locales/{zh-cn.yml → zh-CN.yml} +86 -73
- data/web/locales/{zh-tw.yml → zh-TW.yml} +3 -1
- data/web/views/_footer.erb +31 -33
- data/web/views/_job_info.erb +91 -89
- data/web/views/_metrics_period_select.erb +13 -10
- data/web/views/_nav.erb +14 -21
- data/web/views/_paging.erb +22 -21
- data/web/views/_poll_link.erb +2 -2
- data/web/views/_summary.erb +16 -16
- data/web/views/busy.erb +123 -125
- data/web/views/dashboard.erb +62 -66
- data/web/views/dead.erb +31 -27
- data/web/views/filtering.erb +4 -4
- data/web/views/layout.erb +12 -28
- data/web/views/metrics.erb +76 -82
- data/web/views/metrics_for_job.erb +45 -46
- data/web/views/morgue.erb +65 -70
- data/web/views/profiles.erb +43 -0
- data/web/views/queue.erb +54 -52
- data/web/views/queues.erb +43 -41
- data/web/views/retries.erb +70 -75
- data/web/views/retry.erb +32 -27
- data/web/views/scheduled.erb +63 -55
- data/web/views/scheduled_job_info.erb +3 -3
- metadata +24 -41
- data/web/assets/stylesheets/application-dark.css +0 -147
- data/web/assets/stylesheets/application-rtl.css +0 -163
- data/web/assets/stylesheets/application.css +0 -759
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/views/_status.erb +0 -4
data/lib/sidekiq/config.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "forwardable"
|
|
4
|
-
|
|
5
|
-
require "set"
|
|
6
4
|
require "sidekiq/redis_connection"
|
|
7
5
|
|
|
8
6
|
module Sidekiq
|
|
@@ -19,16 +17,16 @@ module Sidekiq
|
|
|
19
17
|
poll_interval_average: nil,
|
|
20
18
|
average_scheduled_poll_interval: 5,
|
|
21
19
|
on_complex_arguments: :raise,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
20
|
+
# if the Iterable job runs longer than this value (in seconds), then the job
|
|
21
|
+
# will be interrupted after the current iteration and re-enqueued at the back of the queue
|
|
22
|
+
max_iteration_runtime: nil,
|
|
26
23
|
error_handlers: [],
|
|
27
24
|
death_handlers: [],
|
|
28
25
|
lifecycle_events: {
|
|
29
26
|
startup: [],
|
|
30
27
|
quiet: [],
|
|
31
28
|
shutdown: [],
|
|
29
|
+
exit: [],
|
|
32
30
|
# triggers when we fire the first heartbeat on startup OR repairing a network partition
|
|
33
31
|
heartbeat: [],
|
|
34
32
|
# triggers on EVERY heartbeat call, every 10 seconds
|
|
@@ -37,16 +35,28 @@ module Sidekiq
|
|
|
37
35
|
dead_max_jobs: 10_000,
|
|
38
36
|
dead_timeout_in_seconds: 180 * 24 * 60 * 60, # 6 months
|
|
39
37
|
reloader: proc { |&block| block.call },
|
|
40
|
-
backtrace_cleaner: ->(backtrace) { backtrace }
|
|
38
|
+
backtrace_cleaner: ->(backtrace) { backtrace },
|
|
39
|
+
logged_job_attributes: ["bid", "tags"],
|
|
40
|
+
reap_connections: nil # TODO Enable by default
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
ERROR_HANDLER = ->(ex, ctx, cfg = Sidekiq.default_configuration) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
Sidekiq::Context.with(ctx) do
|
|
45
|
+
dev = cfg[:environment] == "development"
|
|
46
|
+
fancy = dev && $stdout.tty? # 🎩
|
|
47
|
+
# Weird logic here but we want to show the backtrace in local
|
|
48
|
+
# development or if verbose logging is enabled.
|
|
49
|
+
#
|
|
50
|
+
# `full_message` contains the error class, message and backtrace
|
|
51
|
+
# `detailed_message` contains the error class and message
|
|
52
|
+
#
|
|
53
|
+
# Absolutely terrible API names. Not useful at all to have two
|
|
54
|
+
# methods with similar but obscure names.
|
|
55
|
+
if dev || cfg.logger.debug?
|
|
56
|
+
cfg.logger.info { ex.full_message(highlight: fancy) }
|
|
57
|
+
else
|
|
58
|
+
cfg.logger.info { ex.detailed_message(highlight: fancy) }
|
|
59
|
+
end
|
|
50
60
|
end
|
|
51
61
|
}
|
|
52
62
|
|
|
@@ -60,6 +70,7 @@ module Sidekiq
|
|
|
60
70
|
|
|
61
71
|
def_delegators :@options, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig
|
|
62
72
|
attr_reader :capsules
|
|
73
|
+
attr_accessor :thread_priority
|
|
63
74
|
|
|
64
75
|
def inspect
|
|
65
76
|
"#<#{self.class.name} @options=#{
|
|
@@ -135,11 +146,16 @@ module Sidekiq
|
|
|
135
146
|
@redis_config = @redis_config.merge(hash)
|
|
136
147
|
end
|
|
137
148
|
|
|
149
|
+
def reap_idle_redis_connections(timeout = nil)
|
|
150
|
+
self[:reap_connections] = timeout
|
|
151
|
+
end
|
|
152
|
+
alias_method :reap, :reap_idle_redis_connections
|
|
153
|
+
|
|
138
154
|
def redis_pool
|
|
139
155
|
Thread.current[:sidekiq_redis_pool] || Thread.current[:sidekiq_capsule]&.redis_pool || local_redis_pool
|
|
140
156
|
end
|
|
141
157
|
|
|
142
|
-
|
|
158
|
+
def local_redis_pool
|
|
143
159
|
# this is our internal client/housekeeping pool. each capsule has its
|
|
144
160
|
# own pool for executing threads.
|
|
145
161
|
@redis ||= new_redis_pool(10, "internal")
|
|
@@ -249,7 +265,7 @@ module Sidekiq
|
|
|
249
265
|
end
|
|
250
266
|
|
|
251
267
|
# Register a block to run at a point in the Sidekiq lifecycle.
|
|
252
|
-
# :startup, :quiet or :
|
|
268
|
+
# :startup, :quiet, :shutdown, or :exit are valid events.
|
|
253
269
|
#
|
|
254
270
|
# Sidekiq.configure_server do |config|
|
|
255
271
|
# config.on(:shutdown) do
|
|
@@ -293,13 +309,7 @@ module Sidekiq
|
|
|
293
309
|
p ["!!!!!", ex]
|
|
294
310
|
end
|
|
295
311
|
@options[:error_handlers].each do |handler|
|
|
296
|
-
|
|
297
|
-
# TODO Remove in 8.0
|
|
298
|
-
logger.info { "DEPRECATION: Sidekiq exception handlers now take three arguments, see #{handler}" }
|
|
299
|
-
handler.call(ex, {_config: self}.merge(ctx))
|
|
300
|
-
else
|
|
301
|
-
handler.call(ex, ctx, self)
|
|
302
|
-
end
|
|
312
|
+
handler.call(ex, ctx, self)
|
|
303
313
|
rescue Exception => e
|
|
304
314
|
l = logger
|
|
305
315
|
l.error "!!! ERROR HANDLER THREW AN ERROR !!!"
|
data/lib/sidekiq/embedded.rb
CHANGED
|
@@ -34,6 +34,7 @@ module Sidekiq
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
36
|
def housekeeping
|
|
37
|
+
@config[:tag] ||= default_tag
|
|
37
38
|
logger.info "Running in #{RUBY_DESCRIPTION}"
|
|
38
39
|
logger.info Sidekiq::LICENSE
|
|
39
40
|
logger.info "Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org" unless defined?(::Sidekiq::Pro)
|
|
@@ -42,7 +43,7 @@ module Sidekiq
|
|
|
42
43
|
# fire startup and start multithreading.
|
|
43
44
|
info = config.redis_info
|
|
44
45
|
ver = Gem::Version.new(info["redis_version"])
|
|
45
|
-
raise "You are
|
|
46
|
+
raise "You are connected to Redis #{ver}, Sidekiq requires Redis 7.0.0 or greater" if ver < Gem::Version.new("7.0.0")
|
|
46
47
|
|
|
47
48
|
maxmemory_policy = info["maxmemory_policy"]
|
|
48
49
|
if maxmemory_policy != "noeviction"
|
data/lib/sidekiq/iterable_job.rb
CHANGED
data/lib/sidekiq/job/iterable.rb
CHANGED
|
@@ -32,8 +32,14 @@ module Sidekiq
|
|
|
32
32
|
@_runtime = 0
|
|
33
33
|
@_args = nil
|
|
34
34
|
@_cancelled = nil
|
|
35
|
+
@current_object = nil
|
|
35
36
|
end
|
|
36
37
|
|
|
38
|
+
# Access to the current object while iterating.
|
|
39
|
+
# This value is not reset so the latest element is
|
|
40
|
+
# explicitly available to cleanup/complete callbacks.
|
|
41
|
+
attr_reader :current_object
|
|
42
|
+
|
|
37
43
|
def arguments
|
|
38
44
|
@_args
|
|
39
45
|
end
|
|
@@ -49,14 +55,12 @@ module Sidekiq
|
|
|
49
55
|
def cancel!
|
|
50
56
|
return @_cancelled if cancelled?
|
|
51
57
|
|
|
52
|
-
key =
|
|
58
|
+
key = iteration_key
|
|
53
59
|
_, result, _ = Sidekiq.redis do |c|
|
|
54
60
|
c.pipelined do |p|
|
|
55
61
|
p.hsetnx(key, "cancelled", Time.now.to_i)
|
|
56
62
|
p.hget(key, "cancelled")
|
|
57
|
-
|
|
58
|
-
# p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
|
|
59
|
-
p.expire(key, Sidekiq::Job::Iterable::STATE_TTL)
|
|
63
|
+
p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
|
|
60
64
|
end
|
|
61
65
|
end
|
|
62
66
|
@_cancelled = result.to_i
|
|
@@ -66,6 +70,10 @@ module Sidekiq
|
|
|
66
70
|
@_cancelled
|
|
67
71
|
end
|
|
68
72
|
|
|
73
|
+
def cursor
|
|
74
|
+
@_cursor.freeze
|
|
75
|
+
end
|
|
76
|
+
|
|
69
77
|
# A hook to override that will be called when the job starts iterating.
|
|
70
78
|
#
|
|
71
79
|
# It is called only once, for the first time.
|
|
@@ -93,6 +101,11 @@ module Sidekiq
|
|
|
93
101
|
def on_stop
|
|
94
102
|
end
|
|
95
103
|
|
|
104
|
+
# A hook to override that will be called when the job is cancelled.
|
|
105
|
+
#
|
|
106
|
+
def on_cancel
|
|
107
|
+
end
|
|
108
|
+
|
|
96
109
|
# A hook to override that will be called when the job finished iterating.
|
|
97
110
|
#
|
|
98
111
|
def on_complete
|
|
@@ -130,7 +143,7 @@ module Sidekiq
|
|
|
130
143
|
fetch_previous_iteration_state
|
|
131
144
|
|
|
132
145
|
@_executions += 1
|
|
133
|
-
@_start_time =
|
|
146
|
+
@_start_time = mono_now
|
|
134
147
|
|
|
135
148
|
enumerator = build_enumerator(*args, cursor: @_cursor)
|
|
136
149
|
unless enumerator
|
|
@@ -164,7 +177,7 @@ module Sidekiq
|
|
|
164
177
|
private
|
|
165
178
|
|
|
166
179
|
def is_cancelled?
|
|
167
|
-
@_cancelled = Sidekiq.redis { |c| c.hget(
|
|
180
|
+
@_cancelled = Sidekiq.redis { |c| c.hget(iteration_key, "cancelled") }
|
|
168
181
|
end
|
|
169
182
|
|
|
170
183
|
def fetch_previous_iteration_state
|
|
@@ -184,34 +197,40 @@ module Sidekiq
|
|
|
184
197
|
|
|
185
198
|
def iterate_with_enumerator(enumerator, arguments)
|
|
186
199
|
if is_cancelled?
|
|
200
|
+
on_cancel
|
|
187
201
|
logger.info { "Job cancelled" }
|
|
188
202
|
return true
|
|
189
203
|
end
|
|
190
204
|
|
|
191
205
|
time_limit = Sidekiq.default_configuration[:timeout]
|
|
192
206
|
found_record = false
|
|
193
|
-
state_flushed_at =
|
|
207
|
+
state_flushed_at = mono_now
|
|
194
208
|
|
|
195
209
|
enumerator.each do |object, cursor|
|
|
196
210
|
found_record = true
|
|
197
211
|
@_cursor = cursor
|
|
212
|
+
@current_object = object
|
|
198
213
|
|
|
199
|
-
|
|
200
|
-
if
|
|
214
|
+
interrupt_job = interrupted? || should_interrupt?
|
|
215
|
+
if mono_now - state_flushed_at >= STATE_FLUSH_INTERVAL || interrupt_job
|
|
201
216
|
_, _, cancelled = flush_state
|
|
202
|
-
state_flushed_at =
|
|
217
|
+
state_flushed_at = mono_now
|
|
203
218
|
if cancelled
|
|
204
219
|
@_cancelled = true
|
|
220
|
+
on_cancel
|
|
205
221
|
logger.info { "Job cancelled" }
|
|
206
222
|
return true
|
|
207
223
|
end
|
|
208
224
|
end
|
|
209
225
|
|
|
210
|
-
return false if
|
|
226
|
+
return false if interrupt_job
|
|
211
227
|
|
|
212
|
-
verify_iteration_time(time_limit
|
|
228
|
+
verify_iteration_time(time_limit) do
|
|
213
229
|
around_iteration do
|
|
214
230
|
each_iteration(object, *arguments)
|
|
231
|
+
rescue Exception
|
|
232
|
+
flush_state
|
|
233
|
+
raise
|
|
215
234
|
end
|
|
216
235
|
end
|
|
217
236
|
end
|
|
@@ -219,16 +238,16 @@ module Sidekiq
|
|
|
219
238
|
logger.debug("Enumerator found nothing to iterate!") unless found_record
|
|
220
239
|
true
|
|
221
240
|
ensure
|
|
222
|
-
@_runtime += (
|
|
241
|
+
@_runtime += (mono_now - @_start_time)
|
|
223
242
|
end
|
|
224
243
|
|
|
225
|
-
def verify_iteration_time(time_limit
|
|
226
|
-
start =
|
|
244
|
+
def verify_iteration_time(time_limit)
|
|
245
|
+
start = mono_now
|
|
227
246
|
yield
|
|
228
|
-
finish =
|
|
247
|
+
finish = mono_now
|
|
229
248
|
total = finish - start
|
|
230
249
|
if total > time_limit
|
|
231
|
-
logger.warn { "Iteration took longer (%.2f) than Sidekiq's shutdown timeout (%d)
|
|
250
|
+
logger.warn { "Iteration took longer (%.2f) than Sidekiq's shutdown timeout (%d). This can lead to job processing problems during deploys" % [total, time_limit] }
|
|
232
251
|
end
|
|
233
252
|
end
|
|
234
253
|
|
|
@@ -254,6 +273,11 @@ module Sidekiq
|
|
|
254
273
|
end
|
|
255
274
|
end
|
|
256
275
|
|
|
276
|
+
def should_interrupt?
|
|
277
|
+
max_iteration_runtime = Sidekiq.default_configuration[:max_iteration_runtime]
|
|
278
|
+
max_iteration_runtime && (mono_now - @_start_time > max_iteration_runtime)
|
|
279
|
+
end
|
|
280
|
+
|
|
257
281
|
def flush_state
|
|
258
282
|
key = iteration_key
|
|
259
283
|
state = {
|
|
@@ -265,7 +289,7 @@ module Sidekiq
|
|
|
265
289
|
Sidekiq.redis do |conn|
|
|
266
290
|
conn.multi do |pipe|
|
|
267
291
|
pipe.hset(key, state)
|
|
268
|
-
pipe.expire(key, STATE_TTL)
|
|
292
|
+
pipe.expire(key, STATE_TTL, "nx")
|
|
269
293
|
pipe.hget(key, "cancelled")
|
|
270
294
|
end
|
|
271
295
|
end
|
|
@@ -289,6 +313,10 @@ module Sidekiq
|
|
|
289
313
|
raise "Unexpected thrown value: #{completed.inspect}"
|
|
290
314
|
end
|
|
291
315
|
end
|
|
316
|
+
|
|
317
|
+
def mono_now
|
|
318
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
319
|
+
end
|
|
292
320
|
end
|
|
293
321
|
end
|
|
294
322
|
end
|
data/lib/sidekiq/job.rb
CHANGED
|
@@ -248,9 +248,9 @@ module Sidekiq
|
|
|
248
248
|
end
|
|
249
249
|
alias_method :perform_sync, :perform_inline
|
|
250
250
|
|
|
251
|
-
def perform_bulk(args,
|
|
251
|
+
def perform_bulk(args, **options)
|
|
252
252
|
client = @klass.build_client
|
|
253
|
-
client.push_bulk(@opts.merge("class" => @klass, "args" => args,
|
|
253
|
+
client.push_bulk(@opts.merge({"class" => @klass, "args" => args}, options))
|
|
254
254
|
end
|
|
255
255
|
|
|
256
256
|
# +interval+ must be a timestamp, numeric or something that acts
|
data/lib/sidekiq/job_logger.rb
CHANGED
|
@@ -26,16 +26,18 @@ module Sidekiq
|
|
|
26
26
|
# If we're using a wrapper class, like ActiveJob, use the "wrapped"
|
|
27
27
|
# attribute to expose the underlying thing.
|
|
28
28
|
h = {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
jid: job_hash["jid"],
|
|
30
|
+
class: job_hash["wrapped"] || job_hash["class"]
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
|
|
33
|
+
@config[:logged_job_attributes].each do |attr|
|
|
34
|
+
h[attr.to_sym] = job_hash[attr] if job_hash.has_key?(attr)
|
|
35
|
+
end
|
|
34
36
|
|
|
35
37
|
Thread.current[:sidekiq_context] = h
|
|
36
38
|
level = job_hash["log_level"]
|
|
37
|
-
if level
|
|
38
|
-
@logger.
|
|
39
|
+
if level
|
|
40
|
+
@logger.with_level(level, &block)
|
|
39
41
|
else
|
|
40
42
|
yield
|
|
41
43
|
end
|
data/lib/sidekiq/job_retry.rb
CHANGED
|
@@ -139,6 +139,10 @@ module Sidekiq
|
|
|
139
139
|
|
|
140
140
|
private
|
|
141
141
|
|
|
142
|
+
def now_ms
|
|
143
|
+
::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
144
|
+
end
|
|
145
|
+
|
|
142
146
|
# Note that +jobinst+ can be nil here if an error is raised before we can
|
|
143
147
|
# instantiate the job instance. All access must be guarded and
|
|
144
148
|
# best effort.
|
|
@@ -149,17 +153,17 @@ module Sidekiq
|
|
|
149
153
|
|
|
150
154
|
m = exception_message(exception)
|
|
151
155
|
if m.respond_to?(:scrub!)
|
|
152
|
-
m.force_encoding(
|
|
156
|
+
m.force_encoding(Encoding::UTF_8)
|
|
153
157
|
m.scrub!
|
|
154
158
|
end
|
|
155
159
|
|
|
156
160
|
msg["error_message"] = m
|
|
157
161
|
msg["error_class"] = exception.class.name
|
|
158
162
|
count = if msg["retry_count"]
|
|
159
|
-
msg["retried_at"] =
|
|
163
|
+
msg["retried_at"] = now_ms
|
|
160
164
|
msg["retry_count"] += 1
|
|
161
165
|
else
|
|
162
|
-
msg["failed_at"] =
|
|
166
|
+
msg["failed_at"] = now_ms
|
|
163
167
|
msg["retry_count"] = 0
|
|
164
168
|
end
|
|
165
169
|
|
|
@@ -177,19 +181,21 @@ module Sidekiq
|
|
|
177
181
|
return retries_exhausted(jobinst, msg, exception) if count >= max_retry_attempts
|
|
178
182
|
|
|
179
183
|
rf = msg["retry_for"]
|
|
180
|
-
return retries_exhausted(jobinst, msg, exception) if rf && ((msg["failed_at"] + rf) < Time.now
|
|
184
|
+
return retries_exhausted(jobinst, msg, exception) if rf && (time_for(msg["failed_at"]) + rf) < Time.now
|
|
181
185
|
|
|
182
186
|
strategy, delay = delay_for(jobinst, count, exception, msg)
|
|
183
187
|
case strategy
|
|
184
188
|
when :discard
|
|
185
|
-
|
|
189
|
+
msg["discarded_at"] = now_ms
|
|
190
|
+
|
|
191
|
+
return run_death_handlers(msg, exception)
|
|
186
192
|
when :kill
|
|
187
193
|
return retries_exhausted(jobinst, msg, exception)
|
|
188
194
|
end
|
|
189
195
|
|
|
190
196
|
# Logging here can break retries if the logging device raises ENOSPC #3979
|
|
191
197
|
# logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
|
|
192
|
-
jitter = rand(10
|
|
198
|
+
jitter = rand(10 * (count + 1))
|
|
193
199
|
retry_at = Time.now.to_f + delay + jitter
|
|
194
200
|
payload = Sidekiq.dump_json(msg)
|
|
195
201
|
redis do |conn|
|
|
@@ -197,6 +203,14 @@ module Sidekiq
|
|
|
197
203
|
end
|
|
198
204
|
end
|
|
199
205
|
|
|
206
|
+
def time_for(item)
|
|
207
|
+
if item.is_a?(Float)
|
|
208
|
+
Time.at(item)
|
|
209
|
+
else
|
|
210
|
+
Time.at(item / 1000, item % 1000)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
200
214
|
# returns (strategy, seconds)
|
|
201
215
|
def delay_for(jobinst, count, exception, msg)
|
|
202
216
|
rv = begin
|
|
@@ -243,13 +257,22 @@ module Sidekiq
|
|
|
243
257
|
handle_exception(e, {context: "Error calling retries_exhausted", job: msg})
|
|
244
258
|
end
|
|
245
259
|
|
|
246
|
-
|
|
247
|
-
|
|
260
|
+
discarded = msg["dead"] == false || rv == :discard
|
|
261
|
+
|
|
262
|
+
if discarded
|
|
263
|
+
msg["discarded_at"] = now_ms
|
|
264
|
+
else
|
|
265
|
+
send_to_morgue(msg)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
run_death_handlers(msg, exception)
|
|
269
|
+
end
|
|
248
270
|
|
|
271
|
+
def run_death_handlers(job, exception)
|
|
249
272
|
@capsule.config.death_handlers.each do |handler|
|
|
250
|
-
handler.call(
|
|
273
|
+
handler.call(job, exception)
|
|
251
274
|
rescue => e
|
|
252
|
-
handle_exception(e, {context: "Error calling death handler", job:
|
|
275
|
+
handle_exception(e, {context: "Error calling death handler", job: job})
|
|
253
276
|
end
|
|
254
277
|
end
|
|
255
278
|
|
data/lib/sidekiq/job_util.rb
CHANGED
|
@@ -58,10 +58,14 @@ module Sidekiq
|
|
|
58
58
|
item["class"] = item["class"].to_s
|
|
59
59
|
item["queue"] = item["queue"].to_s
|
|
60
60
|
item["retry_for"] = item["retry_for"].to_i if item["retry_for"]
|
|
61
|
-
item["created_at"] ||=
|
|
61
|
+
item["created_at"] ||= now_in_millis
|
|
62
62
|
item
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
def now_in_millis
|
|
66
|
+
::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
67
|
+
end
|
|
68
|
+
|
|
65
69
|
def normalized_hash(item_class)
|
|
66
70
|
if item_class.is_a?(Class)
|
|
67
71
|
raise(ArgumentError, "Message must include a Sidekiq::Job class, not class name: #{item_class.ancestors.inspect}") unless item_class.respond_to?(:get_sidekiq_options)
|
data/lib/sidekiq/launcher.rb
CHANGED
|
@@ -68,6 +68,7 @@ module Sidekiq
|
|
|
68
68
|
stoppers.each(&:join)
|
|
69
69
|
|
|
70
70
|
clear_heartbeat
|
|
71
|
+
fire_event(:exit, reverse: true)
|
|
71
72
|
end
|
|
72
73
|
|
|
73
74
|
def stopping?
|
|
@@ -81,7 +82,7 @@ module Sidekiq
|
|
|
81
82
|
❤
|
|
82
83
|
end
|
|
83
84
|
|
|
84
|
-
private
|
|
85
|
+
private
|
|
85
86
|
|
|
86
87
|
BEAT_PAUSE = 10
|
|
87
88
|
|
|
@@ -141,6 +142,12 @@ module Sidekiq
|
|
|
141
142
|
key = identity
|
|
142
143
|
fails = procd = 0
|
|
143
144
|
|
|
145
|
+
idle = config[:reap_connections]
|
|
146
|
+
if idle
|
|
147
|
+
config.capsules.each_value { |cap| cap.local_redis_pool.reap(idle, &:close) }
|
|
148
|
+
config.local_redis_pool.reap(idle, &:close)
|
|
149
|
+
end
|
|
150
|
+
|
|
144
151
|
begin
|
|
145
152
|
flush_stats
|
|
146
153
|
|
|
@@ -213,11 +220,11 @@ module Sidekiq
|
|
|
213
220
|
# Log a warning if it's a disaster.
|
|
214
221
|
if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
|
|
215
222
|
logger.warn <<~EOM
|
|
216
|
-
Your Redis network connection
|
|
223
|
+
Your Redis network connection appears to be performing poorly.
|
|
217
224
|
Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
|
|
218
|
-
Ensure Redis is running in the same AZ or datacenter as Sidekiq
|
|
219
|
-
|
|
220
|
-
|
|
225
|
+
Ensure Redis is running in the same AZ or datacenter as Sidekiq and that
|
|
226
|
+
your Sidekiq process is not CPU-saturated; reduce your concurrency and/or
|
|
227
|
+
see https://github.com/sidekiq/sidekiq/discussions/5039
|
|
221
228
|
EOM
|
|
222
229
|
RTT_READINGS.reset
|
|
223
230
|
end
|
|
@@ -251,8 +258,15 @@ module Sidekiq
|
|
|
251
258
|
"pid" => ::Process.pid,
|
|
252
259
|
"tag" => @config[:tag] || "",
|
|
253
260
|
"concurrency" => @config.total_concurrency,
|
|
261
|
+
"capsules" => @config.capsules.each_with_object({}) { |(name, cap), memo|
|
|
262
|
+
memo[name] = cap.to_h
|
|
263
|
+
},
|
|
264
|
+
#####
|
|
265
|
+
# TODO deprecated, remove in 9.0
|
|
266
|
+
# This data is now found in the `capsules` element above
|
|
254
267
|
"queues" => @config.capsules.values.flat_map { |cap| cap.queues }.uniq,
|
|
255
|
-
"weights" =>
|
|
268
|
+
"weights" => @config.capsules.values.map(&:weights),
|
|
269
|
+
#####
|
|
256
270
|
"labels" => @config[:labels].to_a,
|
|
257
271
|
"identity" => identity,
|
|
258
272
|
"version" => Sidekiq::VERSION,
|
|
@@ -260,10 +274,6 @@ module Sidekiq
|
|
|
260
274
|
}
|
|
261
275
|
end
|
|
262
276
|
|
|
263
|
-
def to_weights
|
|
264
|
-
@config.capsules.values.map(&:weights)
|
|
265
|
-
end
|
|
266
|
-
|
|
267
277
|
def to_json
|
|
268
278
|
# this data changes infrequently so dump it to a string
|
|
269
279
|
# now so we don't need to dump it every heartbeat.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Sidekiq
|
|
2
|
+
require "sidekiq/component"
|
|
3
|
+
|
|
4
|
+
class Loader
|
|
5
|
+
include Sidekiq::Component
|
|
6
|
+
|
|
7
|
+
def initialize(cfg = Sidekiq.default_configuration)
|
|
8
|
+
@config = cfg
|
|
9
|
+
@load_hooks = Hash.new { |h, k| h[k] = [] }
|
|
10
|
+
@loaded = Set.new
|
|
11
|
+
@lock = Mutex.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Declares a block that will be executed when a Sidekiq component is fully
|
|
15
|
+
# loaded. If the component has already loaded, the block is executed
|
|
16
|
+
# immediately.
|
|
17
|
+
#
|
|
18
|
+
# Sidekiq.loader.on_load(:api) do
|
|
19
|
+
# # extend the sidekiq API
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
def on_load(name, &block)
|
|
23
|
+
# we don't want to hold the lock while calling the block
|
|
24
|
+
to_run = nil
|
|
25
|
+
|
|
26
|
+
@lock.synchronize do
|
|
27
|
+
if @loaded.include?(name)
|
|
28
|
+
to_run = block
|
|
29
|
+
else
|
|
30
|
+
@load_hooks[name] << block
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
to_run&.call
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Executes all blocks registered to +name+ via on_load.
|
|
39
|
+
#
|
|
40
|
+
# Sidekiq.loader.run_load_hooks(:api)
|
|
41
|
+
#
|
|
42
|
+
# In the case of the above example, it will execute all hooks registered for +:api+.
|
|
43
|
+
#
|
|
44
|
+
def run_load_hooks(name)
|
|
45
|
+
hks = @lock.synchronize do
|
|
46
|
+
@loaded << name
|
|
47
|
+
@load_hooks.delete(name)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
hks&.each do |blk|
|
|
51
|
+
blk.call
|
|
52
|
+
rescue => ex
|
|
53
|
+
handle_exception(ex, hook: name)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|