sidekiq 6.2.2 → 8.1.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 +726 -11
- data/LICENSE.txt +9 -0
- data/README.md +70 -39
- data/bin/kiq +17 -0
- data/bin/lint-herb +13 -0
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiq +4 -9
- data/bin/sidekiqload +214 -115
- data/bin/sidekiqmon +4 -1
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
- data/lib/generators/sidekiq/job_generator.rb +71 -0
- data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +3 -3
- data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
- data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
- data/lib/sidekiq/api.rb +729 -264
- data/lib/sidekiq/capsule.rb +135 -0
- data/lib/sidekiq/cli.rb +124 -100
- data/lib/sidekiq/client.rb +153 -106
- data/lib/sidekiq/component.rb +132 -0
- data/lib/sidekiq/config.rb +320 -0
- data/lib/sidekiq/deploy.rb +64 -0
- data/lib/sidekiq/embedded.rb +64 -0
- data/lib/sidekiq/fetch.rb +27 -26
- data/lib/sidekiq/iterable_job.rb +56 -0
- data/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +322 -0
- data/lib/sidekiq/job.rb +397 -5
- data/lib/sidekiq/job_logger.rb +23 -32
- data/lib/sidekiq/job_retry.rb +141 -68
- data/lib/sidekiq/job_util.rb +113 -0
- data/lib/sidekiq/launcher.rb +122 -98
- data/lib/sidekiq/loader.rb +57 -0
- data/lib/sidekiq/logger.rb +27 -106
- data/lib/sidekiq/manager.rb +41 -43
- data/lib/sidekiq/metrics/query.rb +184 -0
- data/lib/sidekiq/metrics/shared.rb +109 -0
- data/lib/sidekiq/metrics/tracking.rb +153 -0
- data/lib/sidekiq/middleware/chain.rb +96 -51
- data/lib/sidekiq/middleware/current_attributes.rb +120 -0
- data/lib/sidekiq/middleware/i18n.rb +8 -4
- data/lib/sidekiq/middleware/modules.rb +23 -0
- data/lib/sidekiq/monitor.rb +16 -6
- data/lib/sidekiq/paginator.rb +37 -10
- data/lib/sidekiq/processor.rb +105 -87
- data/lib/sidekiq/profiler.rb +73 -0
- data/lib/sidekiq/rails.rb +49 -36
- data/lib/sidekiq/redis_client_adapter.rb +117 -0
- data/lib/sidekiq/redis_connection.rb +55 -86
- data/lib/sidekiq/ring_buffer.rb +32 -0
- data/lib/sidekiq/scheduled.rb +106 -50
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/test_api.rb +331 -0
- data/lib/sidekiq/testing/inline.rb +2 -30
- data/lib/sidekiq/testing.rb +2 -342
- data/lib/sidekiq/transaction_aware_client.rb +59 -0
- data/lib/sidekiq/tui/controls.rb +53 -0
- data/lib/sidekiq/tui/filtering.rb +53 -0
- data/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
- data/lib/sidekiq/tui/tabs/busy.rb +118 -0
- data/lib/sidekiq/tui/tabs/dead.rb +19 -0
- data/lib/sidekiq/tui/tabs/home.rb +144 -0
- data/lib/sidekiq/tui/tabs/metrics.rb +131 -0
- data/lib/sidekiq/tui/tabs/queues.rb +95 -0
- data/lib/sidekiq/tui/tabs/retries.rb +19 -0
- data/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
- data/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
- data/lib/sidekiq/tui/tabs.rb +15 -0
- data/lib/sidekiq/tui.rb +382 -0
- data/lib/sidekiq/version.rb +6 -1
- data/lib/sidekiq/web/action.rb +149 -64
- data/lib/sidekiq/web/application.rb +376 -268
- data/lib/sidekiq/web/config.rb +117 -0
- data/lib/sidekiq/web/helpers.rb +213 -87
- data/lib/sidekiq/web/router.rb +61 -74
- data/lib/sidekiq/web.rb +71 -100
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +95 -196
- data/sidekiq.gemspec +14 -11
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status.png +0 -0
- data/web/assets/javascripts/application.js +171 -57
- data/web/assets/javascripts/base-charts.js +120 -0
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard-charts.js +194 -0
- data/web/assets/javascripts/dashboard.js +41 -274
- data/web/assets/javascripts/metrics.js +280 -0
- data/web/assets/stylesheets/style.css +776 -0
- data/web/locales/ar.yml +72 -70
- data/web/locales/cs.yml +64 -62
- data/web/locales/da.yml +62 -53
- data/web/locales/de.yml +67 -65
- data/web/locales/el.yml +45 -24
- data/web/locales/en.yml +93 -69
- data/web/locales/es.yml +91 -68
- data/web/locales/fa.yml +67 -65
- data/web/locales/fr.yml +82 -67
- data/web/locales/gd.yml +110 -0
- data/web/locales/he.yml +67 -64
- data/web/locales/hi.yml +61 -59
- data/web/locales/it.yml +94 -54
- data/web/locales/ja.yml +74 -68
- data/web/locales/ko.yml +54 -52
- data/web/locales/lt.yml +68 -66
- data/web/locales/nb.yml +63 -61
- data/web/locales/nl.yml +54 -52
- data/web/locales/pl.yml +47 -45
- data/web/locales/{pt-br.yml → pt-BR.yml} +85 -56
- data/web/locales/pt.yml +53 -51
- data/web/locales/ru.yml +69 -66
- data/web/locales/sv.yml +55 -53
- data/web/locales/ta.yml +62 -60
- data/web/locales/tr.yml +102 -0
- data/web/locales/uk.yml +87 -61
- data/web/locales/ur.yml +66 -64
- data/web/locales/vi.yml +69 -67
- data/web/locales/zh-CN.yml +107 -0
- data/web/locales/{zh-tw.yml → zh-TW.yml} +44 -9
- data/web/views/_footer.html.erb +32 -0
- data/web/views/_job_info.html.erb +115 -0
- data/web/views/_metrics_period_select.html.erb +15 -0
- data/web/views/_nav.html.erb +45 -0
- data/web/views/_paging.html.erb +26 -0
- data/web/views/_poll_link.html.erb +4 -0
- data/web/views/_summary.html.erb +40 -0
- data/web/views/busy.html.erb +151 -0
- data/web/views/dashboard.html.erb +104 -0
- data/web/views/dead.html.erb +38 -0
- data/web/views/filtering.html.erb +6 -0
- data/web/views/layout.html.erb +26 -0
- data/web/views/metrics.html.erb +85 -0
- data/web/views/metrics_for_job.html.erb +58 -0
- data/web/views/morgue.html.erb +69 -0
- data/web/views/profiles.html.erb +43 -0
- data/web/views/queue.html.erb +57 -0
- data/web/views/queues.html.erb +46 -0
- data/web/views/retries.html.erb +77 -0
- data/web/views/retry.html.erb +39 -0
- data/web/views/scheduled.html.erb +64 -0
- data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
- metadata +130 -61
- data/LICENSE +0 -9
- data/lib/generators/sidekiq/worker_generator.rb +0 -57
- data/lib/sidekiq/delay.rb +0 -41
- data/lib/sidekiq/exception_handler.rb +0 -27
- data/lib/sidekiq/extensions/action_mailer.rb +0 -48
- data/lib/sidekiq/extensions/active_record.rb +0 -43
- data/lib/sidekiq/extensions/class_methods.rb +0 -43
- data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
- data/lib/sidekiq/util.rb +0 -95
- data/lib/sidekiq/web/csrf_protection.rb +0 -180
- data/lib/sidekiq/worker.rb +0 -244
- data/web/assets/stylesheets/application-dark.css +0 -147
- data/web/assets/stylesheets/application-rtl.css +0 -246
- data/web/assets/stylesheets/application.css +0 -1053
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/locales/zh-cn.yml +0 -68
- data/web/views/_footer.erb +0 -20
- data/web/views/_job_info.erb +0 -89
- data/web/views/_nav.erb +0 -52
- data/web/views/_paging.erb +0 -23
- data/web/views/_poll_link.erb +0 -7
- data/web/views/_status.erb +0 -4
- data/web/views/_summary.erb +0 -40
- data/web/views/busy.erb +0 -132
- data/web/views/dashboard.erb +0 -83
- data/web/views/dead.erb +0 -34
- data/web/views/layout.erb +0 -42
- data/web/views/morgue.erb +0 -78
- data/web/views/queue.erb +0 -55
- data/web/views/queues.erb +0 -38
- data/web/views/retries.erb +0 -83
- data/web/views/retry.erb +0 -34
- data/web/views/scheduled.erb +0 -57
data/lib/sidekiq/processor.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "sidekiq/util"
|
|
4
3
|
require "sidekiq/fetch"
|
|
5
4
|
require "sidekiq/job_logger"
|
|
6
5
|
require "sidekiq/job_retry"
|
|
6
|
+
require "sidekiq/profiler"
|
|
7
7
|
|
|
8
8
|
module Sidekiq
|
|
9
9
|
##
|
|
@@ -11,33 +11,34 @@ module Sidekiq
|
|
|
11
11
|
#
|
|
12
12
|
# 1. fetches a job from Redis
|
|
13
13
|
# 2. executes the job
|
|
14
|
-
# a. instantiate the
|
|
14
|
+
# a. instantiate the job class
|
|
15
15
|
# b. run the middleware chain
|
|
16
16
|
# c. call #perform
|
|
17
17
|
#
|
|
18
|
-
# A Processor can exit due to shutdown
|
|
19
|
-
#
|
|
18
|
+
# A Processor can exit due to shutdown or due to
|
|
19
|
+
# an error during job execution.
|
|
20
20
|
#
|
|
21
21
|
# If an error occurs in the job execution, the
|
|
22
22
|
# Processor calls the Manager to create a new one
|
|
23
23
|
# to replace itself and exits.
|
|
24
24
|
#
|
|
25
25
|
class Processor
|
|
26
|
-
include
|
|
26
|
+
include Sidekiq::Component
|
|
27
27
|
|
|
28
28
|
attr_reader :thread
|
|
29
29
|
attr_reader :job
|
|
30
|
+
attr_reader :capsule
|
|
30
31
|
|
|
31
|
-
def initialize(
|
|
32
|
-
@
|
|
32
|
+
def initialize(capsule, &block)
|
|
33
|
+
@config = @capsule = capsule
|
|
34
|
+
@callback = block
|
|
33
35
|
@down = false
|
|
34
36
|
@done = false
|
|
35
37
|
@job = nil
|
|
36
38
|
@thread = nil
|
|
37
|
-
@
|
|
38
|
-
@
|
|
39
|
-
@
|
|
40
|
-
@retrier = Sidekiq::JobRetry.new
|
|
39
|
+
@reloader = Sidekiq.default_configuration[:reloader]
|
|
40
|
+
@job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(capsule.config)
|
|
41
|
+
@retrier = Sidekiq::JobRetry.new(capsule)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
def terminate(wait = false)
|
|
@@ -58,34 +59,42 @@ module Sidekiq
|
|
|
58
59
|
@thread.value if wait
|
|
59
60
|
end
|
|
60
61
|
|
|
62
|
+
def stopping?
|
|
63
|
+
@done
|
|
64
|
+
end
|
|
65
|
+
|
|
61
66
|
def start
|
|
62
|
-
@thread ||= safe_thread("processor", &method(:run))
|
|
67
|
+
@thread ||= safe_thread("#{config.name}/processor", &method(:run))
|
|
63
68
|
end
|
|
64
69
|
|
|
65
|
-
private
|
|
70
|
+
private
|
|
66
71
|
|
|
67
72
|
def run
|
|
73
|
+
# By setting this thread-local, Sidekiq.redis will access +Sidekiq::Capsule#redis_pool+
|
|
74
|
+
# instead of the global pool in +Sidekiq::Config#redis_pool+.
|
|
75
|
+
Thread.current[:sidekiq_capsule] = @capsule
|
|
76
|
+
|
|
68
77
|
process_one until @done
|
|
69
|
-
@
|
|
78
|
+
@callback.call(self)
|
|
70
79
|
rescue Sidekiq::Shutdown
|
|
71
|
-
@
|
|
80
|
+
@callback.call(self)
|
|
72
81
|
rescue Exception => ex
|
|
73
|
-
@
|
|
82
|
+
@callback.call(self, ex)
|
|
74
83
|
end
|
|
75
84
|
|
|
76
|
-
def process_one
|
|
85
|
+
def process_one(&block)
|
|
77
86
|
@job = fetch
|
|
78
87
|
process(@job) if @job
|
|
79
88
|
@job = nil
|
|
80
89
|
end
|
|
81
90
|
|
|
82
91
|
def get_one
|
|
83
|
-
|
|
92
|
+
uow = capsule.fetcher.retrieve_work
|
|
84
93
|
if @down
|
|
85
94
|
logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
|
|
86
95
|
@down = nil
|
|
87
96
|
end
|
|
88
|
-
|
|
97
|
+
uow
|
|
89
98
|
rescue Sidekiq::Shutdown
|
|
90
99
|
rescue => ex
|
|
91
100
|
handle_fetch_exception(ex)
|
|
@@ -104,13 +113,17 @@ module Sidekiq
|
|
|
104
113
|
def handle_fetch_exception(ex)
|
|
105
114
|
unless @down
|
|
106
115
|
@down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
107
|
-
logger.error("Error fetching job: #{ex}")
|
|
108
116
|
handle_exception(ex)
|
|
109
117
|
end
|
|
110
118
|
sleep(1)
|
|
111
119
|
nil
|
|
112
120
|
end
|
|
113
121
|
|
|
122
|
+
def profile(job, &block)
|
|
123
|
+
return yield unless job["profile"]
|
|
124
|
+
Sidekiq::Profiler.new(config).call(job, &block)
|
|
125
|
+
end
|
|
126
|
+
|
|
114
127
|
def dispatch(job_hash, queue, jobstr)
|
|
115
128
|
# since middleware can mutate the job hash
|
|
116
129
|
# we need to clone it to report the original
|
|
@@ -124,16 +137,19 @@ module Sidekiq
|
|
|
124
137
|
@retrier.global(jobstr, queue) do
|
|
125
138
|
@job_logger.call(job_hash, queue) do
|
|
126
139
|
stats(jobstr, queue) do
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
137
153
|
end
|
|
138
154
|
end
|
|
139
155
|
end
|
|
@@ -142,58 +158,73 @@ module Sidekiq
|
|
|
142
158
|
end
|
|
143
159
|
end
|
|
144
160
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
161
|
+
IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :never}
|
|
162
|
+
private_constant :IGNORE_SHUTDOWN_INTERRUPTS
|
|
163
|
+
ALLOW_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :immediate}
|
|
164
|
+
private_constant :ALLOW_SHUTDOWN_INTERRUPTS
|
|
165
|
+
|
|
166
|
+
def process(uow)
|
|
167
|
+
jobstr = uow.job
|
|
168
|
+
queue = uow.queue_name
|
|
148
169
|
|
|
149
170
|
# Treat malformed JSON as a special case: job goes straight to the morgue.
|
|
150
171
|
job_hash = nil
|
|
151
172
|
begin
|
|
152
173
|
job_hash = Sidekiq.load_json(jobstr)
|
|
153
174
|
rescue => ex
|
|
175
|
+
now = Time.now.to_f
|
|
176
|
+
redis do |conn|
|
|
177
|
+
conn.multi do |xa|
|
|
178
|
+
xa.zadd("dead", now.to_s, jobstr)
|
|
179
|
+
xa.zremrangebyscore("dead", "-inf", now - @capsule.config[:dead_timeout_in_seconds])
|
|
180
|
+
xa.zremrangebyrank("dead", 0, - @capsule.config[:dead_max_jobs])
|
|
181
|
+
end
|
|
182
|
+
end
|
|
154
183
|
handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
|
|
155
|
-
|
|
156
|
-
DeadSet.new.kill(jobstr, notify_failure: false)
|
|
157
|
-
return work.acknowledge
|
|
184
|
+
return uow.acknowledge
|
|
158
185
|
end
|
|
159
186
|
|
|
160
187
|
ack = false
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
188
|
+
Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
|
|
189
|
+
Thread.handle_interrupt(ALLOW_SHUTDOWN_INTERRUPTS) do
|
|
190
|
+
dispatch(job_hash, queue, jobstr) do |instance|
|
|
191
|
+
config.server_middleware.invoke(instance, job_hash, queue) do
|
|
192
|
+
execute_job(instance, job_hash["args"])
|
|
193
|
+
end
|
|
165
194
|
end
|
|
195
|
+
ack = true
|
|
196
|
+
rescue Sidekiq::Shutdown
|
|
197
|
+
# Had to force kill this job because it didn't finish
|
|
198
|
+
# within the timeout. Don't acknowledge the work since
|
|
199
|
+
# we didn't properly finish it.
|
|
200
|
+
rescue Sidekiq::JobRetry::Skip => s
|
|
201
|
+
# Skip means we handled this error elsewhere. We don't
|
|
202
|
+
# need to log or report the error.
|
|
203
|
+
ack = true
|
|
204
|
+
raise s
|
|
205
|
+
rescue Sidekiq::JobRetry::Handled => h
|
|
206
|
+
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
|
207
|
+
# signals that we created a retry successfully. We can acknowledge the job.
|
|
208
|
+
ack = true
|
|
209
|
+
e = h.cause || h
|
|
210
|
+
handle_exception(e, {context: "Job raised exception", job: job_hash})
|
|
211
|
+
raise e
|
|
212
|
+
rescue Exception => ex
|
|
213
|
+
# Unexpected error! This is very bad and indicates an exception that got past
|
|
214
|
+
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
|
215
|
+
# so it can be rescued when using Sidekiq Pro.
|
|
216
|
+
handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
|
|
217
|
+
raise ex
|
|
166
218
|
end
|
|
167
|
-
ack = true
|
|
168
|
-
rescue Sidekiq::Shutdown
|
|
169
|
-
# Had to force kill this job because it didn't finish
|
|
170
|
-
# within the timeout. Don't acknowledge the work since
|
|
171
|
-
# we didn't properly finish it.
|
|
172
|
-
rescue Sidekiq::JobRetry::Handled => h
|
|
173
|
-
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
|
174
|
-
# signals that we created a retry successfully. We can acknowlege the job.
|
|
175
|
-
ack = true
|
|
176
|
-
e = h.cause || h
|
|
177
|
-
handle_exception(e, {context: "Job raised exception", job: job_hash, jobstr: jobstr})
|
|
178
|
-
raise e
|
|
179
|
-
rescue Exception => ex
|
|
180
|
-
# Unexpected error! This is very bad and indicates an exception that got past
|
|
181
|
-
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
|
182
|
-
# so it can be rescued when using Sidekiq Pro.
|
|
183
|
-
handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
|
|
184
|
-
raise ex
|
|
185
219
|
ensure
|
|
186
220
|
if ack
|
|
187
|
-
|
|
188
|
-
Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
|
|
189
|
-
work.acknowledge
|
|
190
|
-
end
|
|
221
|
+
uow.acknowledge
|
|
191
222
|
end
|
|
192
223
|
end
|
|
193
224
|
end
|
|
194
225
|
|
|
195
|
-
def execute_job(
|
|
196
|
-
|
|
226
|
+
def execute_job(instance, cloned_args)
|
|
227
|
+
instance.perform(*cloned_args)
|
|
197
228
|
end
|
|
198
229
|
|
|
199
230
|
# Ruby doesn't provide atomic counters out of the box so we'll
|
|
@@ -219,39 +250,39 @@ module Sidekiq
|
|
|
219
250
|
end
|
|
220
251
|
|
|
221
252
|
# jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
|
|
222
|
-
class
|
|
253
|
+
class SharedWorkState
|
|
223
254
|
def initialize
|
|
224
|
-
@
|
|
255
|
+
@work_state = {}
|
|
225
256
|
@lock = Mutex.new
|
|
226
257
|
end
|
|
227
258
|
|
|
228
259
|
def set(tid, hash)
|
|
229
|
-
@lock.synchronize { @
|
|
260
|
+
@lock.synchronize { @work_state[tid] = hash }
|
|
230
261
|
end
|
|
231
262
|
|
|
232
263
|
def delete(tid)
|
|
233
|
-
@lock.synchronize { @
|
|
264
|
+
@lock.synchronize { @work_state.delete(tid) }
|
|
234
265
|
end
|
|
235
266
|
|
|
236
267
|
def dup
|
|
237
|
-
@lock.synchronize { @
|
|
268
|
+
@lock.synchronize { @work_state.dup }
|
|
238
269
|
end
|
|
239
270
|
|
|
240
271
|
def size
|
|
241
|
-
@lock.synchronize { @
|
|
272
|
+
@lock.synchronize { @work_state.size }
|
|
242
273
|
end
|
|
243
274
|
|
|
244
275
|
def clear
|
|
245
|
-
@lock.synchronize { @
|
|
276
|
+
@lock.synchronize { @work_state.clear }
|
|
246
277
|
end
|
|
247
278
|
end
|
|
248
279
|
|
|
249
280
|
PROCESSED = Counter.new
|
|
250
281
|
FAILURE = Counter.new
|
|
251
|
-
|
|
282
|
+
WORK_STATE = SharedWorkState.new
|
|
252
283
|
|
|
253
284
|
def stats(jobstr, queue)
|
|
254
|
-
|
|
285
|
+
WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
|
|
255
286
|
|
|
256
287
|
begin
|
|
257
288
|
yield
|
|
@@ -259,22 +290,9 @@ module Sidekiq
|
|
|
259
290
|
FAILURE.incr
|
|
260
291
|
raise
|
|
261
292
|
ensure
|
|
262
|
-
|
|
293
|
+
WORK_STATE.delete(tid)
|
|
263
294
|
PROCESSED.incr
|
|
264
295
|
end
|
|
265
296
|
end
|
|
266
|
-
|
|
267
|
-
def constantize(str)
|
|
268
|
-
return Object.const_get(str) unless str.include?("::")
|
|
269
|
-
|
|
270
|
-
names = str.split("::")
|
|
271
|
-
names.shift if names.empty? || names.first.empty?
|
|
272
|
-
|
|
273
|
-
names.inject(Object) do |constant, name|
|
|
274
|
-
# the false flag limits search for name to under the constant namespace
|
|
275
|
-
# which mimics Rails' behaviour
|
|
276
|
-
constant.const_get(name, false)
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
297
|
end
|
|
280
298
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
|
|
15
|
+
def initialize(config)
|
|
16
|
+
@config = config
|
|
17
|
+
@vernier_output_dir = ENV.fetch("VERNIER_OUTPUT_DIR") { Dir.tmpdir }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(job, &block)
|
|
21
|
+
return yield unless job["profile"]
|
|
22
|
+
|
|
23
|
+
token = job["profile"]
|
|
24
|
+
type = job["wrapped"] || job["class"]
|
|
25
|
+
jid = job["jid"]
|
|
26
|
+
started_at = Time.now
|
|
27
|
+
|
|
28
|
+
rundata = {
|
|
29
|
+
started_at: started_at.to_i,
|
|
30
|
+
token: token,
|
|
31
|
+
type: type,
|
|
32
|
+
jid: jid,
|
|
33
|
+
# .gz extension tells Vernier to compress the data
|
|
34
|
+
filename: File.join(
|
|
35
|
+
@vernier_output_dir,
|
|
36
|
+
"#{token}-#{type}-#{jid}-#{started_at.strftime("%Y%m%d-%H%M%S")}.json.gz"
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
profiler_options = profiler_options(job, rundata)
|
|
40
|
+
|
|
41
|
+
require "vernier"
|
|
42
|
+
begin
|
|
43
|
+
a = Time.now
|
|
44
|
+
rc = Vernier.profile(**profiler_options, &block)
|
|
45
|
+
b = Time.now
|
|
46
|
+
|
|
47
|
+
# Failed jobs will raise an exception on previous line and skip this
|
|
48
|
+
# block. Only successful jobs will persist profile data to Redis.
|
|
49
|
+
key = "#{token}-#{jid}"
|
|
50
|
+
data = File.read(rundata[:filename])
|
|
51
|
+
redis do |conn|
|
|
52
|
+
conn.multi do |m|
|
|
53
|
+
m.zadd("profiles", Time.now.to_f + EXPIRY, key)
|
|
54
|
+
m.hset(key, rundata.merge(elapsed: (b - a), data: data, size: data.bytesize))
|
|
55
|
+
m.expire(key, EXPIRY)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
rc
|
|
59
|
+
ensure
|
|
60
|
+
FileUtils.rm_f(rundata[:filename])
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def profiler_options(job, rundata)
|
|
67
|
+
profiler_options = (job["profiler_options"] || {}).transform_keys(&:to_sym)
|
|
68
|
+
profiler_options[:mode] = profiler_options[:mode].to_sym if profiler_options[:mode]
|
|
69
|
+
|
|
70
|
+
DEFAULT_OPTIONS.merge(profiler_options, {out: rundata[:filename]})
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/sidekiq/rails.rb
CHANGED
|
@@ -1,50 +1,63 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "sidekiq/worker"
|
|
4
|
-
|
|
5
3
|
module Sidekiq
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
begin
|
|
5
|
+
gem "railties", ">= 7.0"
|
|
6
|
+
require "rails"
|
|
7
|
+
require "sidekiq/job"
|
|
8
|
+
require_relative "../active_job/queue_adapters/sidekiq_adapter"
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
class Rails < ::Rails::Engine
|
|
11
|
+
class Reloader
|
|
12
|
+
def initialize(app = ::Rails.application)
|
|
13
|
+
@app = app
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
|
|
18
|
+
@app.reloader.wrap(**params) do
|
|
19
|
+
yield
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def inspect
|
|
24
|
+
"#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
|
|
15
25
|
end
|
|
16
|
-
end
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
def to_hash
|
|
28
|
+
{app: @app.class.name}
|
|
29
|
+
end
|
|
20
30
|
end
|
|
21
|
-
end
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
#
|
|
28
|
-
# class SomeJob < ActiveJob::Base
|
|
29
|
-
# queue_as :default
|
|
30
|
-
# sidekiq_options retry: 3, backtrace: 10
|
|
31
|
-
# def perform
|
|
32
|
-
# end
|
|
33
|
-
# end
|
|
34
|
-
initializer "sidekiq.active_job_integration" do
|
|
35
|
-
ActiveSupport.on_load(:active_job) do
|
|
36
|
-
include ::Sidekiq::Worker::Options unless respond_to?(:sidekiq_options)
|
|
32
|
+
initializer "sidekiq.backtrace_cleaner" do
|
|
33
|
+
Sidekiq.configure_server do |config|
|
|
34
|
+
config[:backtrace_cleaner] = ->(backtrace) { ::Rails.backtrace_cleaner.clean(backtrace) }
|
|
35
|
+
end
|
|
37
36
|
end
|
|
38
|
-
end
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
# This hook happens after all initializers are run, just before returning
|
|
39
|
+
# from config/environment.rb back to sidekiq/cli.rb.
|
|
40
|
+
#
|
|
41
|
+
# None of this matters on the client-side, only within the Sidekiq process itself.
|
|
42
|
+
config.after_initialize do
|
|
43
|
+
Sidekiq.configure_server do |config|
|
|
44
|
+
config[:reloader] = Sidekiq::Rails::Reloader.new
|
|
45
|
+
|
|
46
|
+
# This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
|
|
47
|
+
# it will appear in the Sidekiq console with all of the job context.
|
|
48
|
+
unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
|
49
|
+
if ::Rails.logger.respond_to?(:broadcast_to)
|
|
50
|
+
::Rails.logger.broadcast_to(config.logger)
|
|
51
|
+
elsif ::ActiveSupport::Logger.respond_to?(:broadcast)
|
|
52
|
+
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
|
53
|
+
else
|
|
54
|
+
::Rails.logger = ::ActiveSupport::BroadcastLogger.new(::Rails.logger, config.logger)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
47
58
|
end
|
|
48
59
|
end
|
|
60
|
+
rescue Gem::LoadError
|
|
61
|
+
# Rails not available or version requirement not met
|
|
49
62
|
end
|
|
50
63
|
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "redis_client"
|
|
4
|
+
require "redis_client/decorator"
|
|
5
|
+
|
|
6
|
+
module Sidekiq
|
|
7
|
+
class RedisClientAdapter
|
|
8
|
+
BaseError = RedisClient::Error
|
|
9
|
+
CommandError = RedisClient::CommandError
|
|
10
|
+
|
|
11
|
+
# You can add/remove items or clear the whole thing if you don't want deprecation warnings.
|
|
12
|
+
DEPRECATED_COMMANDS = %i[rpoplpush zrangebyscore zrevrange zrevrangebyscore getset hmset setex setnx].to_set
|
|
13
|
+
|
|
14
|
+
module CompatMethods
|
|
15
|
+
def info
|
|
16
|
+
@client.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def evalsha(sha, keys, argv)
|
|
20
|
+
@client.call("EVALSHA", sha, keys.size, *keys, *argv)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# this is the set of Redis commands used by Sidekiq. Not guaranteed
|
|
24
|
+
# to be comprehensive, we use this as a performance enhancement to
|
|
25
|
+
# avoid calling method_missing on most commands
|
|
26
|
+
USED_COMMANDS = %w[bitfield bitfield_ro del exists expire flushdb
|
|
27
|
+
get hdel hget hgetall hincrby hlen hmget hset hsetnx incr incrby
|
|
28
|
+
lindex llen lmove lpop lpush lrange lrem mget mset ping pttl
|
|
29
|
+
publish rpop rpush sadd scard script set sismember smembers
|
|
30
|
+
srem ttl type unlink zadd zcard zincrby zrange zrem
|
|
31
|
+
zremrangebyrank zremrangebyscore]
|
|
32
|
+
|
|
33
|
+
USED_COMMANDS.each do |name|
|
|
34
|
+
define_method(name) do |*args, **kwargs|
|
|
35
|
+
@client.call(name, *args, **kwargs)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# this allows us to use methods like `conn.hmset(...)` instead of having to use
|
|
42
|
+
# redis-client's native `conn.call("hmset", ...)`
|
|
43
|
+
def method_missing(*args, &block)
|
|
44
|
+
warn("[sidekiq#5788] Redis has deprecated the `#{args.first}`command, called at #{caller(1..1)}") if DEPRECATED_COMMANDS.include?(args.first)
|
|
45
|
+
@client.call(*args, *block)
|
|
46
|
+
end
|
|
47
|
+
ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
|
|
48
|
+
|
|
49
|
+
def respond_to_missing?(name, include_private = false)
|
|
50
|
+
super # Appease the linter. We can't tell what is a valid command.
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
CompatClient = RedisClient::Decorator.create(CompatMethods)
|
|
55
|
+
|
|
56
|
+
class CompatClient
|
|
57
|
+
def config
|
|
58
|
+
@client.config
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def initialize(options)
|
|
63
|
+
opts = client_opts(options)
|
|
64
|
+
@config = if opts.key?(:sentinels)
|
|
65
|
+
RedisClient.sentinel(**opts)
|
|
66
|
+
elsif opts.key?(:nodes)
|
|
67
|
+
# Sidekiq does not support Redis clustering but Sidekiq Enterprise's
|
|
68
|
+
# rate limiters are cluster-safe so we can scale to millions
|
|
69
|
+
# of rate limiters using a Redis cluster. This requires the
|
|
70
|
+
# `redis-cluster-client` gem.
|
|
71
|
+
# Sidekiq::Limiter.redis = { nodes: [...] }
|
|
72
|
+
RedisClient.cluster(**opts)
|
|
73
|
+
else
|
|
74
|
+
RedisClient.config(**opts)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def new_client
|
|
79
|
+
CompatClient.new(@config.new_client)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def client_opts(options)
|
|
85
|
+
opts = options.dup
|
|
86
|
+
|
|
87
|
+
if opts[:namespace]
|
|
88
|
+
raise ArgumentError, "Your Redis configuration uses the namespace '#{opts[:namespace]}' but this feature is no longer supported in Sidekiq 7+. See https://github.com/sidekiq/sidekiq/blob/main/docs/7.0-Upgrade.md#redis-namespace."
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
opts.delete(:size)
|
|
92
|
+
opts.delete(:pool_timeout)
|
|
93
|
+
|
|
94
|
+
if opts[:network_timeout]
|
|
95
|
+
opts[:timeout] = opts[:network_timeout]
|
|
96
|
+
opts.delete(:network_timeout)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
opts[:name] = opts.delete(:master_name) if opts.key?(:master_name)
|
|
100
|
+
opts[:role] = opts[:role].to_sym if opts.key?(:role)
|
|
101
|
+
opts[:driver] = opts[:driver].to_sym if opts.key?(:driver)
|
|
102
|
+
|
|
103
|
+
# Issue #3303, redis-rb will silently retry an operation.
|
|
104
|
+
# This can lead to duplicate jobs if Sidekiq::Client's LPUSH
|
|
105
|
+
# is performed twice but I believe this is much, much rarer
|
|
106
|
+
# than the reconnect silently fixing a problem; we keep it
|
|
107
|
+
# on by default.
|
|
108
|
+
opts[:reconnect_attempts] ||= 1
|
|
109
|
+
|
|
110
|
+
# Identify ourselves to Redis via CLIENT SETINFO so connections
|
|
111
|
+
# are distinguishable in CLIENT LIST / CLIENT INFO output.
|
|
112
|
+
opts[:driver_info] ||= "sidekiq_v#{Sidekiq::VERSION}"
|
|
113
|
+
|
|
114
|
+
opts
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|