sidekiq 7.3.7 → 8.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|