sidekiq 7.3.0 → 8.0.5
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 +158 -0
- data/README.md +16 -13
- data/bin/sidekiqload +31 -22
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +120 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/sidekiq/api.rb +184 -71
- data/lib/sidekiq/capsule.rb +11 -9
- data/lib/sidekiq/cli.rb +16 -20
- data/lib/sidekiq/client.rb +28 -11
- data/lib/sidekiq/component.rb +62 -2
- data/lib/sidekiq/config.rb +42 -18
- data/lib/sidekiq/deploy.rb +2 -0
- data/lib/sidekiq/embedded.rb +4 -1
- data/lib/sidekiq/iterable_job.rb +3 -0
- data/lib/sidekiq/job/interrupt_handler.rb +2 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +3 -3
- data/lib/sidekiq/job/iterable.rb +82 -7
- data/lib/sidekiq/job_logger.rb +15 -27
- data/lib/sidekiq/job_retry.rb +17 -5
- data/lib/sidekiq/job_util.rb +7 -1
- data/lib/sidekiq/launcher.rb +3 -2
- data/lib/sidekiq/logger.rb +19 -70
- data/lib/sidekiq/manager.rb +0 -1
- data/lib/sidekiq/metrics/query.rb +73 -45
- data/lib/sidekiq/metrics/shared.rb +23 -9
- data/lib/sidekiq/metrics/tracking.rb +22 -12
- data/lib/sidekiq/middleware/current_attributes.rb +12 -4
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +2 -1
- data/lib/sidekiq/paginator.rb +14 -1
- data/lib/sidekiq/processor.rb +26 -19
- data/lib/sidekiq/profiler.rb +72 -0
- data/lib/sidekiq/rails.rb +44 -55
- data/lib/sidekiq/redis_client_adapter.rb +0 -1
- data/lib/sidekiq/redis_connection.rb +22 -4
- data/lib/sidekiq/ring_buffer.rb +2 -0
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/testing.rb +7 -7
- data/lib/sidekiq/version.rb +6 -2
- data/lib/sidekiq/web/action.rb +124 -69
- data/lib/sidekiq/web/application.rb +355 -377
- data/lib/sidekiq/web/config.rb +120 -0
- data/lib/sidekiq/web/helpers.rb +64 -33
- data/lib/sidekiq/web/router.rb +61 -74
- data/lib/sidekiq/web.rb +52 -150
- data/lib/sidekiq.rb +5 -4
- data/sidekiq.gemspec +6 -6
- data/web/assets/javascripts/application.js +6 -13
- 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-charts.js +2 -0
- data/web/assets/javascripts/dashboard.js +7 -1
- data/web/assets/javascripts/metrics.js +16 -34
- data/web/assets/stylesheets/style.css +766 -0
- data/web/locales/ar.yml +1 -0
- data/web/locales/cs.yml +1 -0
- data/web/locales/da.yml +1 -0
- data/web/locales/de.yml +1 -0
- data/web/locales/el.yml +1 -0
- data/web/locales/en.yml +9 -1
- data/web/locales/es.yml +24 -2
- data/web/locales/fa.yml +1 -0
- data/web/locales/fr.yml +1 -1
- data/web/locales/gd.yml +1 -1
- data/web/locales/he.yml +1 -0
- data/web/locales/hi.yml +1 -0
- data/web/locales/it.yml +40 -1
- data/web/locales/ja.yml +1 -1
- data/web/locales/ko.yml +1 -0
- data/web/locales/lt.yml +1 -0
- data/web/locales/nb.yml +1 -0
- data/web/locales/nl.yml +1 -0
- data/web/locales/pl.yml +1 -0
- data/web/locales/{pt-br.yml → pt-BR.yml} +3 -3
- data/web/locales/pt.yml +1 -0
- data/web/locales/ru.yml +1 -0
- data/web/locales/sv.yml +1 -0
- data/web/locales/ta.yml +1 -0
- data/web/locales/tr.yml +2 -2
- data/web/locales/uk.yml +25 -1
- data/web/locales/ur.yml +1 -0
- data/web/locales/vi.yml +1 -0
- data/web/locales/{zh-cn.yml → zh-CN.yml} +85 -74
- data/web/locales/{zh-tw.yml → zh-TW.yml} +2 -2
- data/web/views/_footer.erb +31 -34
- 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 +23 -21
- data/web/views/_poll_link.erb +2 -2
- data/web/views/_summary.erb +16 -16
- data/web/views/busy.erb +124 -122
- data/web/views/dashboard.erb +63 -64
- data/web/views/dead.erb +31 -27
- data/web/views/filtering.erb +3 -4
- data/web/views/layout.erb +13 -29
- data/web/views/metrics.erb +75 -82
- data/web/views/metrics_for_job.erb +45 -46
- data/web/views/morgue.erb +61 -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 +66 -75
- data/web/views/retry.erb +32 -27
- data/web/views/scheduled.erb +59 -55
- data/web/views/scheduled_job_info.erb +1 -1
- metadata +27 -29
- 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 -758
- 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,6 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "forwardable"
|
4
4
|
require "sidekiq/redis_connection"
|
5
5
|
|
6
6
|
module Sidekiq
|
@@ -27,6 +27,7 @@ module Sidekiq
|
|
27
27
|
startup: [],
|
28
28
|
quiet: [],
|
29
29
|
shutdown: [],
|
30
|
+
exit: [],
|
30
31
|
# triggers when we fire the first heartbeat on startup OR repairing a network partition
|
31
32
|
heartbeat: [],
|
32
33
|
# triggers on EVERY heartbeat call, every 10 seconds
|
@@ -39,12 +40,22 @@ module Sidekiq
|
|
39
40
|
}
|
40
41
|
|
41
42
|
ERROR_HANDLER = ->(ex, ctx, cfg = Sidekiq.default_configuration) {
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
Sidekiq::Context.with(ctx) do
|
44
|
+
dev = cfg[:environment] == "development"
|
45
|
+
fancy = dev && $stdout.tty? # 🎩
|
46
|
+
# Weird logic here but we want to show the backtrace in local
|
47
|
+
# development or if verbose logging is enabled.
|
48
|
+
#
|
49
|
+
# `full_message` contains the error class, message and backtrace
|
50
|
+
# `detailed_message` contains the error class and message
|
51
|
+
#
|
52
|
+
# Absolutely terrible API names. Not useful at all to have two
|
53
|
+
# methods with similar but obscure names.
|
54
|
+
if dev || cfg.logger.debug?
|
55
|
+
cfg.logger.info { ex.full_message(highlight: fancy) }
|
56
|
+
else
|
57
|
+
cfg.logger.info { ex.detailed_message(highlight: fancy) }
|
58
|
+
end
|
48
59
|
end
|
49
60
|
}
|
50
61
|
|
@@ -58,6 +69,13 @@ module Sidekiq
|
|
58
69
|
|
59
70
|
def_delegators :@options, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig
|
60
71
|
attr_reader :capsules
|
72
|
+
attr_accessor :thread_priority
|
73
|
+
|
74
|
+
def inspect
|
75
|
+
"#<#{self.class.name} @options=#{
|
76
|
+
@options.except(:lifecycle_events, :reloader, :death_handlers, :error_handlers).inspect
|
77
|
+
}>"
|
78
|
+
end
|
61
79
|
|
62
80
|
def to_json(*)
|
63
81
|
Sidekiq.dump_json(@options)
|
@@ -183,7 +201,13 @@ module Sidekiq
|
|
183
201
|
|
184
202
|
# register global singletons which can be accessed elsewhere
|
185
203
|
def register(name, instance)
|
186
|
-
|
204
|
+
# logger.debug("register[#{name}] = #{instance}")
|
205
|
+
# Sidekiq Enterprise lazy registers a few services so we
|
206
|
+
# can't lock down this hash completely.
|
207
|
+
hash = @directory.dup
|
208
|
+
hash[name] = instance
|
209
|
+
@directory = hash.freeze
|
210
|
+
instance
|
187
211
|
end
|
188
212
|
|
189
213
|
# find a singleton
|
@@ -191,10 +215,16 @@ module Sidekiq
|
|
191
215
|
# JNDI is just a fancy name for a hash lookup
|
192
216
|
@directory.fetch(name) do |key|
|
193
217
|
return nil unless default_class
|
194
|
-
|
218
|
+
register(key, default_class.new(self))
|
195
219
|
end
|
196
220
|
end
|
197
221
|
|
222
|
+
def freeze!
|
223
|
+
@directory.freeze
|
224
|
+
@options.freeze
|
225
|
+
true
|
226
|
+
end
|
227
|
+
|
198
228
|
##
|
199
229
|
# Death handlers are called when all retries for a job have been exhausted and
|
200
230
|
# the job dies. It's the notification to your application
|
@@ -229,7 +259,7 @@ module Sidekiq
|
|
229
259
|
end
|
230
260
|
|
231
261
|
# Register a block to run at a point in the Sidekiq lifecycle.
|
232
|
-
# :startup, :quiet or :
|
262
|
+
# :startup, :quiet, :shutdown, or :exit are valid events.
|
233
263
|
#
|
234
264
|
# Sidekiq.configure_server do |config|
|
235
265
|
# config.on(:shutdown) do
|
@@ -273,13 +303,7 @@ module Sidekiq
|
|
273
303
|
p ["!!!!!", ex]
|
274
304
|
end
|
275
305
|
@options[:error_handlers].each do |handler|
|
276
|
-
|
277
|
-
# TODO Remove in 8.0
|
278
|
-
logger.info { "DEPRECATION: Sidekiq exception handlers now take three arguments, see #{handler}" }
|
279
|
-
handler.call(ex, {_config: self}.merge(ctx))
|
280
|
-
else
|
281
|
-
handler.call(ex, ctx, self)
|
282
|
-
end
|
306
|
+
handler.call(ex, ctx, self)
|
283
307
|
rescue Exception => e
|
284
308
|
l = logger
|
285
309
|
l.error "!!! ERROR HANDLER THREW AN ERROR !!!"
|
data/lib/sidekiq/deploy.rb
CHANGED
data/lib/sidekiq/embedded.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "sidekiq/component"
|
2
4
|
require "sidekiq/launcher"
|
3
5
|
require "sidekiq/metrics/tracking"
|
@@ -32,6 +34,7 @@ module Sidekiq
|
|
32
34
|
private
|
33
35
|
|
34
36
|
def housekeeping
|
37
|
+
@config[:tag] ||= default_tag
|
35
38
|
logger.info "Running in #{RUBY_DESCRIPTION}"
|
36
39
|
logger.info Sidekiq::LICENSE
|
37
40
|
logger.info "Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org" unless defined?(::Sidekiq::Pro)
|
@@ -40,7 +43,7 @@ module Sidekiq
|
|
40
43
|
# fire startup and start multithreading.
|
41
44
|
info = config.redis_info
|
42
45
|
ver = Gem::Version.new(info["redis_version"])
|
43
|
-
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")
|
44
47
|
|
45
48
|
maxmemory_policy = info["maxmemory_policy"]
|
46
49
|
if maxmemory_policy != "noeviction"
|
data/lib/sidekiq/iterable_job.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "sidekiq/job/iterable"
|
2
4
|
|
3
5
|
# Iterable jobs are ones which provide a sequence to process using
|
@@ -44,6 +46,7 @@ module Sidekiq
|
|
44
46
|
# def on_start
|
45
47
|
# def on_resume
|
46
48
|
# def on_stop
|
49
|
+
# def on_cancel
|
47
50
|
# def on_complete
|
48
51
|
# def around_iteration
|
49
52
|
#
|
@@ -22,7 +22,7 @@ module Sidekiq
|
|
22
22
|
def batches
|
23
23
|
Enumerator.new(-> { @relation.count }) do |yielder|
|
24
24
|
@relation.find_in_batches(**@options, start: @cursor) do |batch|
|
25
|
-
yielder.yield(batch, batch.
|
25
|
+
yielder.yield(batch, batch.first.id)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -35,8 +35,8 @@ module Sidekiq
|
|
35
35
|
options[:of] ||= options.delete(:batch_size)
|
36
36
|
|
37
37
|
@relation.in_batches(**options, start: @cursor) do |relation|
|
38
|
-
|
39
|
-
yielder.yield(relation,
|
38
|
+
first_record = relation.first
|
39
|
+
yielder.yield(relation, first_record.id)
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
data/lib/sidekiq/job/iterable.rb
CHANGED
@@ -30,6 +30,42 @@ module Sidekiq
|
|
30
30
|
@_cursor = nil
|
31
31
|
@_start_time = nil
|
32
32
|
@_runtime = 0
|
33
|
+
@_args = nil
|
34
|
+
@_cancelled = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def arguments
|
38
|
+
@_args
|
39
|
+
end
|
40
|
+
|
41
|
+
# Three days is the longest period you generally need to wait for a retry to
|
42
|
+
# execute when using the default retry scheme. We don't want to "forget" the job
|
43
|
+
# is cancelled before it has a chance to execute and cancel itself.
|
44
|
+
CANCELLATION_PERIOD = (3 * 86_400).to_s
|
45
|
+
|
46
|
+
# Set a flag in Redis to mark this job as cancelled.
|
47
|
+
# Cancellation is asynchronous and is checked at the start of iteration
|
48
|
+
# and every 5 seconds thereafter as part of the recurring state flush.
|
49
|
+
def cancel!
|
50
|
+
return @_cancelled if cancelled?
|
51
|
+
|
52
|
+
key = "it-#{jid}"
|
53
|
+
_, result, _ = Sidekiq.redis do |c|
|
54
|
+
c.pipelined do |p|
|
55
|
+
p.hsetnx(key, "cancelled", Time.now.to_i)
|
56
|
+
p.hget(key, "cancelled")
|
57
|
+
p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
@_cancelled = result.to_i
|
61
|
+
end
|
62
|
+
|
63
|
+
def cancelled?
|
64
|
+
@_cancelled
|
65
|
+
end
|
66
|
+
|
67
|
+
def cursor
|
68
|
+
@_cursor.freeze
|
33
69
|
end
|
34
70
|
|
35
71
|
# A hook to override that will be called when the job starts iterating.
|
@@ -59,6 +95,11 @@ module Sidekiq
|
|
59
95
|
def on_stop
|
60
96
|
end
|
61
97
|
|
98
|
+
# A hook to override that will be called when the job is cancelled.
|
99
|
+
#
|
100
|
+
def on_cancel
|
101
|
+
end
|
102
|
+
|
62
103
|
# A hook to override that will be called when the job finished iterating.
|
63
104
|
#
|
64
105
|
def on_complete
|
@@ -91,13 +132,14 @@ module Sidekiq
|
|
91
132
|
end
|
92
133
|
|
93
134
|
# @api private
|
94
|
-
def perform(*
|
135
|
+
def perform(*args)
|
136
|
+
@_args = args.dup.freeze
|
95
137
|
fetch_previous_iteration_state
|
96
138
|
|
97
139
|
@_executions += 1
|
98
140
|
@_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
99
141
|
|
100
|
-
enumerator = build_enumerator(*
|
142
|
+
enumerator = build_enumerator(*args, cursor: @_cursor)
|
101
143
|
unless enumerator
|
102
144
|
logger.info("'#build_enumerator' returned nil, skipping the job.")
|
103
145
|
return
|
@@ -112,7 +154,7 @@ module Sidekiq
|
|
112
154
|
end
|
113
155
|
|
114
156
|
completed = catch(:abort) do
|
115
|
-
iterate_with_enumerator(enumerator,
|
157
|
+
iterate_with_enumerator(enumerator, args)
|
116
158
|
end
|
117
159
|
|
118
160
|
on_stop
|
@@ -128,6 +170,10 @@ module Sidekiq
|
|
128
170
|
|
129
171
|
private
|
130
172
|
|
173
|
+
def is_cancelled?
|
174
|
+
@_cancelled = Sidekiq.redis { |c| c.hget("it-#{jid}", "cancelled") }
|
175
|
+
end
|
176
|
+
|
131
177
|
def fetch_previous_iteration_state
|
132
178
|
state = Sidekiq.redis { |conn| conn.hgetall(iteration_key) }
|
133
179
|
|
@@ -144,6 +190,13 @@ module Sidekiq
|
|
144
190
|
STATE_TTL = 30 * 24 * 60 * 60 # one month
|
145
191
|
|
146
192
|
def iterate_with_enumerator(enumerator, arguments)
|
193
|
+
if is_cancelled?
|
194
|
+
on_cancel
|
195
|
+
logger.info { "Job cancelled" }
|
196
|
+
return true
|
197
|
+
end
|
198
|
+
|
199
|
+
time_limit = Sidekiq.default_configuration[:timeout]
|
147
200
|
found_record = false
|
148
201
|
state_flushed_at = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
149
202
|
|
@@ -153,14 +206,25 @@ module Sidekiq
|
|
153
206
|
|
154
207
|
is_interrupted = interrupted?
|
155
208
|
if ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - state_flushed_at >= STATE_FLUSH_INTERVAL || is_interrupted
|
156
|
-
flush_state
|
209
|
+
_, _, cancelled = flush_state
|
157
210
|
state_flushed_at = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
211
|
+
if cancelled
|
212
|
+
@_cancelled = true
|
213
|
+
on_cancel
|
214
|
+
logger.info { "Job cancelled" }
|
215
|
+
return true
|
216
|
+
end
|
158
217
|
end
|
159
218
|
|
160
219
|
return false if is_interrupted
|
161
220
|
|
162
|
-
|
163
|
-
|
221
|
+
verify_iteration_time(time_limit, object) do
|
222
|
+
around_iteration do
|
223
|
+
each_iteration(object, *arguments)
|
224
|
+
rescue Exception
|
225
|
+
flush_state
|
226
|
+
raise
|
227
|
+
end
|
164
228
|
end
|
165
229
|
end
|
166
230
|
|
@@ -170,6 +234,16 @@ module Sidekiq
|
|
170
234
|
@_runtime += (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @_start_time)
|
171
235
|
end
|
172
236
|
|
237
|
+
def verify_iteration_time(time_limit, object)
|
238
|
+
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
239
|
+
yield
|
240
|
+
finish = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
241
|
+
total = finish - start
|
242
|
+
if total > time_limit
|
243
|
+
logger.warn { "Iteration took longer (%.2f) than Sidekiq's shutdown timeout (%d) when processing `%s`. This can lead to job processing problems during deploys" % [total, time_limit, object] }
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
173
247
|
def reenqueue_iteration_job
|
174
248
|
flush_state
|
175
249
|
logger.debug { "Interrupting job (cursor=#{@_cursor.inspect})" }
|
@@ -203,7 +277,8 @@ module Sidekiq
|
|
203
277
|
Sidekiq.redis do |conn|
|
204
278
|
conn.multi do |pipe|
|
205
279
|
pipe.hset(key, state)
|
206
|
-
pipe.expire(key, STATE_TTL)
|
280
|
+
pipe.expire(key, STATE_TTL, "nx")
|
281
|
+
pipe.hget(key, "cancelled")
|
207
282
|
end
|
208
283
|
end
|
209
284
|
end
|
data/lib/sidekiq/job_logger.rb
CHANGED
@@ -2,52 +2,40 @@
|
|
2
2
|
|
3
3
|
module Sidekiq
|
4
4
|
class JobLogger
|
5
|
-
include Sidekiq::Component
|
6
|
-
|
7
5
|
def initialize(config)
|
8
6
|
@config = config
|
9
|
-
@logger = logger
|
10
|
-
|
11
|
-
|
12
|
-
# If true we won't do any job logging out of the box.
|
13
|
-
# The user is responsible for any logging.
|
14
|
-
def skip_default_logging?
|
15
|
-
config[:skip_default_job_logging]
|
7
|
+
@logger = @config.logger
|
8
|
+
@skip = !!@config[:skip_default_job_logging]
|
16
9
|
end
|
17
10
|
|
18
11
|
def call(item, queue)
|
19
|
-
|
12
|
+
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
13
|
+
@logger.info { "start" } unless @skip
|
20
14
|
|
21
|
-
|
22
|
-
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
23
|
-
@logger.info("start")
|
15
|
+
yield
|
24
16
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
@logger.info("fail")
|
32
|
-
|
33
|
-
raise
|
34
|
-
end
|
17
|
+
Sidekiq::Context.add(:elapsed, elapsed(start))
|
18
|
+
@logger.info { "done" } unless @skip
|
19
|
+
rescue Exception
|
20
|
+
Sidekiq::Context.add(:elapsed, elapsed(start))
|
21
|
+
@logger.info { "fail" } unless @skip
|
22
|
+
raise
|
35
23
|
end
|
36
24
|
|
37
25
|
def prepare(job_hash, &block)
|
38
26
|
# If we're using a wrapper class, like ActiveJob, use the "wrapped"
|
39
27
|
# attribute to expose the underlying thing.
|
40
28
|
h = {
|
41
|
-
|
42
|
-
|
29
|
+
jid: job_hash["jid"],
|
30
|
+
class: job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
|
43
31
|
}
|
44
32
|
h[:bid] = job_hash["bid"] if job_hash.has_key?("bid")
|
45
33
|
h[:tags] = job_hash["tags"] if job_hash.has_key?("tags")
|
46
34
|
|
47
35
|
Thread.current[:sidekiq_context] = h
|
48
36
|
level = job_hash["log_level"]
|
49
|
-
if level
|
50
|
-
@logger.
|
37
|
+
if level
|
38
|
+
@logger.with_level(level, &block)
|
51
39
|
else
|
52
40
|
yield
|
53
41
|
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,7 +181,7 @@ 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
|
@@ -189,7 +193,7 @@ module Sidekiq
|
|
189
193
|
|
190
194
|
# Logging here can break retries if the logging device raises ENOSPC #3979
|
191
195
|
# logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
|
192
|
-
jitter = rand(10
|
196
|
+
jitter = rand(10 * (count + 1))
|
193
197
|
retry_at = Time.now.to_f + delay + jitter
|
194
198
|
payload = Sidekiq.dump_json(msg)
|
195
199
|
redis do |conn|
|
@@ -197,6 +201,14 @@ module Sidekiq
|
|
197
201
|
end
|
198
202
|
end
|
199
203
|
|
204
|
+
def time_for(item)
|
205
|
+
if item.is_a?(Float)
|
206
|
+
Time.at(item)
|
207
|
+
else
|
208
|
+
Time.at(item / 1000, item % 1000)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
200
212
|
# returns (strategy, seconds)
|
201
213
|
def delay_for(jobinst, count, exception, msg)
|
202
214
|
rv = begin
|
data/lib/sidekiq/job_util.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "securerandom"
|
2
4
|
require "time"
|
3
5
|
|
@@ -56,10 +58,14 @@ module Sidekiq
|
|
56
58
|
item["class"] = item["class"].to_s
|
57
59
|
item["queue"] = item["queue"].to_s
|
58
60
|
item["retry_for"] = item["retry_for"].to_i if item["retry_for"]
|
59
|
-
item["created_at"] ||=
|
61
|
+
item["created_at"] ||= now_in_millis
|
60
62
|
item
|
61
63
|
end
|
62
64
|
|
65
|
+
def now_in_millis
|
66
|
+
::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
67
|
+
end
|
68
|
+
|
63
69
|
def normalized_hash(item_class)
|
64
70
|
if item_class.is_a?(Class)
|
65
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
@@ -36,8 +36,8 @@ module Sidekiq
|
|
36
36
|
# has a heartbeat thread, caller can use `async_beat: false`
|
37
37
|
# and instead have thread call Launcher#heartbeat every N seconds.
|
38
38
|
def run(async_beat: true)
|
39
|
-
Sidekiq.freeze!
|
40
39
|
logger.debug { @config.merge!({}) }
|
40
|
+
Sidekiq.freeze!
|
41
41
|
@thread = safe_thread("heartbeat", &method(:start_heartbeat)) if async_beat
|
42
42
|
@poller.start
|
43
43
|
@managers.each(&:start)
|
@@ -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
|
|
data/lib/sidekiq/logger.rb
CHANGED
@@ -22,92 +22,41 @@ module Sidekiq
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
module LoggingUtils
|
26
|
-
LEVELS = {
|
27
|
-
"debug" => 0,
|
28
|
-
"info" => 1,
|
29
|
-
"warn" => 2,
|
30
|
-
"error" => 3,
|
31
|
-
"fatal" => 4
|
32
|
-
}
|
33
|
-
LEVELS.default_proc = proc do |_, level|
|
34
|
-
puts("Invalid log level: #{level.inspect}")
|
35
|
-
nil
|
36
|
-
end
|
37
|
-
|
38
|
-
LEVELS.each do |level, numeric_level|
|
39
|
-
define_method(:"#{level}?") do
|
40
|
-
local_level.nil? ? super() : local_level <= numeric_level
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def local_level
|
45
|
-
Thread.current[:sidekiq_log_level]
|
46
|
-
end
|
47
|
-
|
48
|
-
def local_level=(level)
|
49
|
-
case level
|
50
|
-
when Integer
|
51
|
-
Thread.current[:sidekiq_log_level] = level
|
52
|
-
when Symbol, String
|
53
|
-
Thread.current[:sidekiq_log_level] = LEVELS[level.to_s]
|
54
|
-
when nil
|
55
|
-
Thread.current[:sidekiq_log_level] = nil
|
56
|
-
else
|
57
|
-
raise ArgumentError, "Invalid log level: #{level.inspect}"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def level
|
62
|
-
local_level || super
|
63
|
-
end
|
64
|
-
|
65
|
-
# Change the thread-local level for the duration of the given block.
|
66
|
-
def log_at(level)
|
67
|
-
old_local_level = local_level
|
68
|
-
self.local_level = level
|
69
|
-
yield
|
70
|
-
ensure
|
71
|
-
self.local_level = old_local_level
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
25
|
class Logger < ::Logger
|
76
|
-
include LoggingUtils
|
77
|
-
|
78
26
|
module Formatters
|
27
|
+
COLORS = {
|
28
|
+
"DEBUG" => "\e[1;32mDEBUG\e[0m", # green
|
29
|
+
"INFO" => "\e[1;34mINFO \e[0m", # blue
|
30
|
+
"WARN" => "\e[1;33mWARN \e[0m", # yellow
|
31
|
+
"ERROR" => "\e[1;31mERROR\e[0m", # red
|
32
|
+
"FATAL" => "\e[1;35mFATAL\e[0m" # pink
|
33
|
+
}
|
79
34
|
class Base < ::Logger::Formatter
|
80
35
|
def tid
|
81
36
|
Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
|
82
37
|
end
|
83
38
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
"#{k}=#{v.join(",")}"
|
94
|
-
else
|
95
|
-
"#{k}=#{v}"
|
96
|
-
end
|
97
|
-
}.join(" ")
|
98
|
-
end
|
39
|
+
def format_context(ctxt = Sidekiq::Context.current)
|
40
|
+
(ctxt.size == 0) ? "" : " #{ctxt.map { |k, v|
|
41
|
+
case v
|
42
|
+
when Array
|
43
|
+
"#{k}=#{v.join(",")}"
|
44
|
+
else
|
45
|
+
"#{k}=#{v}"
|
46
|
+
end
|
47
|
+
}.join(" ")}"
|
99
48
|
end
|
100
49
|
end
|
101
50
|
|
102
51
|
class Pretty < Base
|
103
52
|
def call(severity, time, program_name, message)
|
104
|
-
"#{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context}
|
53
|
+
"#{Formatters::COLORS[severity]} #{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context}: #{message}\n"
|
105
54
|
end
|
106
55
|
end
|
107
56
|
|
108
57
|
class WithoutTimestamp < Pretty
|
109
58
|
def call(severity, time, program_name, message)
|
110
|
-
"pid=#{::Process.pid} tid=#{tid}#{format_context}
|
59
|
+
"#{Formatters::COLORS[severity]} pid=#{::Process.pid} tid=#{tid} #{format_context}: #{message}\n"
|
111
60
|
end
|
112
61
|
end
|
113
62
|
|
@@ -120,7 +69,7 @@ module Sidekiq
|
|
120
69
|
lvl: severity,
|
121
70
|
msg: message
|
122
71
|
}
|
123
|
-
c =
|
72
|
+
c = Sidekiq::Context.current
|
124
73
|
hash["ctx"] = c unless c.empty?
|
125
74
|
|
126
75
|
Sidekiq.dump_json(hash) << "\n"
|