sidekiq 2.15.1 → 4.2.10
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.github/contributing.md +32 -0
- data/.github/issue_template.md +9 -0
- data/.gitignore +1 -0
- data/.travis.yml +16 -17
- data/3.0-Upgrade.md +70 -0
- data/4.0-Upgrade.md +53 -0
- data/COMM-LICENSE +56 -44
- data/Changes.md +644 -1
- data/Ent-Changes.md +173 -0
- data/Gemfile +27 -0
- data/LICENSE +1 -1
- data/Pro-2.0-Upgrade.md +138 -0
- data/Pro-3.0-Upgrade.md +44 -0
- data/Pro-Changes.md +457 -3
- data/README.md +46 -29
- data/Rakefile +6 -3
- data/bin/sidekiq +4 -0
- data/bin/sidekiqctl +41 -20
- data/bin/sidekiqload +154 -0
- data/code_of_conduct.md +50 -0
- data/lib/generators/sidekiq/templates/worker.rb.erb +9 -0
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +6 -0
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +8 -0
- data/lib/generators/sidekiq/worker_generator.rb +49 -0
- data/lib/sidekiq.rb +141 -29
- data/lib/sidekiq/api.rb +540 -106
- data/lib/sidekiq/cli.rb +131 -71
- data/lib/sidekiq/client.rb +168 -96
- data/lib/sidekiq/core_ext.rb +36 -8
- data/lib/sidekiq/exception_handler.rb +20 -28
- data/lib/sidekiq/extensions/action_mailer.rb +25 -5
- data/lib/sidekiq/extensions/active_record.rb +8 -4
- data/lib/sidekiq/extensions/class_methods.rb +9 -5
- data/lib/sidekiq/extensions/generic_proxy.rb +1 -0
- data/lib/sidekiq/fetch.rb +45 -101
- data/lib/sidekiq/launcher.rb +144 -30
- data/lib/sidekiq/logging.rb +69 -12
- data/lib/sidekiq/manager.rb +90 -140
- data/lib/sidekiq/middleware/chain.rb +18 -5
- data/lib/sidekiq/middleware/i18n.rb +9 -2
- data/lib/sidekiq/middleware/server/active_record.rb +1 -1
- data/lib/sidekiq/middleware/server/logging.rb +11 -11
- data/lib/sidekiq/middleware/server/retry_jobs.rb +98 -44
- data/lib/sidekiq/paginator.rb +20 -8
- data/lib/sidekiq/processor.rb +157 -96
- data/lib/sidekiq/rails.rb +109 -5
- data/lib/sidekiq/redis_connection.rb +70 -24
- data/lib/sidekiq/scheduled.rb +122 -50
- data/lib/sidekiq/testing.rb +171 -31
- data/lib/sidekiq/testing/inline.rb +1 -0
- data/lib/sidekiq/util.rb +31 -5
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web.rb +136 -263
- data/lib/sidekiq/web/action.rb +93 -0
- data/lib/sidekiq/web/application.rb +336 -0
- data/lib/sidekiq/web/helpers.rb +278 -0
- data/lib/sidekiq/web/router.rb +100 -0
- data/lib/sidekiq/worker.rb +40 -7
- data/sidekiq.gemspec +18 -14
- data/web/assets/images/favicon.ico +0 -0
- data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
- data/web/assets/javascripts/application.js +67 -19
- data/web/assets/javascripts/dashboard.js +138 -29
- data/web/assets/stylesheets/application.css +267 -406
- data/web/assets/stylesheets/bootstrap.css +4 -8
- data/web/locales/cs.yml +78 -0
- data/web/locales/da.yml +9 -1
- data/web/locales/de.yml +18 -9
- data/web/locales/el.yml +68 -0
- data/web/locales/en.yml +19 -4
- data/web/locales/es.yml +10 -1
- data/web/locales/fa.yml +79 -0
- data/web/locales/fr.yml +50 -32
- data/web/locales/hi.yml +75 -0
- data/web/locales/it.yml +27 -18
- data/web/locales/ja.yml +27 -12
- data/web/locales/ko.yml +8 -3
- data/web/locales/{no.yml → nb.yml} +19 -5
- data/web/locales/nl.yml +8 -3
- data/web/locales/pl.yml +0 -1
- data/web/locales/pt-br.yml +11 -4
- data/web/locales/pt.yml +8 -1
- data/web/locales/ru.yml +39 -21
- data/web/locales/sv.yml +68 -0
- data/web/locales/ta.yml +75 -0
- data/web/locales/uk.yml +76 -0
- data/web/locales/zh-cn.yml +68 -0
- data/web/locales/zh-tw.yml +68 -0
- data/web/views/_footer.erb +17 -0
- data/web/views/_job_info.erb +72 -60
- data/web/views/_nav.erb +58 -25
- data/web/views/_paging.erb +5 -5
- data/web/views/_poll_link.erb +7 -0
- data/web/views/_summary.erb +20 -14
- data/web/views/busy.erb +94 -0
- data/web/views/dashboard.erb +34 -21
- data/web/views/dead.erb +34 -0
- data/web/views/layout.erb +8 -30
- data/web/views/morgue.erb +75 -0
- data/web/views/queue.erb +37 -30
- data/web/views/queues.erb +26 -20
- data/web/views/retries.erb +60 -47
- data/web/views/retry.erb +23 -19
- data/web/views/scheduled.erb +39 -35
- data/web/views/scheduled_job_info.erb +2 -1
- metadata +152 -195
- data/Contributing.md +0 -29
- data/config.ru +0 -18
- data/lib/sidekiq/actor.rb +0 -7
- data/lib/sidekiq/capistrano.rb +0 -54
- data/lib/sidekiq/yaml_patch.rb +0 -21
- data/test/config.yml +0 -11
- data/test/env_based_config.yml +0 -11
- data/test/fake_env.rb +0 -0
- data/test/helper.rb +0 -42
- data/test/test_api.rb +0 -341
- data/test/test_cli.rb +0 -326
- data/test/test_client.rb +0 -211
- data/test/test_exception_handler.rb +0 -124
- data/test/test_extensions.rb +0 -105
- data/test/test_fetch.rb +0 -44
- data/test/test_manager.rb +0 -83
- data/test/test_middleware.rb +0 -135
- data/test/test_processor.rb +0 -160
- data/test/test_redis_connection.rb +0 -97
- data/test/test_retry.rb +0 -306
- data/test/test_scheduled.rb +0 -86
- data/test/test_scheduling.rb +0 -47
- data/test/test_sidekiq.rb +0 -37
- data/test/test_testing.rb +0 -82
- data/test/test_testing_fake.rb +0 -265
- data/test/test_testing_inline.rb +0 -92
- data/test/test_util.rb +0 -18
- data/test/test_web.rb +0 -372
- data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/web/assets/images/status/active.png +0 -0
- data/web/assets/images/status/idle.png +0 -0
- data/web/assets/javascripts/locales/README.md +0 -27
- data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
- data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.cz.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
- data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
- data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
- data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
- data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.zh-CN.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.zh-TW.js +0 -20
- data/web/views/_poll.erb +0 -14
- data/web/views/_workers.erb +0 -29
- data/web/views/index.erb +0 -16
data/lib/sidekiq/testing.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'securerandom'
|
3
|
+
require 'sidekiq'
|
4
|
+
|
1
5
|
module Sidekiq
|
2
6
|
|
3
7
|
class Testing
|
4
8
|
class << self
|
5
9
|
attr_accessor :__test_mode
|
6
10
|
|
7
|
-
def __set_test_mode(mode
|
8
|
-
if
|
11
|
+
def __set_test_mode(mode)
|
12
|
+
if block_given?
|
9
13
|
current_mode = self.__test_mode
|
10
14
|
begin
|
11
15
|
self.__test_mode = mode
|
12
|
-
|
16
|
+
yield
|
13
17
|
ensure
|
14
18
|
self.__test_mode = current_mode
|
15
19
|
end
|
@@ -45,6 +49,12 @@ module Sidekiq
|
|
45
49
|
def inline?
|
46
50
|
self.__test_mode == :inline
|
47
51
|
end
|
52
|
+
|
53
|
+
def server_middleware
|
54
|
+
@server_chain ||= Middleware::Chain.new
|
55
|
+
yield @server_chain if block_given?
|
56
|
+
@server_chain
|
57
|
+
end
|
48
58
|
end
|
49
59
|
end
|
50
60
|
|
@@ -54,24 +64,127 @@ module Sidekiq
|
|
54
64
|
class EmptyQueueError < RuntimeError; end
|
55
65
|
|
56
66
|
class Client
|
57
|
-
|
58
|
-
alias_method :raw_push_real, :raw_push
|
67
|
+
alias_method :raw_push_real, :raw_push
|
59
68
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
69
|
+
def raw_push(payloads)
|
70
|
+
if Sidekiq::Testing.fake?
|
71
|
+
payloads.each do |job|
|
72
|
+
job = Sidekiq.load_json(Sidekiq.dump_json(job))
|
73
|
+
job.merge!('enqueued_at' => Time.now.to_f) unless job['at']
|
74
|
+
Queues.push(job['queue'], job['class'], job)
|
75
|
+
end
|
76
|
+
true
|
77
|
+
elsif Sidekiq::Testing.inline?
|
78
|
+
payloads.each do |job|
|
79
|
+
klass = job['class'].constantize
|
80
|
+
job['id'] ||= SecureRandom.hex(12)
|
81
|
+
job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
|
82
|
+
klass.process_job(job_hash)
|
74
83
|
end
|
84
|
+
true
|
85
|
+
else
|
86
|
+
raw_push_real(payloads)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
module Queues
|
92
|
+
##
|
93
|
+
# The Queues class is only for testing the fake queue implementation.
|
94
|
+
# There are 2 data structures involved in tandem. This is due to the
|
95
|
+
# Rspec syntax of change(QueueWorker.jobs, :size). It keeps a reference
|
96
|
+
# to the array. Because the array was dervied from a filter of the total
|
97
|
+
# jobs enqueued, it appeared as though the array didn't change.
|
98
|
+
#
|
99
|
+
# To solve this, we'll keep 2 hashes containing the jobs. One with keys based
|
100
|
+
# on the queue, and another with keys of the worker names, so the array for
|
101
|
+
# QueueWorker.jobs is a straight reference to a real array.
|
102
|
+
#
|
103
|
+
# Queue-based hash:
|
104
|
+
#
|
105
|
+
# {
|
106
|
+
# "default"=>[
|
107
|
+
# {
|
108
|
+
# "class"=>"TestTesting::QueueWorker",
|
109
|
+
# "args"=>[1, 2],
|
110
|
+
# "retry"=>true,
|
111
|
+
# "queue"=>"default",
|
112
|
+
# "jid"=>"abc5b065c5c4b27fc1102833",
|
113
|
+
# "created_at"=>1447445554.419934
|
114
|
+
# }
|
115
|
+
# ]
|
116
|
+
# }
|
117
|
+
#
|
118
|
+
# Worker-based hash:
|
119
|
+
#
|
120
|
+
# {
|
121
|
+
# "TestTesting::QueueWorker"=>[
|
122
|
+
# {
|
123
|
+
# "class"=>"TestTesting::QueueWorker",
|
124
|
+
# "args"=>[1, 2],
|
125
|
+
# "retry"=>true,
|
126
|
+
# "queue"=>"default",
|
127
|
+
# "jid"=>"abc5b065c5c4b27fc1102833",
|
128
|
+
# "created_at"=>1447445554.419934
|
129
|
+
# }
|
130
|
+
# ]
|
131
|
+
# }
|
132
|
+
#
|
133
|
+
# Example:
|
134
|
+
#
|
135
|
+
# require 'sidekiq/testing'
|
136
|
+
#
|
137
|
+
# assert_equal 0, Sidekiq::Queues["default"].size
|
138
|
+
# HardWorker.perform_async(:something)
|
139
|
+
# assert_equal 1, Sidekiq::Queues["default"].size
|
140
|
+
# assert_equal :something, Sidekiq::Queues["default"].first['args'][0]
|
141
|
+
#
|
142
|
+
# You can also clear all workers' jobs:
|
143
|
+
#
|
144
|
+
# assert_equal 0, Sidekiq::Queues["default"].size
|
145
|
+
# HardWorker.perform_async(:something)
|
146
|
+
# Sidekiq::Queues.clear_all
|
147
|
+
# assert_equal 0, Sidekiq::Queues["default"].size
|
148
|
+
#
|
149
|
+
# This can be useful to make sure jobs don't linger between tests:
|
150
|
+
#
|
151
|
+
# RSpec.configure do |config|
|
152
|
+
# config.before(:each) do
|
153
|
+
# Sidekiq::Queues.clear_all
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
class << self
|
158
|
+
def [](queue)
|
159
|
+
jobs_by_queue[queue]
|
160
|
+
end
|
161
|
+
|
162
|
+
def push(queue, klass, job)
|
163
|
+
jobs_by_queue[queue] << job
|
164
|
+
jobs_by_worker[klass] << job
|
165
|
+
end
|
166
|
+
|
167
|
+
def jobs_by_queue
|
168
|
+
@jobs_by_queue ||= Hash.new { |hash, key| hash[key] = [] }
|
169
|
+
end
|
170
|
+
|
171
|
+
def jobs_by_worker
|
172
|
+
@jobs_by_worker ||= Hash.new { |hash, key| hash[key] = [] }
|
173
|
+
end
|
174
|
+
|
175
|
+
def delete_for(jid, queue, klass)
|
176
|
+
jobs_by_queue[queue.to_s].delete_if { |job| job["jid"] == jid }
|
177
|
+
jobs_by_worker[klass].delete_if { |job| job["jid"] == jid }
|
178
|
+
end
|
179
|
+
|
180
|
+
def clear_for(queue, klass)
|
181
|
+
jobs_by_queue[queue].clear
|
182
|
+
jobs_by_worker[klass].clear
|
183
|
+
end
|
184
|
+
|
185
|
+
def clear_all
|
186
|
+
jobs_by_queue.clear
|
187
|
+
jobs_by_worker.clear
|
75
188
|
end
|
76
189
|
end
|
77
190
|
end
|
@@ -134,51 +247,78 @@ module Sidekiq
|
|
134
247
|
#
|
135
248
|
module ClassMethods
|
136
249
|
|
250
|
+
# Queue for this worker
|
251
|
+
def queue
|
252
|
+
self.sidekiq_options["queue"]
|
253
|
+
end
|
254
|
+
|
137
255
|
# Jobs queued for this worker
|
138
256
|
def jobs
|
139
|
-
|
257
|
+
Queues.jobs_by_worker[self.to_s]
|
140
258
|
end
|
141
259
|
|
142
260
|
# Clear all jobs for this worker
|
143
261
|
def clear
|
144
|
-
|
262
|
+
Queues.clear_for(queue, self.to_s)
|
145
263
|
end
|
146
264
|
|
147
265
|
# Drain and run all jobs for this worker
|
148
266
|
def drain
|
149
|
-
while
|
150
|
-
|
151
|
-
|
152
|
-
|
267
|
+
while jobs.any?
|
268
|
+
next_job = jobs.first
|
269
|
+
Queues.delete_for(next_job["jid"], next_job["queue"], self.to_s)
|
270
|
+
process_job(next_job)
|
153
271
|
end
|
154
272
|
end
|
155
273
|
|
156
274
|
# Pop out a single job and perform it
|
157
275
|
def perform_one
|
158
276
|
raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
|
159
|
-
|
277
|
+
next_job = jobs.first
|
278
|
+
Queues.delete_for(next_job["jid"], queue, self.to_s)
|
279
|
+
process_job(next_job)
|
280
|
+
end
|
281
|
+
|
282
|
+
def process_job(job)
|
160
283
|
worker = new
|
161
284
|
worker.jid = job['jid']
|
162
|
-
worker.
|
285
|
+
worker.bid = job['bid'] if worker.respond_to?(:bid=)
|
286
|
+
Sidekiq::Testing.server_middleware.invoke(worker, job, job['queue']) do
|
287
|
+
execute_job(worker, job['args'])
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def execute_job(worker, args)
|
292
|
+
worker.perform(*args)
|
163
293
|
end
|
164
294
|
end
|
165
295
|
|
166
296
|
class << self
|
167
297
|
def jobs # :nodoc:
|
168
|
-
|
298
|
+
Queues.jobs_by_queue.values.flatten
|
169
299
|
end
|
170
300
|
|
171
301
|
# Clear all queued jobs across all workers
|
172
302
|
def clear_all
|
173
|
-
|
303
|
+
Queues.clear_all
|
174
304
|
end
|
175
305
|
|
176
306
|
# Drain all queued jobs across all workers
|
177
307
|
def drain_all
|
178
|
-
|
179
|
-
jobs.
|
308
|
+
while jobs.any?
|
309
|
+
worker_classes = jobs.map { |job| job["class"] }.uniq
|
310
|
+
|
311
|
+
worker_classes.each do |worker_class|
|
312
|
+
worker_class.constantize.drain
|
313
|
+
end
|
180
314
|
end
|
181
315
|
end
|
182
316
|
end
|
183
317
|
end
|
184
318
|
end
|
319
|
+
|
320
|
+
if defined?(::Rails) && !Rails.env.test?
|
321
|
+
puts("**************************************************")
|
322
|
+
puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
|
323
|
+
puts("**************************************************")
|
324
|
+
end
|
data/lib/sidekiq/util.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'socket'
|
3
|
+
require 'securerandom'
|
2
4
|
require 'sidekiq/exception_handler'
|
3
5
|
require 'sidekiq/core_ext'
|
4
6
|
|
@@ -14,10 +16,17 @@ module Sidekiq
|
|
14
16
|
def watchdog(last_words)
|
15
17
|
yield
|
16
18
|
rescue Exception => ex
|
17
|
-
handle_exception(ex, { :
|
19
|
+
handle_exception(ex, { context: last_words })
|
18
20
|
raise ex
|
19
21
|
end
|
20
22
|
|
23
|
+
def safe_thread(name, &block)
|
24
|
+
Thread.new do
|
25
|
+
Thread.current['sidekiq_label'] = name
|
26
|
+
watchdog(name, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
21
30
|
def logger
|
22
31
|
Sidekiq.logger
|
23
32
|
end
|
@@ -26,12 +35,29 @@ module Sidekiq
|
|
26
35
|
Sidekiq.redis(&block)
|
27
36
|
end
|
28
37
|
|
29
|
-
def
|
30
|
-
|
38
|
+
def hostname
|
39
|
+
ENV['DYNO'] || Socket.gethostname
|
31
40
|
end
|
32
41
|
|
33
|
-
def
|
34
|
-
|
42
|
+
def process_nonce
|
43
|
+
@@process_nonce ||= SecureRandom.hex(6)
|
44
|
+
end
|
45
|
+
|
46
|
+
def identity
|
47
|
+
@@identity ||= "#{hostname}:#{$$}:#{process_nonce}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def fire_event(event, reverse=false)
|
51
|
+
arr = Sidekiq.options[:lifecycle_events][event]
|
52
|
+
arr.reverse! if reverse
|
53
|
+
arr.each do |block|
|
54
|
+
begin
|
55
|
+
block.call
|
56
|
+
rescue => ex
|
57
|
+
handle_exception(ex, { context: "Exception during Sidekiq lifecycle event.", event: event })
|
58
|
+
end
|
59
|
+
end
|
60
|
+
arr.clear
|
35
61
|
end
|
36
62
|
end
|
37
63
|
end
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web.rb
CHANGED
@@ -1,340 +1,213 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'erb'
|
2
|
-
require 'yaml'
|
3
|
-
require 'sinatra/base'
|
4
3
|
|
5
4
|
require 'sidekiq'
|
6
5
|
require 'sidekiq/api'
|
7
6
|
require 'sidekiq/paginator'
|
7
|
+
require 'sidekiq/web/helpers'
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
set :root, File.expand_path(File.dirname(__FILE__) + "/../../web")
|
14
|
-
set :public_folder, Proc.new { "#{root}/assets" }
|
15
|
-
set :views, Proc.new { "#{root}/views" }
|
16
|
-
set :locales, Proc.new { "#{root}/locales" }
|
17
|
-
|
18
|
-
helpers do
|
19
|
-
def strings
|
20
|
-
@strings ||= begin
|
21
|
-
Dir["#{settings.locales}/*.yml"].inject({}) do |memo, file|
|
22
|
-
memo.merge(YAML.load(File.open(file)))
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def locale
|
28
|
-
lang = (request.env["HTTP_ACCEPT_LANGUAGE"] || 'en')[0,2]
|
29
|
-
strings[lang] ? lang : 'en'
|
30
|
-
end
|
31
|
-
|
32
|
-
def get_locale
|
33
|
-
strings[locale]
|
34
|
-
end
|
35
|
-
|
36
|
-
def t(msg, options={})
|
37
|
-
string = get_locale[msg] || msg
|
38
|
-
string % options
|
39
|
-
end
|
40
|
-
|
41
|
-
def reset_worker_list
|
42
|
-
Sidekiq.redis do |conn|
|
43
|
-
workers = conn.smembers('workers')
|
44
|
-
conn.srem('workers', workers) if !workers.empty?
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def workers_size
|
49
|
-
@workers_size ||= Sidekiq.redis do |conn|
|
50
|
-
conn.scard('workers')
|
51
|
-
end
|
52
|
-
end
|
9
|
+
require 'sidekiq/web/router'
|
10
|
+
require 'sidekiq/web/action'
|
11
|
+
require 'sidekiq/web/application'
|
53
12
|
|
54
|
-
|
55
|
-
@workers ||= begin
|
56
|
-
to_rem = []
|
57
|
-
workers = Sidekiq.redis do |conn|
|
58
|
-
conn.smembers('workers').map do |w|
|
59
|
-
msg = conn.get("worker:#{w}")
|
60
|
-
msg ? [w, Sidekiq.load_json(msg)] : (to_rem << w; nil)
|
61
|
-
end.compact.sort { |x| x[1] ? -1 : 1 }
|
62
|
-
end
|
63
|
-
|
64
|
-
# Detect and clear out any orphaned worker records.
|
65
|
-
# These can be left in Redis if Sidekiq crashes hard
|
66
|
-
# while processing jobs.
|
67
|
-
if to_rem.size > 0
|
68
|
-
Sidekiq.redis { |conn| conn.srem('workers', to_rem) }
|
69
|
-
end
|
70
|
-
workers
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def stats
|
75
|
-
@stats ||= Sidekiq::Stats.new
|
76
|
-
end
|
77
|
-
|
78
|
-
def retries_with_score(score)
|
79
|
-
Sidekiq.redis do |conn|
|
80
|
-
results = conn.zrangebyscore('retry', score, score)
|
81
|
-
results.map { |msg| Sidekiq.load_json(msg) }
|
82
|
-
end
|
83
|
-
end
|
13
|
+
require 'rack/protection'
|
84
14
|
|
85
|
-
|
86
|
-
|
87
|
-
|
15
|
+
require 'rack/builder'
|
16
|
+
require 'rack/file'
|
17
|
+
require 'rack/session/cookie'
|
88
18
|
|
89
|
-
|
90
|
-
|
91
|
-
|
19
|
+
module Sidekiq
|
20
|
+
class Web
|
21
|
+
ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
|
22
|
+
VIEWS = "#{ROOT}/views".freeze
|
23
|
+
LOCALES = ["#{ROOT}/locales".freeze]
|
24
|
+
LAYOUT = "#{VIEWS}/layout.erb".freeze
|
25
|
+
ASSETS = "#{ROOT}/assets".freeze
|
92
26
|
|
93
|
-
|
94
|
-
|
95
|
-
|
27
|
+
DEFAULT_TABS = {
|
28
|
+
"Dashboard" => '',
|
29
|
+
"Busy" => 'busy',
|
30
|
+
"Queues" => 'queues',
|
31
|
+
"Retries" => 'retries',
|
32
|
+
"Scheduled" => 'scheduled',
|
33
|
+
"Dead" => 'morgue',
|
34
|
+
}
|
96
35
|
|
97
|
-
|
98
|
-
|
36
|
+
class << self
|
37
|
+
def settings
|
38
|
+
self
|
99
39
|
end
|
100
40
|
|
101
|
-
def
|
102
|
-
|
103
|
-
return 'active'
|
41
|
+
def middlewares
|
42
|
+
@middlewares ||= []
|
104
43
|
end
|
105
44
|
|
106
|
-
def
|
107
|
-
|
45
|
+
def use(*middleware_args, &block)
|
46
|
+
middlewares << [middleware_args, block]
|
108
47
|
end
|
109
48
|
|
110
|
-
def
|
111
|
-
|
49
|
+
def default_tabs
|
50
|
+
DEFAULT_TABS
|
112
51
|
end
|
113
52
|
|
114
|
-
def
|
115
|
-
|
116
|
-
[score.to_f, jid]
|
53
|
+
def custom_tabs
|
54
|
+
@custom_tabs ||= {}
|
117
55
|
end
|
56
|
+
alias_method :tabs, :custom_tabs
|
118
57
|
|
119
|
-
def
|
120
|
-
|
58
|
+
def locales
|
59
|
+
@locales ||= LOCALES
|
121
60
|
end
|
122
61
|
|
123
|
-
def
|
124
|
-
|
125
|
-
a = arg.inspect
|
126
|
-
truncate(a)
|
127
|
-
end.join(", ")
|
62
|
+
def views
|
63
|
+
@views ||= VIEWS
|
128
64
|
end
|
129
65
|
|
130
|
-
|
131
|
-
|
132
|
-
jid error_message error_class backtrace
|
133
|
-
error_backtrace enqueued_at retry
|
134
|
-
))
|
135
|
-
|
136
|
-
def retry_extra_items(retry_job)
|
137
|
-
@retry_extra_items ||= {}.tap do |extra|
|
138
|
-
retry_job.item.each do |key, value|
|
139
|
-
extra[key] = value unless RETRY_JOB_KEYS.include?(key)
|
140
|
-
end
|
141
|
-
end
|
66
|
+
def enable(*opts)
|
67
|
+
opts.each {|key| set(key, true) }
|
142
68
|
end
|
143
69
|
|
144
|
-
def
|
145
|
-
|
146
|
-
"Dashboard" => '',
|
147
|
-
"Workers" => 'workers',
|
148
|
-
"Queues" => 'queues',
|
149
|
-
"Retries" => 'retries',
|
150
|
-
"Scheduled" => 'scheduled',
|
151
|
-
}
|
70
|
+
def disable(*opts)
|
71
|
+
opts.each {|key| set(key, false) }
|
152
72
|
end
|
153
73
|
|
154
|
-
|
155
|
-
|
74
|
+
# Helper for the Sinatra syntax: Sidekiq::Web.set(:session_secret, Rails.application.secrets...)
|
75
|
+
def set(attribute, value)
|
76
|
+
send(:"#{attribute}=", value)
|
156
77
|
end
|
157
78
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
rescue ArgumentError, TypeError
|
162
|
-
return number
|
163
|
-
end
|
164
|
-
|
165
|
-
options = {:delimiter => ',', :separator => '.'}
|
166
|
-
parts = number.to_s.to_str.split('.')
|
167
|
-
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
|
168
|
-
parts.join(options[:separator])
|
169
|
-
end
|
79
|
+
attr_accessor :app_url, :session_secret, :redis_pool, :sessions
|
80
|
+
attr_writer :locales, :views
|
81
|
+
end
|
170
82
|
|
171
|
-
|
172
|
-
|
173
|
-
|
83
|
+
def self.inherited(child)
|
84
|
+
child.app_url = self.app_url
|
85
|
+
child.session_secret = self.session_secret
|
86
|
+
child.redis_pool = self.redis_pool
|
87
|
+
child.sessions = self.sessions
|
88
|
+
end
|
174
89
|
|
175
|
-
|
176
|
-
|
177
|
-
end
|
90
|
+
def settings
|
91
|
+
self.class.settings
|
178
92
|
end
|
179
93
|
|
180
|
-
|
181
|
-
|
94
|
+
def use(*middleware_args, &block)
|
95
|
+
middlewares << [middleware_args, block]
|
182
96
|
end
|
183
97
|
|
184
|
-
|
185
|
-
@
|
186
|
-
erb :queues
|
98
|
+
def middlewares
|
99
|
+
@middlewares ||= Web.middlewares.dup
|
187
100
|
end
|
188
101
|
|
189
|
-
|
190
|
-
|
191
|
-
@count = (params[:count] || 25).to_i
|
192
|
-
@name = params[:name]
|
193
|
-
(@current_page, @total_size, @messages) = page("queue:#{@name}", params[:page], @count)
|
194
|
-
@messages = @messages.map {|msg| Sidekiq.load_json(msg) }
|
195
|
-
erb :queue
|
102
|
+
def call(env)
|
103
|
+
app.call(env)
|
196
104
|
end
|
197
105
|
|
198
|
-
|
199
|
-
|
200
|
-
|
106
|
+
def self.call(env)
|
107
|
+
@app ||= new
|
108
|
+
@app.call(env)
|
201
109
|
end
|
202
110
|
|
203
|
-
|
204
|
-
|
205
|
-
redirect "#{root_path}queues"
|
111
|
+
def app
|
112
|
+
@app ||= build
|
206
113
|
end
|
207
114
|
|
208
|
-
|
209
|
-
|
210
|
-
redirect "#{root_path}queues/#{params[:name]}"
|
115
|
+
def enable(*opts)
|
116
|
+
opts.each {|key| set(key, true) }
|
211
117
|
end
|
212
118
|
|
213
|
-
|
214
|
-
|
215
|
-
(@current_page, @total_size, @retries) = page("retry", params[:page], @count)
|
216
|
-
@retries = @retries.map {|msg, score| [Sidekiq.load_json(msg), score] }
|
217
|
-
erb :retries
|
119
|
+
def disable(*opts)
|
120
|
+
opts.each {|key| set(key, false) }
|
218
121
|
end
|
219
122
|
|
220
|
-
|
221
|
-
|
222
|
-
@retry = Sidekiq::RetrySet.new.fetch(*parse_params(params['key'])).first
|
223
|
-
redirect "#{root_path}retries" if @retry.nil?
|
224
|
-
erb :retry
|
123
|
+
def set(attribute, value)
|
124
|
+
send(:"#{attribute}=", value)
|
225
125
|
end
|
226
126
|
|
227
|
-
|
228
|
-
|
127
|
+
# Default values
|
128
|
+
set :sessions, true
|
229
129
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
job.delete
|
237
|
-
end
|
130
|
+
attr_writer :sessions
|
131
|
+
|
132
|
+
def sessions
|
133
|
+
unless instance_variable_defined?("@sessions")
|
134
|
+
@sessions = self.class.sessions
|
135
|
+
@sessions = @sessions.to_hash.dup if @sessions.respond_to?(:to_hash)
|
238
136
|
end
|
239
|
-
redirect "#{root_path}retries"
|
240
|
-
end
|
241
137
|
|
242
|
-
|
243
|
-
Sidekiq::RetrySet.new.clear
|
244
|
-
redirect "#{root_path}retries"
|
138
|
+
@sessions
|
245
139
|
end
|
246
140
|
|
247
|
-
|
248
|
-
|
249
|
-
redirect "#{root_path}retries"
|
141
|
+
def self.register(extension)
|
142
|
+
extension.registered(WebApplication)
|
250
143
|
end
|
251
144
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
job.retry
|
258
|
-
elsif params['delete']
|
259
|
-
job.delete
|
260
|
-
end
|
145
|
+
private
|
146
|
+
|
147
|
+
def using?(middleware)
|
148
|
+
middlewares.any? do |(m,_)|
|
149
|
+
m.kind_of?(Array) && (m[0] == middleware || m[0].kind_of?(middleware))
|
261
150
|
end
|
262
|
-
redirect "#{root_path}retries"
|
263
151
|
end
|
264
152
|
|
265
|
-
|
266
|
-
|
267
|
-
(@current_page, @total_size, @scheduled) = page("schedule", params[:page], @count)
|
268
|
-
@scheduled = @scheduled.map {|msg, score| [Sidekiq.load_json(msg), score] }
|
269
|
-
erb :scheduled
|
270
|
-
end
|
153
|
+
def build_sessions
|
154
|
+
middlewares = self.middlewares
|
271
155
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
redirect "#{root_path}scheduled" if @job.nil?
|
276
|
-
erb :scheduled_job_info
|
277
|
-
end
|
156
|
+
unless using?(::Rack::Protection) || ENV['RACK_ENV'] == 'test'
|
157
|
+
middlewares.unshift [[::Rack::Protection, { use: :authenticity_token }], nil]
|
158
|
+
end
|
278
159
|
|
279
|
-
|
280
|
-
|
160
|
+
s = sessions
|
161
|
+
return unless s
|
281
162
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
job.delete
|
287
|
-
elsif params['add_to_queue']
|
288
|
-
job.add_to_queue
|
289
|
-
end
|
163
|
+
unless using? ::Rack::Session::Cookie
|
164
|
+
unless secret = Web.session_secret
|
165
|
+
require 'securerandom'
|
166
|
+
secret = SecureRandom.hex(64)
|
290
167
|
end
|
168
|
+
|
169
|
+
options = { secret: secret }
|
170
|
+
options = options.merge(s.to_hash) if s.respond_to? :to_hash
|
171
|
+
|
172
|
+
middlewares.unshift [[::Rack::Session::Cookie, options], nil]
|
291
173
|
end
|
292
|
-
redirect "#{root_path}scheduled"
|
293
174
|
end
|
294
175
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
176
|
+
def build
|
177
|
+
build_sessions
|
178
|
+
|
179
|
+
middlewares = self.middlewares
|
180
|
+
klass = self.class
|
181
|
+
|
182
|
+
::Rack::Builder.new do
|
183
|
+
%w(stylesheets javascripts images).each do |asset_dir|
|
184
|
+
map "/#{asset_dir}" do
|
185
|
+
run ::Rack::File.new("#{ASSETS}/#{asset_dir}", { 'Cache-Control' => 'public, max-age=86400' })
|
186
|
+
end
|
303
187
|
end
|
188
|
+
|
189
|
+
middlewares.each {|middleware, block| use(*middleware, &block) }
|
190
|
+
|
191
|
+
run WebApplication.new(klass)
|
304
192
|
end
|
305
|
-
redirect "#{root_path}scheduled"
|
306
193
|
end
|
194
|
+
end
|
307
195
|
|
308
|
-
|
309
|
-
|
310
|
-
stats_history = Sidekiq::Stats::History.new((params[:days] || 30).to_i)
|
311
|
-
@processed_history = stats_history.processed
|
312
|
-
@failed_history = stats_history.failed
|
313
|
-
erb :dashboard
|
314
|
-
end
|
196
|
+
Sidekiq::WebApplication.helpers WebHelpers
|
197
|
+
Sidekiq::WebApplication.helpers Sidekiq::Paginator
|
315
198
|
|
316
|
-
|
317
|
-
|
318
|
-
redis_stats = Sidekiq.redis { |conn| conn.info }.select{ |k, v| redis_keys.include? k }
|
319
|
-
|
320
|
-
content_type :json
|
321
|
-
Sidekiq.dump_json({
|
322
|
-
sidekiq: {
|
323
|
-
processed: sidekiq_stats.processed,
|
324
|
-
failed: sidekiq_stats.failed,
|
325
|
-
busy: workers_size,
|
326
|
-
enqueued: sidekiq_stats.enqueued,
|
327
|
-
scheduled: sidekiq_stats.scheduled_size,
|
328
|
-
retries: sidekiq_stats.retry_size,
|
329
|
-
},
|
330
|
-
redis: redis_stats
|
331
|
-
})
|
332
|
-
end
|
199
|
+
Sidekiq::WebAction.class_eval "def _render\n#{ERB.new(File.read(Web::LAYOUT)).src}\nend"
|
200
|
+
end
|
333
201
|
|
334
|
-
|
335
|
-
|
202
|
+
if defined?(::ActionDispatch::Request::Session) &&
|
203
|
+
!::ActionDispatch::Request::Session.method_defined?(:each)
|
204
|
+
# mperham/sidekiq#2460
|
205
|
+
# Rack apps can't reuse the Rails session store without
|
206
|
+
# this monkeypatch, fixed in Rails 5.
|
207
|
+
class ActionDispatch::Request::Session
|
208
|
+
def each(&block)
|
209
|
+
hash = self.to_hash
|
210
|
+
hash.each(&block)
|
336
211
|
end
|
337
|
-
|
338
212
|
end
|
339
|
-
|
340
213
|
end
|