sidekiq 7.3.7 → 8.0.0.beta1
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 +30 -0
- data/README.md +16 -13
- data/bin/sidekiqload +10 -10
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +5 -18
- data/lib/sidekiq/api.rb +108 -36
- data/lib/sidekiq/capsule.rb +1 -1
- data/lib/sidekiq/cli.rb +15 -19
- data/lib/sidekiq/client.rb +13 -16
- data/lib/sidekiq/component.rb +30 -2
- data/lib/sidekiq/config.rb +18 -15
- data/lib/sidekiq/embedded.rb +1 -0
- data/lib/sidekiq/job/iterable.rb +3 -5
- data/lib/sidekiq/job_retry.rb +2 -2
- data/lib/sidekiq/job_util.rb +5 -1
- data/lib/sidekiq/launcher.rb +1 -1
- data/lib/sidekiq/logger.rb +6 -10
- data/lib/sidekiq/manager.rb +0 -1
- data/lib/sidekiq/metrics/query.rb +1 -3
- data/lib/sidekiq/middleware/current_attributes.rb +5 -17
- data/lib/sidekiq/paginator.rb +14 -1
- data/lib/sidekiq/processor.rb +21 -14
- data/lib/sidekiq/profiler.rb +59 -0
- data/lib/sidekiq/rails.rb +12 -2
- data/lib/sidekiq/redis_client_adapter.rb +0 -1
- data/lib/sidekiq/testing.rb +2 -2
- data/lib/sidekiq/version.rb +2 -2
- data/lib/sidekiq/web/action.rb +101 -85
- data/lib/sidekiq/web/application.rb +339 -333
- data/lib/sidekiq/web/config.rb +116 -0
- data/lib/sidekiq/web/helpers.rb +45 -20
- data/lib/sidekiq/web/router.rb +60 -76
- data/lib/sidekiq/web.rb +51 -156
- data/sidekiq.gemspec +6 -4
- data/web/assets/javascripts/application.js +6 -13
- data/web/assets/javascripts/base-charts.js +30 -18
- data/web/assets/javascripts/dashboard-charts.js +2 -0
- data/web/assets/javascripts/dashboard.js +6 -0
- data/web/assets/javascripts/metrics.js +1 -1
- data/web/assets/stylesheets/style.css +750 -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 -0
- data/web/locales/es.yml +24 -2
- data/web/locales/fa.yml +1 -0
- data/web/locales/fr.yml +1 -0
- data/web/locales/gd.yml +1 -0
- data/web/locales/he.yml +1 -0
- data/web/locales/hi.yml +1 -0
- data/web/locales/it.yml +1 -0
- data/web/locales/ja.yml +1 -0
- 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} +2 -1
- 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 +1 -0
- data/web/locales/uk.yml +1 -0
- data/web/locales/ur.yml +1 -0
- data/web/locales/vi.yml +1 -0
- data/web/locales/{zh-cn.yml → zh-CN.yml} +85 -73
- data/web/locales/{zh-tw.yml → zh-TW.yml} +2 -1
- data/web/views/_footer.erb +31 -34
- data/web/views/_job_info.erb +91 -89
- data/web/views/_metrics_period_select.erb +1 -1
- 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 +62 -64
- data/web/views/dead.erb +31 -27
- data/web/views/filtering.erb +3 -3
- data/web/views/layout.erb +5 -21
- data/web/views/metrics.erb +83 -80
- data/web/views/metrics_for_job.erb +39 -42
- 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 +58 -54
- data/web/views/scheduled_job_info.erb +1 -1
- metadata +46 -22
- 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/client.rb
CHANGED
@@ -67,9 +67,7 @@ module Sidekiq
|
|
67
67
|
c.pipelined do |p|
|
68
68
|
p.hsetnx(key, "cancelled", Time.now.to_i)
|
69
69
|
p.hget(key, "cancelled")
|
70
|
-
p.expire(key, Sidekiq::Job::Iterable::STATE_TTL)
|
71
|
-
# TODO When Redis 7.2 is required
|
72
|
-
# p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
|
70
|
+
p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
|
73
71
|
end
|
74
72
|
end
|
75
73
|
result.to_i
|
@@ -266,22 +264,21 @@ module Sidekiq
|
|
266
264
|
if payloads.first.key?("at")
|
267
265
|
conn.zadd("schedule", payloads.flat_map { |hash|
|
268
266
|
at = hash["at"].to_s
|
269
|
-
# ActiveJob sets
|
270
|
-
hash.
|
271
|
-
# TODO: Use hash.except("at") when support for Ruby 2.7 is dropped
|
272
|
-
hash = hash.dup
|
273
|
-
hash.delete("at")
|
267
|
+
# ActiveJob sets enqueued_at but the job has not been enqueued yet
|
268
|
+
hash = hash.except("enqueued_at", "at")
|
274
269
|
[at, Sidekiq.dump_json(hash)]
|
275
270
|
})
|
276
271
|
else
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
272
|
+
now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) # milliseconds since the epoch
|
273
|
+
grouped_queues = payloads.group_by { |job| job["queue"] }
|
274
|
+
conn.sadd("queues", grouped_queues.keys)
|
275
|
+
grouped_queues.each do |queue, grouped_payloads|
|
276
|
+
to_push = grouped_payloads.map { |entry|
|
277
|
+
entry["enqueued_at"] = now
|
278
|
+
Sidekiq.dump_json(entry)
|
279
|
+
}
|
280
|
+
conn.lpush("queue:#{queue}", to_push)
|
281
|
+
end
|
285
282
|
end
|
286
283
|
end
|
287
284
|
end
|
data/lib/sidekiq/component.rb
CHANGED
@@ -1,6 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Sidekiq
|
4
|
+
# Ruby's default thread priority is 0, which uses 100ms time slices.
|
5
|
+
# This can lead to some surprising thread starvation; if using a lot of
|
6
|
+
# CPU-heavy concurrency, it may take several seconds before a Thread gets
|
7
|
+
# on the CPU.
|
8
|
+
#
|
9
|
+
# Negative priorities lower the timeslice by half, so -1 = 50ms, -2 = 25ms, etc.
|
10
|
+
# With more frequent timeslices, we reduce the risk of unintentional timeouts
|
11
|
+
# and starvation.
|
12
|
+
#
|
13
|
+
# Customize like so:
|
14
|
+
#
|
15
|
+
# Sidekiq.configure_server do |cfg|
|
16
|
+
# cfg.thread_priority = 0
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
DEFAULT_THREAD_PRIORITY = -1
|
20
|
+
|
4
21
|
##
|
5
22
|
# Sidekiq::Component assumes a config instance is available at @config
|
6
23
|
module Component # :nodoc:
|
@@ -13,11 +30,11 @@ module Sidekiq
|
|
13
30
|
raise ex
|
14
31
|
end
|
15
32
|
|
16
|
-
def safe_thread(name, &block)
|
33
|
+
def safe_thread(name, priority: nil, &block)
|
17
34
|
Thread.new do
|
18
35
|
Thread.current.name = "sidekiq.#{name}"
|
19
36
|
watchdog(name, &block)
|
20
|
-
end
|
37
|
+
end.tap { |t| t.priority = (priority || config.thread_priority || DEFAULT_THREAD_PRIORITY) }
|
21
38
|
end
|
22
39
|
|
23
40
|
def logger
|
@@ -86,5 +103,16 @@ module Sidekiq
|
|
86
103
|
end.join(", ")
|
87
104
|
}>"
|
88
105
|
end
|
106
|
+
|
107
|
+
def default_tag(dir = Dir.pwd)
|
108
|
+
name = File.basename(dir)
|
109
|
+
prevdir = File.dirname(dir) # Capistrano release directory?
|
110
|
+
if name.to_i != 0 && prevdir
|
111
|
+
if File.basename(prevdir) == "releases"
|
112
|
+
return File.basename(File.dirname(prevdir))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
name
|
116
|
+
end
|
89
117
|
end
|
90
118
|
end
|
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
|
@@ -41,12 +39,22 @@ module Sidekiq
|
|
41
39
|
}
|
42
40
|
|
43
41
|
ERROR_HANDLER = ->(ex, ctx, cfg = Sidekiq.default_configuration) {
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
42
|
+
Sidekiq::Context.with(ctx) do
|
43
|
+
dev = cfg[:environment] == "development"
|
44
|
+
fancy = dev && $stdout.tty? # 🎩
|
45
|
+
# Weird logic here but we want to show the backtrace in local
|
46
|
+
# development or if verbose logging is enabled.
|
47
|
+
#
|
48
|
+
# `full_message` contains the error class, message and backtrace
|
49
|
+
# `detailed_message` contains the error class and message
|
50
|
+
#
|
51
|
+
# Absolutely terrible API names. Not useful at all to have two
|
52
|
+
# methods with similar but obscure names.
|
53
|
+
if dev || cfg.logger.debug?
|
54
|
+
cfg.logger.info { ex.full_message(highlight: fancy) }
|
55
|
+
else
|
56
|
+
cfg.logger.info { ex.detailed_message(highlight: fancy) }
|
57
|
+
end
|
50
58
|
end
|
51
59
|
}
|
52
60
|
|
@@ -60,6 +68,7 @@ module Sidekiq
|
|
60
68
|
|
61
69
|
def_delegators :@options, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig
|
62
70
|
attr_reader :capsules
|
71
|
+
attr_accessor :thread_priority
|
63
72
|
|
64
73
|
def inspect
|
65
74
|
"#<#{self.class.name} @options=#{
|
@@ -293,13 +302,7 @@ module Sidekiq
|
|
293
302
|
p ["!!!!!", ex]
|
294
303
|
end
|
295
304
|
@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
|
305
|
+
handler.call(ex, ctx, self)
|
303
306
|
rescue Exception => e
|
304
307
|
l = logger
|
305
308
|
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)
|
data/lib/sidekiq/job/iterable.rb
CHANGED
@@ -54,9 +54,7 @@ module Sidekiq
|
|
54
54
|
c.pipelined do |p|
|
55
55
|
p.hsetnx(key, "cancelled", Time.now.to_i)
|
56
56
|
p.hget(key, "cancelled")
|
57
|
-
|
58
|
-
# p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
|
59
|
-
p.expire(key, Sidekiq::Job::Iterable::STATE_TTL)
|
57
|
+
p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
|
60
58
|
end
|
61
59
|
end
|
62
60
|
@_cancelled = result.to_i
|
@@ -200,7 +198,7 @@ module Sidekiq
|
|
200
198
|
if ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - state_flushed_at >= STATE_FLUSH_INTERVAL || is_interrupted
|
201
199
|
_, _, cancelled = flush_state
|
202
200
|
state_flushed_at = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
203
|
-
if cancelled
|
201
|
+
if cancelled
|
204
202
|
@_cancelled = true
|
205
203
|
logger.info { "Job cancelled" }
|
206
204
|
return true
|
@@ -265,7 +263,7 @@ module Sidekiq
|
|
265
263
|
Sidekiq.redis do |conn|
|
266
264
|
conn.multi do |pipe|
|
267
265
|
pipe.hset(key, state)
|
268
|
-
pipe.expire(key, STATE_TTL)
|
266
|
+
pipe.expire(key, STATE_TTL, "nx")
|
269
267
|
pipe.hget(key, "cancelled")
|
270
268
|
end
|
271
269
|
end
|
data/lib/sidekiq/job_retry.rb
CHANGED
@@ -149,7 +149,7 @@ module Sidekiq
|
|
149
149
|
|
150
150
|
m = exception_message(exception)
|
151
151
|
if m.respond_to?(:scrub!)
|
152
|
-
m.force_encoding(
|
152
|
+
m.force_encoding(Encoding::UTF_8)
|
153
153
|
m.scrub!
|
154
154
|
end
|
155
155
|
|
@@ -189,7 +189,7 @@ module Sidekiq
|
|
189
189
|
|
190
190
|
# Logging here can break retries if the logging device raises ENOSPC #3979
|
191
191
|
# logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
|
192
|
-
jitter = rand(10
|
192
|
+
jitter = rand(10 * (count + 1))
|
193
193
|
retry_at = Time.now.to_f + delay + jitter
|
194
194
|
payload = Sidekiq.dump_json(msg)
|
195
195
|
redis do |conn|
|
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
data/lib/sidekiq/logger.rb
CHANGED
@@ -81,13 +81,9 @@ module Sidekiq
|
|
81
81
|
Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
|
82
82
|
end
|
83
83
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
def format_context
|
89
|
-
if ctx.any?
|
90
|
-
" " + ctx.compact.map { |k, v|
|
84
|
+
def format_context(ctxt = Sidekiq::Context.current)
|
85
|
+
if ctxt.size > 0
|
86
|
+
ctxt.map { |k, v|
|
91
87
|
case v
|
92
88
|
when Array
|
93
89
|
"#{k}=#{v.join(",")}"
|
@@ -101,13 +97,13 @@ module Sidekiq
|
|
101
97
|
|
102
98
|
class Pretty < Base
|
103
99
|
def call(severity, time, program_name, message)
|
104
|
-
"#{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
|
100
|
+
"#{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid} #{format_context} #{severity}: #{message}\n"
|
105
101
|
end
|
106
102
|
end
|
107
103
|
|
108
104
|
class WithoutTimestamp < Pretty
|
109
105
|
def call(severity, time, program_name, message)
|
110
|
-
"pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
|
106
|
+
"pid=#{::Process.pid} tid=#{tid} #{format_context} #{severity}: #{message}\n"
|
111
107
|
end
|
112
108
|
end
|
113
109
|
|
@@ -120,7 +116,7 @@ module Sidekiq
|
|
120
116
|
lvl: severity,
|
121
117
|
msg: message
|
122
118
|
}
|
123
|
-
c =
|
119
|
+
c = Sidekiq::Context.current
|
124
120
|
hash["ctx"] = c unless c.empty?
|
125
121
|
|
126
122
|
Sidekiq.dump_json(hash) << "\n"
|
data/lib/sidekiq/manager.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_job/arguments"
|
3
4
|
require "active_support/current_attributes"
|
4
5
|
|
5
6
|
module Sidekiq
|
@@ -20,6 +21,8 @@ module Sidekiq
|
|
20
21
|
# Sidekiq::CurrentAttributes.persist(["Myapp::Current", "Myapp::OtherCurrent"])
|
21
22
|
#
|
22
23
|
module CurrentAttributes
|
24
|
+
Serializer = ::ActiveJob::Arguments
|
25
|
+
|
23
26
|
class Save
|
24
27
|
include Sidekiq::ClientMiddleware
|
25
28
|
|
@@ -33,26 +36,11 @@ module Sidekiq
|
|
33
36
|
attrs = strklass.constantize.attributes
|
34
37
|
# Retries can push the job N times, we don't
|
35
38
|
# 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
|
39
|
+
job[key] = Serializer.serialize(attrs) if attrs.any?
|
45
40
|
end
|
46
41
|
end
|
47
42
|
yield
|
48
43
|
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
|
56
44
|
end
|
57
45
|
|
58
46
|
class Load
|
@@ -68,7 +56,7 @@ module Sidekiq
|
|
68
56
|
@cattrs.each do |(key, strklass)|
|
69
57
|
next unless job.has_key?(key)
|
70
58
|
|
71
|
-
klass_attrs[strklass.constantize] = job[key]
|
59
|
+
klass_attrs[strklass.constantize] = Serializer.deserialize(job[key]).to_h
|
72
60
|
end
|
73
61
|
|
74
62
|
wrap(klass_attrs.to_a, &block)
|
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
|
@@ -11,7 +17,14 @@ module Sidekiq
|
|
11
17
|
ending = starting + page_size - 1
|
12
18
|
|
13
19
|
Sidekiq.redis do |conn|
|
14
|
-
|
20
|
+
# horrible, think you can make this cleaner?
|
21
|
+
type = TYPE_CACHE[key]
|
22
|
+
if type
|
23
|
+
elsif key.start_with?("queue:")
|
24
|
+
type = TYPE_CACHE[key] = "list"
|
25
|
+
else
|
26
|
+
type = TYPE_CACHE[key] = conn.type(key)
|
27
|
+
end
|
15
28
|
rev = opts && opts[:reverse]
|
16
29
|
|
17
30
|
case type
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "sidekiq/fetch"
|
4
4
|
require "sidekiq/job_logger"
|
5
5
|
require "sidekiq/job_retry"
|
6
|
+
require "sidekiq/profiler"
|
6
7
|
|
7
8
|
module Sidekiq
|
8
9
|
##
|
@@ -66,7 +67,7 @@ module Sidekiq
|
|
66
67
|
@thread ||= safe_thread("#{config.name}/processor", &method(:run))
|
67
68
|
end
|
68
69
|
|
69
|
-
private
|
70
|
+
private
|
70
71
|
|
71
72
|
def run
|
72
73
|
# By setting this thread-local, Sidekiq.redis will access +Sidekiq::Capsule#redis_pool+
|
@@ -112,13 +113,17 @@ module Sidekiq
|
|
112
113
|
def handle_fetch_exception(ex)
|
113
114
|
unless @down
|
114
115
|
@down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
115
|
-
logger.error("Error fetching job: #{ex}")
|
116
116
|
handle_exception(ex)
|
117
117
|
end
|
118
118
|
sleep(1)
|
119
119
|
nil
|
120
120
|
end
|
121
121
|
|
122
|
+
def profile(job, &block)
|
123
|
+
return yield unless job["profile"]
|
124
|
+
Sidekiq::Profiler.new(config).call(job, &block)
|
125
|
+
end
|
126
|
+
|
122
127
|
def dispatch(job_hash, queue, jobstr)
|
123
128
|
# since middleware can mutate the job hash
|
124
129
|
# we need to clone it to report the original
|
@@ -132,17 +137,19 @@ module Sidekiq
|
|
132
137
|
@retrier.global(jobstr, queue) do
|
133
138
|
@job_logger.call(job_hash, queue) do
|
134
139
|
stats(jobstr, queue) do
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
140
|
+
profile(job_hash) do
|
141
|
+
# Rails 5 requires a Reloader to wrap code execution. In order to
|
142
|
+
# constantize the worker and instantiate an instance, we have to call
|
143
|
+
# the Reloader. It handles code loading, db connection management, etc.
|
144
|
+
# Effectively this block denotes a "unit of work" to Rails.
|
145
|
+
@reloader.call do
|
146
|
+
klass = Object.const_get(job_hash["class"])
|
147
|
+
instance = klass.new
|
148
|
+
instance.jid = job_hash["jid"]
|
149
|
+
instance._context = self
|
150
|
+
@retrier.local(instance, jobstr, queue) do
|
151
|
+
yield instance
|
152
|
+
end
|
146
153
|
end
|
147
154
|
end
|
148
155
|
end
|
@@ -165,7 +172,6 @@ module Sidekiq
|
|
165
172
|
begin
|
166
173
|
job_hash = Sidekiq.load_json(jobstr)
|
167
174
|
rescue => ex
|
168
|
-
handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
|
169
175
|
now = Time.now.to_f
|
170
176
|
redis do |conn|
|
171
177
|
conn.multi do |xa|
|
@@ -174,6 +180,7 @@ module Sidekiq
|
|
174
180
|
xa.zremrangebyrank("dead", 0, - @capsule.config[:dead_max_jobs])
|
175
181
|
end
|
176
182
|
end
|
183
|
+
handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
|
177
184
|
return uow.acknowledge
|
178
185
|
end
|
179
186
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "sidekiq/component"
|
3
|
+
|
4
|
+
module Sidekiq
|
5
|
+
# Allows the user to profile jobs running in production.
|
6
|
+
# See details in the Profiling wiki page.
|
7
|
+
class Profiler
|
8
|
+
EXPIRY = 86400 # 1 day
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
mode: :wall
|
11
|
+
}
|
12
|
+
|
13
|
+
include Sidekiq::Component
|
14
|
+
def initialize(config)
|
15
|
+
@config = config
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(job, &block)
|
19
|
+
return yield unless job["profile"]
|
20
|
+
|
21
|
+
token = job["profile"]
|
22
|
+
type = job["class"]
|
23
|
+
jid = job["jid"]
|
24
|
+
started_at = Time.now
|
25
|
+
options = DEFAULT_OPTIONS.merge((job["profiler_options"] || {}).transform_keys!(&:to_sym))
|
26
|
+
|
27
|
+
rundata = {
|
28
|
+
started_at: started_at.to_i,
|
29
|
+
token: token,
|
30
|
+
type: type,
|
31
|
+
jid: jid,
|
32
|
+
# .gz extension tells Vernier to compress the data
|
33
|
+
filename: "#{token}-#{type}-#{jid}-#{started_at.strftime("%Y%m%d-%H%M%S")}.json.gz"
|
34
|
+
}
|
35
|
+
|
36
|
+
require "vernier"
|
37
|
+
begin
|
38
|
+
a = Time.now
|
39
|
+
rc = Vernier.profile(**options.merge(out: rundata[:filename]), &block)
|
40
|
+
b = Time.now
|
41
|
+
|
42
|
+
# Failed jobs will raise an exception on previous line and skip this
|
43
|
+
# block. Only successful jobs will persist profile data to Redis.
|
44
|
+
key = "#{token}-#{jid}"
|
45
|
+
data = File.read(rundata[:filename])
|
46
|
+
redis do |conn|
|
47
|
+
conn.multi do |m|
|
48
|
+
m.zadd("profiles", Time.now.to_f + EXPIRY, key)
|
49
|
+
m.hset(key, rundata.merge(elapsed: (b - a), data: data, size: data.bytesize))
|
50
|
+
m.expire(key, EXPIRY)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
rc
|
54
|
+
ensure
|
55
|
+
FileUtils.rm_f(rundata[:filename])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
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)
|
@@ -39,8 +50,7 @@ module Sidekiq
|
|
39
50
|
# end
|
40
51
|
initializer "sidekiq.active_job_integration" do
|
41
52
|
ActiveSupport.on_load(:active_job) do
|
42
|
-
|
43
|
-
|
53
|
+
require_relative "../active_job/queue_adapters/sidekiq_adapter"
|
44
54
|
include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
|
45
55
|
end
|
46
56
|
end
|
data/lib/sidekiq/testing.rb
CHANGED
@@ -87,7 +87,7 @@ module Sidekiq
|
|
87
87
|
if Sidekiq::Testing.fake?
|
88
88
|
payloads.each do |job|
|
89
89
|
job = Sidekiq.load_json(Sidekiq.dump_json(job))
|
90
|
-
job["enqueued_at"] =
|
90
|
+
job["enqueued_at"] = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) unless job["at"]
|
91
91
|
Queues.push(job["queue"], job["class"], job)
|
92
92
|
end
|
93
93
|
true
|
@@ -329,6 +329,6 @@ module Sidekiq
|
|
329
329
|
end
|
330
330
|
end
|
331
331
|
|
332
|
-
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
|
332
|
+
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING # rubocop:disable Style/GlobalVars
|
333
333
|
warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
|
334
334
|
end
|