sidekiq 7.3.9 → 8.0.0.beta2
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 +28 -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 -5
- data/lib/sidekiq/api.rb +120 -36
- data/lib/sidekiq/capsule.rb +6 -6
- data/lib/sidekiq/cli.rb +15 -19
- data/lib/sidekiq/client.rb +13 -16
- data/lib/sidekiq/component.rb +40 -2
- data/lib/sidekiq/config.rb +18 -15
- data/lib/sidekiq/embedded.rb +1 -0
- data/lib/sidekiq/iterable_job.rb +1 -0
- data/lib/sidekiq/job/iterable.rb +13 -4
- data/lib/sidekiq/job_retry.rb +17 -5
- 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 +71 -45
- data/lib/sidekiq/metrics/shared.rb +4 -1
- data/lib/sidekiq/metrics/tracking.rb +9 -7
- data/lib/sidekiq/middleware/current_attributes.rb +5 -17
- data/lib/sidekiq/paginator.rb +8 -1
- data/lib/sidekiq/processor.rb +21 -14
- data/lib/sidekiq/profiler.rb +59 -0
- 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 +104 -84
- data/lib/sidekiq/web/application.rb +347 -332
- data/lib/sidekiq/web/config.rb +116 -0
- data/lib/sidekiq/web/helpers.rb +41 -16
- data/lib/sidekiq/web/router.rb +60 -76
- data/lib/sidekiq/web.rb +51 -156
- data/lib/sidekiq.rb +1 -1
- data/sidekiq.gemspec +5 -4
- 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/metrics.js +16 -34
- 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 +6 -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 -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 +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 -66
- data/web/views/dead.erb +31 -27
- data/web/views/filtering.erb +3 -3
- data/web/views/layout.erb +6 -22
- data/web/views/metrics.erb +75 -81
- 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 +58 -54
- data/web/views/scheduled_job_info.erb +1 -1
- metadata +32 -18
- 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
@@ -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
@@ -17,7 +17,14 @@ module Sidekiq
|
|
17
17
|
ending = starting + page_size - 1
|
18
18
|
|
19
19
|
Sidekiq.redis do |conn|
|
20
|
-
|
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
|
21
28
|
rev = opts && opts[:reverse]
|
22
29
|
|
23
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/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
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
@@ -1,115 +1,135 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
class WebAction
|
5
|
-
RACK_SESSION = "rack.session"
|
3
|
+
require "erb"
|
6
4
|
|
7
|
-
|
5
|
+
module Sidekiq
|
6
|
+
class Web
|
7
|
+
##
|
8
|
+
# These instance methods are available to all executing ERB
|
9
|
+
# templates.
|
10
|
+
class Action
|
11
|
+
attr_accessor :env, :block
|
12
|
+
|
13
|
+
def initialize(env, block)
|
14
|
+
@_erb = false
|
15
|
+
@env = env
|
16
|
+
@block = block
|
17
|
+
end
|
8
18
|
|
9
|
-
|
10
|
-
|
11
|
-
|
19
|
+
def config
|
20
|
+
env[:web_config]
|
21
|
+
end
|
12
22
|
|
13
|
-
|
14
|
-
|
15
|
-
|
23
|
+
def request
|
24
|
+
@request ||= ::Rack::Request.new(env)
|
25
|
+
end
|
16
26
|
|
17
|
-
|
18
|
-
|
19
|
-
|
27
|
+
def halt(res)
|
28
|
+
throw :halt, [res, {"content-type" => "text/plain"}, [res.to_s]]
|
29
|
+
end
|
20
30
|
|
21
|
-
|
22
|
-
|
23
|
-
|
31
|
+
# external redirect
|
32
|
+
def redirect_to(url)
|
33
|
+
throw :halt, [302, {"Location" => url}, []]
|
34
|
+
end
|
24
35
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
36
|
+
def header(key, value)
|
37
|
+
env["response_headers"][key] = value
|
38
|
+
end
|
29
39
|
|
30
|
-
|
31
|
-
|
32
|
-
|
40
|
+
# internal redirect
|
41
|
+
def redirect(location)
|
42
|
+
throw :halt, [302, {"Location" => "#{request.base_url}#{location}"}, []]
|
43
|
+
end
|
33
44
|
|
34
|
-
|
35
|
-
|
45
|
+
def reload_page
|
46
|
+
current_location = request.referer.gsub(request.base_url, "")
|
47
|
+
redirect current_location
|
48
|
+
end
|
36
49
|
|
37
|
-
|
38
|
-
|
50
|
+
# stuff after ? or form input
|
51
|
+
# uses String keys, no Symbols!
|
52
|
+
def url_params(key)
|
53
|
+
warn { "URL parameter `#{key}` should be accessed via String, not Symbol (at #{caller(3..3).first})" } if key.is_a?(Symbol)
|
54
|
+
request.params[key.to_s]
|
55
|
+
end
|
39
56
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
57
|
+
# variables embedded in path, `/metrics/:name`
|
58
|
+
# uses Symbol keys, no Strings!
|
59
|
+
def route_params(key)
|
60
|
+
warn { "Route parameter `#{key}` should be accessed via Symbol, not String (at #{caller(3..3).first})" } if key.is_a?(String)
|
61
|
+
env["rack.route_params"][key.to_sym]
|
62
|
+
end
|
44
63
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
if key
|
49
|
-
env[WebRouter::ROUTE_PARAMS][key]
|
50
|
-
else
|
51
|
-
env[WebRouter::ROUTE_PARAMS]
|
64
|
+
def params
|
65
|
+
warn { "Direct access to Rack parameters is discouraged, use `url_params` or `route_params` (at #{caller(3..3).first})" }
|
66
|
+
request.params
|
52
67
|
end
|
53
|
-
end
|
54
68
|
|
55
|
-
|
56
|
-
|
57
|
-
|
69
|
+
def session
|
70
|
+
env["rack.session"]
|
71
|
+
end
|
58
72
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
+
def erb(content, options = {})
|
74
|
+
if content.is_a? Symbol
|
75
|
+
unless respond_to?(:"_erb_#{content}")
|
76
|
+
views = options[:views] || Web.views
|
77
|
+
filename = "#{views}/#{content}.erb"
|
78
|
+
src = ERB.new(File.read(filename)).src
|
79
|
+
|
80
|
+
# Need to use lineno less by 1 because erb generates a
|
81
|
+
# comment before the source code.
|
82
|
+
Action.class_eval <<-RUBY, filename, -1 # standard:disable Style/EvalWithLocation
|
83
|
+
def _erb_#{content}
|
84
|
+
#{src}
|
85
|
+
end
|
86
|
+
RUBY
|
87
|
+
end
|
73
88
|
end
|
74
|
-
end
|
75
89
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
90
|
+
if @_erb
|
91
|
+
_erb(content, options[:locals])
|
92
|
+
else
|
93
|
+
@_erb = true
|
94
|
+
content = _erb(content, options[:locals])
|
81
95
|
|
82
|
-
|
96
|
+
_render { content }
|
97
|
+
end
|
83
98
|
end
|
84
|
-
end
|
85
99
|
|
86
|
-
|
87
|
-
|
100
|
+
def render(engine, content, options = {})
|
101
|
+
raise "Only erb templates are supported" if engine != :erb
|
88
102
|
|
89
|
-
|
90
|
-
|
103
|
+
erb(content, options)
|
104
|
+
end
|
91
105
|
|
92
|
-
|
93
|
-
|
94
|
-
|
106
|
+
def json(payload)
|
107
|
+
[200,
|
108
|
+
{"content-type" => "application/json", "cache-control" => "private, no-store"},
|
109
|
+
[Sidekiq.dump_json(payload)]]
|
110
|
+
end
|
95
111
|
|
96
|
-
|
97
|
-
@_erb = false
|
98
|
-
@env = env
|
99
|
-
@block = block
|
100
|
-
@files ||= {}
|
101
|
-
end
|
112
|
+
private
|
102
113
|
|
103
|
-
|
114
|
+
def warn
|
115
|
+
Sidekiq.logger.warn yield
|
116
|
+
end
|
104
117
|
|
105
|
-
|
106
|
-
|
118
|
+
def _erb(file, locals)
|
119
|
+
locals&.each { |k, v| define_singleton_method(k) { v } unless singleton_methods.include? k }
|
107
120
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
121
|
+
if file.is_a?(String)
|
122
|
+
ERB.new(file).result(binding)
|
123
|
+
else
|
124
|
+
send(:"_erb_#{file}")
|
125
|
+
end
|
112
126
|
end
|
127
|
+
|
128
|
+
class_eval <<-RUBY, ::Sidekiq::Web::LAYOUT, -1 # standard:disable Style/EvalWithLocation
|
129
|
+
def _render
|
130
|
+
#{ERB.new(File.read(::Sidekiq::Web::LAYOUT)).src}
|
131
|
+
end
|
132
|
+
RUBY
|
113
133
|
end
|
114
134
|
end
|
115
135
|
end
|