sidekiq 4.2.4 → 5.2.0
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 +4 -4
- data/.github/issue_template.md +8 -1
- data/.gitignore +1 -0
- data/.travis.yml +5 -3
- data/5.0-Upgrade.md +56 -0
- data/COMM-LICENSE +1 -1
- data/Changes.md +151 -0
- data/Ent-Changes.md +77 -2
- data/Gemfile +10 -25
- data/LICENSE +1 -1
- data/Pro-4.0-Upgrade.md +35 -0
- data/Pro-Changes.md +156 -2
- data/README.md +9 -6
- data/Rakefile +1 -2
- data/bin/sidekiqctl +1 -1
- data/bin/sidekiqload +15 -33
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +1 -1
- data/lib/sidekiq/api.rb +157 -67
- data/lib/sidekiq/cli.rb +71 -26
- data/lib/sidekiq/client.rb +25 -18
- data/lib/sidekiq/core_ext.rb +1 -106
- data/lib/sidekiq/delay.rb +42 -0
- data/lib/sidekiq/exception_handler.rb +2 -4
- data/lib/sidekiq/extensions/generic_proxy.rb +7 -1
- data/lib/sidekiq/fetch.rb +1 -1
- data/lib/sidekiq/job_logger.rb +25 -0
- data/lib/sidekiq/job_retry.rb +241 -0
- data/lib/sidekiq/launcher.rb +45 -37
- data/lib/sidekiq/logging.rb +18 -2
- data/lib/sidekiq/manager.rb +3 -4
- data/lib/sidekiq/middleware/server/active_record.rb +10 -0
- data/lib/sidekiq/processor.rb +91 -34
- data/lib/sidekiq/rails.rb +15 -51
- data/lib/sidekiq/redis_connection.rb +31 -5
- data/lib/sidekiq/scheduled.rb +35 -8
- data/lib/sidekiq/testing.rb +24 -7
- data/lib/sidekiq/util.rb +6 -2
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +2 -6
- data/lib/sidekiq/web/application.rb +28 -21
- data/lib/sidekiq/web/helpers.rb +67 -23
- data/lib/sidekiq/web/router.rb +14 -10
- data/lib/sidekiq/web.rb +4 -4
- data/lib/sidekiq/worker.rb +97 -14
- data/lib/sidekiq.rb +23 -24
- data/sidekiq.gemspec +7 -10
- data/web/assets/javascripts/application.js +0 -0
- data/web/assets/javascripts/dashboard.js +18 -13
- data/web/assets/stylesheets/application-rtl.css +246 -0
- data/web/assets/stylesheets/application.css +336 -4
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +80 -0
- data/web/locales/en.yml +1 -0
- data/web/locales/es.yml +4 -3
- data/web/locales/fa.yml +80 -0
- data/web/locales/he.yml +79 -0
- data/web/locales/ja.yml +5 -3
- data/web/locales/ur.yml +80 -0
- data/web/views/_footer.erb +5 -2
- data/web/views/_job_info.erb +1 -1
- data/web/views/_nav.erb +1 -1
- data/web/views/_paging.erb +1 -1
- data/web/views/busy.erb +9 -5
- data/web/views/dashboard.erb +3 -3
- data/web/views/layout.erb +11 -2
- data/web/views/morgue.erb +14 -10
- data/web/views/queue.erb +10 -10
- data/web/views/queues.erb +4 -2
- data/web/views/retries.erb +13 -11
- data/web/views/retry.erb +1 -1
- data/web/views/scheduled.erb +2 -2
- metadata +26 -160
- data/lib/sidekiq/middleware/server/logging.rb +0 -40
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
- data/test/config.yml +0 -9
- data/test/env_based_config.yml +0 -11
- data/test/fake_env.rb +0 -1
- data/test/fixtures/en.yml +0 -2
- data/test/helper.rb +0 -75
- data/test/test_actors.rb +0 -138
- data/test/test_api.rb +0 -528
- data/test/test_cli.rb +0 -418
- data/test/test_client.rb +0 -266
- data/test/test_exception_handler.rb +0 -56
- data/test/test_extensions.rb +0 -127
- data/test/test_fetch.rb +0 -50
- data/test/test_launcher.rb +0 -95
- data/test/test_logging.rb +0 -35
- data/test/test_manager.rb +0 -50
- data/test/test_middleware.rb +0 -158
- data/test/test_processor.rb +0 -235
- data/test/test_rails.rb +0 -22
- data/test/test_redis_connection.rb +0 -132
- data/test/test_retry.rb +0 -326
- data/test/test_retry_exhausted.rb +0 -149
- data/test/test_scheduled.rb +0 -115
- data/test/test_scheduling.rb +0 -58
- data/test/test_sidekiq.rb +0 -107
- data/test/test_testing.rb +0 -143
- data/test/test_testing_fake.rb +0 -357
- data/test/test_testing_inline.rb +0 -94
- data/test/test_util.rb +0 -13
- data/test/test_web.rb +0 -726
- data/test/test_web_helpers.rb +0 -54
data/lib/sidekiq/rails.rb
CHANGED
@@ -1,36 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module Sidekiq
|
3
|
-
def self.hook_rails!
|
4
|
-
return if defined?(@delay_removed)
|
5
|
-
|
6
|
-
ActiveSupport.on_load(:active_record) do
|
7
|
-
include Sidekiq::Extensions::ActiveRecord
|
8
|
-
end
|
9
|
-
|
10
|
-
ActiveSupport.on_load(:action_mailer) do
|
11
|
-
extend Sidekiq::Extensions::ActionMailer
|
12
|
-
end
|
13
|
-
|
14
|
-
Module.__send__(:include, Sidekiq::Extensions::Klass)
|
15
|
-
end
|
16
|
-
|
17
|
-
# Removes the generic aliases which MAY clash with names of already
|
18
|
-
# created methods by other applications. The methods `sidekiq_delay`,
|
19
|
-
# `sidekiq_delay_for` and `sidekiq_delay_until` can be used instead.
|
20
|
-
def self.remove_delay!
|
21
|
-
@delay_removed = true
|
22
|
-
|
23
|
-
[Extensions::ActiveRecord,
|
24
|
-
Extensions::ActionMailer,
|
25
|
-
Extensions::Klass].each do |mod|
|
26
|
-
mod.module_eval do
|
27
|
-
remove_method :delay if respond_to?(:delay)
|
28
|
-
remove_method :delay_for if respond_to?(:delay_for)
|
29
|
-
remove_method :delay_until if respond_to?(:delay_until)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
3
|
class Rails < ::Rails::Engine
|
35
4
|
# We need to setup this up before any application configuration which might
|
36
5
|
# change Sidekiq middleware.
|
@@ -48,26 +17,16 @@ module Sidekiq
|
|
48
17
|
end
|
49
18
|
end
|
50
19
|
|
51
|
-
initializer 'sidekiq' do
|
52
|
-
Sidekiq.hook_rails!
|
53
|
-
end
|
54
|
-
|
55
|
-
# We have to add the reloader after initialize to see if cache_classes has
|
56
|
-
# been turned on.
|
57
|
-
#
|
58
|
-
# This hook happens after all initialziers are run, just before returning
|
59
|
-
# from config/environment.rb back to sidekiq/cli.rb.
|
60
20
|
config.after_initialize do
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
else
|
21
|
+
# This hook happens after all initializers are run, just before returning
|
22
|
+
# from config/environment.rb back to sidekiq/cli.rb.
|
23
|
+
# We have to add the reloader after initialize to see if cache_classes has
|
24
|
+
# been turned on.
|
25
|
+
#
|
26
|
+
# None of this matters on the client-side, only within the Sidekiq process itself.
|
27
|
+
#
|
28
|
+
Sidekiq.configure_server do |_|
|
29
|
+
if ::Rails::VERSION::MAJOR >= 5
|
71
30
|
Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
|
72
31
|
end
|
73
32
|
end
|
@@ -75,7 +34,6 @@ module Sidekiq
|
|
75
34
|
|
76
35
|
class Reloader
|
77
36
|
def initialize(app = ::Rails.application)
|
78
|
-
Sidekiq.logger.debug "Enabling Rails 5+ live code reloading, so hot!" unless app.config.cache_classes
|
79
37
|
@app = app
|
80
38
|
end
|
81
39
|
|
@@ -91,3 +49,9 @@ module Sidekiq
|
|
91
49
|
end
|
92
50
|
end if defined?(::Rails)
|
93
51
|
end
|
52
|
+
|
53
|
+
if defined?(::Rails) && ::Rails::VERSION::MAJOR < 4
|
54
|
+
$stderr.puts("**************************************************")
|
55
|
+
$stderr.puts("⛔️ WARNING: Sidekiq server is no longer supported by Rails 3.2 - please ensure your server/workers are updated")
|
56
|
+
$stderr.puts("**************************************************")
|
57
|
+
end
|
@@ -8,11 +8,22 @@ module Sidekiq
|
|
8
8
|
class << self
|
9
9
|
|
10
10
|
def create(options={})
|
11
|
-
options
|
11
|
+
options.keys.each do |key|
|
12
|
+
options[key.to_sym] = options.delete(key)
|
13
|
+
end
|
12
14
|
|
15
|
+
options[:id] = "Sidekiq-#{Sidekiq.server? ? "server" : "client"}-PID-#{$$}" if !options.has_key?(:id)
|
13
16
|
options[:url] ||= determine_redis_provider
|
14
17
|
|
15
|
-
size = options[:size]
|
18
|
+
size = if options[:size]
|
19
|
+
options[:size]
|
20
|
+
elsif Sidekiq.server?
|
21
|
+
Sidekiq.options[:concurrency] + 5
|
22
|
+
elsif ENV['RAILS_MAX_THREADS']
|
23
|
+
Integer(ENV['RAILS_MAX_THREADS'])
|
24
|
+
else
|
25
|
+
5
|
26
|
+
end
|
16
27
|
|
17
28
|
verify_sizing(size, Sidekiq.options[:concurrency]) if Sidekiq.server?
|
18
29
|
|
@@ -35,7 +46,7 @@ module Sidekiq
|
|
35
46
|
# - enterprise's leader election
|
36
47
|
# - enterprise's cron support
|
37
48
|
def verify_sizing(size, concurrency)
|
38
|
-
raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but
|
49
|
+
raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size <= concurrency
|
39
50
|
end
|
40
51
|
|
41
52
|
def build_client(options)
|
@@ -67,7 +78,14 @@ module Sidekiq
|
|
67
78
|
opts.delete(:network_timeout)
|
68
79
|
end
|
69
80
|
|
70
|
-
opts[:driver]
|
81
|
+
opts[:driver] ||= 'ruby'
|
82
|
+
|
83
|
+
# Issue #3303, redis-rb will silently retry an operation.
|
84
|
+
# This can lead to duplicate jobs if Sidekiq::Client's LPUSH
|
85
|
+
# is performed twice but I believe this is much, much rarer
|
86
|
+
# than the reconnect silently fixing a problem; we keep it
|
87
|
+
# on by default.
|
88
|
+
opts[:reconnect_attempts] ||= 1
|
71
89
|
|
72
90
|
opts
|
73
91
|
end
|
@@ -91,7 +109,15 @@ module Sidekiq
|
|
91
109
|
end
|
92
110
|
|
93
111
|
def determine_redis_provider
|
94
|
-
|
112
|
+
# If you have this in your environment:
|
113
|
+
# MY_REDIS_URL=redis://hostname.example.com:1238/4
|
114
|
+
# then set:
|
115
|
+
# REDIS_PROVIDER=MY_REDIS_URL
|
116
|
+
# and Sidekiq will find your custom URL variable with no custom
|
117
|
+
# initialization code at all.
|
118
|
+
ENV[
|
119
|
+
ENV['REDIS_PROVIDER'] || 'REDIS_URL'
|
120
|
+
]
|
95
121
|
end
|
96
122
|
|
97
123
|
end
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -17,7 +17,7 @@ module Sidekiq
|
|
17
17
|
# We need to go through the list one at a time to reduce the risk of something
|
18
18
|
# going wrong between the time jobs are popped from the scheduled queue and when
|
19
19
|
# they are pushed onto a work queue and losing the jobs.
|
20
|
-
while job = conn.zrangebyscore(sorted_set, '-inf'
|
20
|
+
while job = conn.zrangebyscore(sorted_set, '-inf', now, :limit => [0, 1]).first do
|
21
21
|
|
22
22
|
# Pop item off the queue and add it to the work queue. If the job can't be popped from
|
23
23
|
# the queue, it's because another process already popped it so we can move on to the
|
@@ -79,9 +79,7 @@ module Sidekiq
|
|
79
79
|
# Most likely a problem with redis networking.
|
80
80
|
# Punt and try again at the next interval
|
81
81
|
logger.error ex.message
|
82
|
-
ex
|
83
|
-
logger.error(bt)
|
84
|
-
end
|
82
|
+
handle_exception(ex)
|
85
83
|
end
|
86
84
|
end
|
87
85
|
|
@@ -95,13 +93,38 @@ module Sidekiq
|
|
95
93
|
# if poll_interval_average hasn't been calculated yet, we can
|
96
94
|
# raise an error trying to reach Redis.
|
97
95
|
logger.error ex.message
|
98
|
-
|
96
|
+
handle_exception(ex)
|
99
97
|
sleep 5
|
100
98
|
end
|
101
99
|
|
102
|
-
# Calculates a random interval that is ±50% the desired average.
|
103
100
|
def random_poll_interval
|
104
|
-
|
101
|
+
# We want one Sidekiq process to schedule jobs every N seconds. We have M processes
|
102
|
+
# and **don't** want to coordinate.
|
103
|
+
#
|
104
|
+
# So in N*M second timespan, we want each process to schedule once. The basic loop is:
|
105
|
+
#
|
106
|
+
# * sleep a random amount within that N*M timespan
|
107
|
+
# * wake up and schedule
|
108
|
+
#
|
109
|
+
# We want to avoid one edge case: imagine a set of 2 processes, scheduling every 5 seconds,
|
110
|
+
# so N*M = 10. Each process decides to randomly sleep 8 seconds, now we've failed to meet
|
111
|
+
# that 5 second average. Thankfully each schedule cycle will sleep randomly so the next
|
112
|
+
# iteration could see each process sleep for 1 second, undercutting our average.
|
113
|
+
#
|
114
|
+
# So below 10 processes, we special case and ensure the processes sleep closer to the average.
|
115
|
+
# In the example above, each process should schedule every 10 seconds on average. We special
|
116
|
+
# case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds.
|
117
|
+
# As we run more processes, the scheduling interval average will approach an even spread
|
118
|
+
# between 0 and poll interval so we don't need this artifical boost.
|
119
|
+
#
|
120
|
+
if process_count < 10
|
121
|
+
# For small clusters, calculate a random interval that is ±50% the desired average.
|
122
|
+
poll_interval_average * rand + poll_interval_average.to_f / 2
|
123
|
+
else
|
124
|
+
# With 10+ processes, we should have enough randomness to get decent polling
|
125
|
+
# across the entire timespan
|
126
|
+
poll_interval_average * rand
|
127
|
+
end
|
105
128
|
end
|
106
129
|
|
107
130
|
# We do our best to tune the poll interval to the size of the active Sidekiq
|
@@ -125,9 +148,13 @@ module Sidekiq
|
|
125
148
|
# This minimizes a single point of failure by dispersing check-ins but without taxing
|
126
149
|
# Redis if you run many Sidekiq processes.
|
127
150
|
def scaled_poll_interval
|
151
|
+
process_count * Sidekiq.options[:average_scheduled_poll_interval]
|
152
|
+
end
|
153
|
+
|
154
|
+
def process_count
|
128
155
|
pcount = Sidekiq::ProcessSet.new.size
|
129
156
|
pcount = 1 if pcount == 0
|
130
|
-
pcount
|
157
|
+
pcount
|
131
158
|
end
|
132
159
|
|
133
160
|
def initial_wait
|
data/lib/sidekiq/testing.rb
CHANGED
@@ -55,6 +55,15 @@ module Sidekiq
|
|
55
55
|
yield @server_chain if block_given?
|
56
56
|
@server_chain
|
57
57
|
end
|
58
|
+
|
59
|
+
def constantize(str)
|
60
|
+
names = str.split('::')
|
61
|
+
names.shift if names.empty? || names.first.empty?
|
62
|
+
|
63
|
+
names.inject(Object) do |constant, name|
|
64
|
+
constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
65
|
+
end
|
66
|
+
end
|
58
67
|
end
|
59
68
|
end
|
60
69
|
|
@@ -63,29 +72,31 @@ module Sidekiq
|
|
63
72
|
|
64
73
|
class EmptyQueueError < RuntimeError; end
|
65
74
|
|
66
|
-
|
67
|
-
alias_method :raw_push_real, :raw_push
|
68
|
-
|
75
|
+
module TestingClient
|
69
76
|
def raw_push(payloads)
|
70
77
|
if Sidekiq::Testing.fake?
|
71
78
|
payloads.each do |job|
|
72
|
-
|
79
|
+
job = Sidekiq.load_json(Sidekiq.dump_json(job))
|
80
|
+
job.merge!('enqueued_at' => Time.now.to_f) unless job['at']
|
81
|
+
Queues.push(job['queue'], job['class'], job)
|
73
82
|
end
|
74
83
|
true
|
75
84
|
elsif Sidekiq::Testing.inline?
|
76
85
|
payloads.each do |job|
|
77
|
-
klass = job['class']
|
86
|
+
klass = Sidekiq::Testing.constantize(job['class'])
|
78
87
|
job['id'] ||= SecureRandom.hex(12)
|
79
88
|
job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
|
80
89
|
klass.process_job(job_hash)
|
81
90
|
end
|
82
91
|
true
|
83
92
|
else
|
84
|
-
|
93
|
+
super
|
85
94
|
end
|
86
95
|
end
|
87
96
|
end
|
88
97
|
|
98
|
+
Sidekiq::Client.prepend TestingClient
|
99
|
+
|
89
100
|
module Queues
|
90
101
|
##
|
91
102
|
# The Queues class is only for testing the fake queue implementation.
|
@@ -307,10 +318,16 @@ module Sidekiq
|
|
307
318
|
worker_classes = jobs.map { |job| job["class"] }.uniq
|
308
319
|
|
309
320
|
worker_classes.each do |worker_class|
|
310
|
-
|
321
|
+
Sidekiq::Testing.constantize(worker_class).drain
|
311
322
|
end
|
312
323
|
end
|
313
324
|
end
|
314
325
|
end
|
315
326
|
end
|
316
327
|
end
|
328
|
+
|
329
|
+
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test?
|
330
|
+
puts("**************************************************")
|
331
|
+
puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
|
332
|
+
puts("**************************************************")
|
333
|
+
end
|
data/lib/sidekiq/util.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
require 'socket'
|
3
3
|
require 'securerandom'
|
4
4
|
require 'sidekiq/exception_handler'
|
5
|
-
require 'sidekiq/core_ext'
|
6
5
|
|
7
6
|
module Sidekiq
|
8
7
|
##
|
@@ -22,6 +21,7 @@ module Sidekiq
|
|
22
21
|
|
23
22
|
def safe_thread(name, &block)
|
24
23
|
Thread.new do
|
24
|
+
Thread.current['sidekiq_label'] = name
|
25
25
|
watchdog(name, &block)
|
26
26
|
end
|
27
27
|
end
|
@@ -46,7 +46,10 @@ module Sidekiq
|
|
46
46
|
@@identity ||= "#{hostname}:#{$$}:#{process_nonce}"
|
47
47
|
end
|
48
48
|
|
49
|
-
def fire_event(event,
|
49
|
+
def fire_event(event, options={})
|
50
|
+
reverse = options[:reverse]
|
51
|
+
reraise = options[:reraise]
|
52
|
+
|
50
53
|
arr = Sidekiq.options[:lifecycle_events][event]
|
51
54
|
arr.reverse! if reverse
|
52
55
|
arr.each do |block|
|
@@ -54,6 +57,7 @@ module Sidekiq
|
|
54
57
|
block.call
|
55
58
|
rescue => ex
|
56
59
|
handle_exception(ex, { context: "Exception during Sidekiq lifecycle event.", event: event })
|
60
|
+
raise ex if reraise
|
57
61
|
end
|
58
62
|
end
|
59
63
|
arr.clear
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Sidekiq
|
4
4
|
class WebAction
|
5
|
-
RACK_SESSION = 'rack.session'
|
5
|
+
RACK_SESSION = 'rack.session'
|
6
6
|
|
7
7
|
attr_accessor :env, :block, :type
|
8
8
|
|
@@ -39,10 +39,6 @@ module Sidekiq
|
|
39
39
|
env[RACK_SESSION]
|
40
40
|
end
|
41
41
|
|
42
|
-
def content_type(type)
|
43
|
-
@type = type
|
44
|
-
end
|
45
|
-
|
46
42
|
def erb(content, options = {})
|
47
43
|
if content.kind_of? Symbol
|
48
44
|
unless respond_to?(:"_erb_#{content}")
|
@@ -81,7 +77,7 @@ module Sidekiq
|
|
81
77
|
private
|
82
78
|
|
83
79
|
def _erb(file, locals)
|
84
|
-
locals.each {|k, v| define_singleton_method(k){ v } } if locals
|
80
|
+
locals.each {|k, v| define_singleton_method(k){ v } unless (singleton_methods.include? k)} if locals
|
85
81
|
|
86
82
|
if file.kind_of?(String)
|
87
83
|
ERB.new(file).result(binding)
|
@@ -4,9 +4,24 @@ module Sidekiq
|
|
4
4
|
class WebApplication
|
5
5
|
extend WebRouter
|
6
6
|
|
7
|
-
CONTENT_LENGTH = "Content-Length"
|
8
|
-
CONTENT_TYPE = "Content-Type"
|
7
|
+
CONTENT_LENGTH = "Content-Length"
|
8
|
+
CONTENT_TYPE = "Content-Type"
|
9
9
|
REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
|
10
|
+
CSP_HEADER = [
|
11
|
+
"default-src 'self' https: http:",
|
12
|
+
"child-src 'self'",
|
13
|
+
"connect-src 'self' https: http: wss: ws:",
|
14
|
+
"font-src 'self' https: http:",
|
15
|
+
"frame-src 'self'",
|
16
|
+
"img-src 'self' https: http: data:",
|
17
|
+
"manifest-src 'self'",
|
18
|
+
"media-src 'self'",
|
19
|
+
"object-src 'none'",
|
20
|
+
"script-src 'self' https: http:",
|
21
|
+
"style-src 'self' https: http: 'unsafe-inline'",
|
22
|
+
"worker-src 'self'",
|
23
|
+
"base-uri 'self'"
|
24
|
+
].join('; ').freeze
|
10
25
|
|
11
26
|
def initialize(klass)
|
12
27
|
@klass = klass
|
@@ -28,10 +43,6 @@ module Sidekiq
|
|
28
43
|
# nothing, backwards compatibility
|
29
44
|
end
|
30
45
|
|
31
|
-
get "" do
|
32
|
-
redirect(root_path)
|
33
|
-
end
|
34
|
-
|
35
46
|
get "/" do
|
36
47
|
@redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k }
|
37
48
|
stats_history = Sidekiq::Stats::History.new((params['days'] || 30).to_i)
|
@@ -89,7 +100,7 @@ module Sidekiq
|
|
89
100
|
name = route_params[:name]
|
90
101
|
Sidekiq::Job.new(params['key_val'], name).delete
|
91
102
|
|
92
|
-
redirect_with_query("#{root_path}queues/#{name}")
|
103
|
+
redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
|
93
104
|
end
|
94
105
|
|
95
106
|
get '/morgue' do
|
@@ -238,7 +249,6 @@ module Sidekiq
|
|
238
249
|
get '/stats' do
|
239
250
|
sidekiq_stats = Sidekiq::Stats.new
|
240
251
|
redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
|
241
|
-
|
242
252
|
json(
|
243
253
|
sidekiq: {
|
244
254
|
processed: sidekiq_stats.processed,
|
@@ -251,7 +261,8 @@ module Sidekiq
|
|
251
261
|
dead: sidekiq_stats.dead_size,
|
252
262
|
default_latency: sidekiq_stats.default_queue_latency
|
253
263
|
},
|
254
|
-
redis: redis_stats
|
264
|
+
redis: redis_stats,
|
265
|
+
server_utc_time: server_utc_time
|
255
266
|
)
|
256
267
|
end
|
257
268
|
|
@@ -278,19 +289,15 @@ module Sidekiq
|
|
278
289
|
resp = case resp
|
279
290
|
when Array
|
280
291
|
resp
|
281
|
-
when Fixnum
|
282
|
-
[resp, {}, []]
|
283
292
|
else
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
[200, type_header, [resp]]
|
293
|
+
headers = {
|
294
|
+
"Content-Type" => "text/html",
|
295
|
+
"Cache-Control" => "no-cache",
|
296
|
+
"Content-Language" => action.locale,
|
297
|
+
"Content-Security-Policy" => CSP_HEADER
|
298
|
+
}
|
299
|
+
|
300
|
+
[200, headers, [resp]]
|
294
301
|
end
|
295
302
|
|
296
303
|
resp[1] = resp[1].dup
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'uri'
|
3
|
+
require 'set'
|
3
4
|
require 'yaml'
|
5
|
+
require 'cgi'
|
4
6
|
|
5
7
|
module Sidekiq
|
6
8
|
# This is not a public API
|
@@ -13,7 +15,7 @@ module Sidekiq
|
|
13
15
|
settings.locales.each_with_object({}) do |path, global|
|
14
16
|
find_locale_files(lang).each do |file|
|
15
17
|
strs = YAML.load(File.open(file))
|
16
|
-
global.
|
18
|
+
global.merge!(strs[lang])
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
@@ -22,6 +24,7 @@ module Sidekiq
|
|
22
24
|
def clear_caches
|
23
25
|
@@strings = nil
|
24
26
|
@@locale_files = nil
|
27
|
+
@@available_locales = nil
|
25
28
|
end
|
26
29
|
|
27
30
|
def locale_files
|
@@ -30,6 +33,10 @@ module Sidekiq
|
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
36
|
+
def available_locales
|
37
|
+
@@available_locales ||= locale_files.map { |path| File.basename(path, '.yml') }.uniq
|
38
|
+
end
|
39
|
+
|
33
40
|
def find_locale_files(lang)
|
34
41
|
locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
|
35
42
|
end
|
@@ -63,23 +70,52 @@ module Sidekiq
|
|
63
70
|
end
|
64
71
|
end
|
65
72
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
73
|
+
def text_direction
|
74
|
+
get_locale['TextDirection'] || 'ltr'
|
75
|
+
end
|
76
|
+
|
77
|
+
def rtl?
|
78
|
+
text_direction == 'rtl'
|
79
|
+
end
|
80
|
+
|
81
|
+
# See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
82
|
+
def user_preferred_languages
|
83
|
+
languages = env['HTTP_ACCEPT_LANGUAGE']
|
84
|
+
languages.to_s.downcase.gsub(/\s+/, '').split(',').map do |language|
|
85
|
+
locale, quality = language.split(';q=', 2)
|
86
|
+
locale = nil if locale == '*' # Ignore wildcards
|
87
|
+
quality = quality ? quality.to_f : 1.0
|
88
|
+
[locale, quality]
|
89
|
+
end.sort do |(_, left), (_, right)|
|
90
|
+
right <=> left
|
91
|
+
end.map(&:first).compact
|
92
|
+
end
|
93
|
+
|
94
|
+
# Given an Accept-Language header like "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2"
|
95
|
+
# this method will try to best match the available locales to the user's preferred languages.
|
96
|
+
#
|
97
|
+
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
70
98
|
def locale
|
71
99
|
@locale ||= begin
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
100
|
+
matched_locale = user_preferred_languages.map do |preferred|
|
101
|
+
preferred_language = preferred.split('-', 2).first
|
102
|
+
|
103
|
+
lang_group = available_locales.select do |available|
|
104
|
+
preferred_language == available.split('-', 2).first
|
105
|
+
end
|
106
|
+
|
107
|
+
lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
|
108
|
+
end.compact.first
|
109
|
+
|
110
|
+
matched_locale || 'en'
|
80
111
|
end
|
81
112
|
end
|
82
113
|
|
114
|
+
# mperham/sidekiq#3243
|
115
|
+
def unfiltered?
|
116
|
+
yield unless env['PATH_INFO'].start_with?("/filter/")
|
117
|
+
end
|
118
|
+
|
83
119
|
def get_locale
|
84
120
|
strings(locale)
|
85
121
|
end
|
@@ -111,16 +147,15 @@ module Sidekiq
|
|
111
147
|
end.map { |msg| Sidekiq.load_json(msg) }
|
112
148
|
end
|
113
149
|
|
114
|
-
def location
|
115
|
-
Sidekiq.redis { |conn| conn.client.location }
|
116
|
-
end
|
117
|
-
|
118
150
|
def redis_connection
|
119
|
-
Sidekiq.redis
|
151
|
+
Sidekiq.redis do |conn|
|
152
|
+
c = conn.connection
|
153
|
+
"redis://#{c[:location]}/#{c[:db]}"
|
154
|
+
end
|
120
155
|
end
|
121
156
|
|
122
157
|
def namespace
|
123
|
-
|
158
|
+
@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
|
124
159
|
end
|
125
160
|
|
126
161
|
def redis_info
|
@@ -140,7 +175,8 @@ module Sidekiq
|
|
140
175
|
end
|
141
176
|
|
142
177
|
def relative_time(time)
|
143
|
-
|
178
|
+
stamp = time.getutc.iso8601
|
179
|
+
%{<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>}
|
144
180
|
end
|
145
181
|
|
146
182
|
def job_params(job, score)
|
@@ -148,7 +184,7 @@ module Sidekiq
|
|
148
184
|
end
|
149
185
|
|
150
186
|
def parse_params(params)
|
151
|
-
score, jid = params.split("-")
|
187
|
+
score, jid = params.split("-", 2)
|
152
188
|
[score.to_f, jid]
|
153
189
|
end
|
154
190
|
|
@@ -156,9 +192,13 @@ module Sidekiq
|
|
156
192
|
|
157
193
|
# Merge options with current params, filter safe params, and stringify to query string
|
158
194
|
def qparams(options)
|
159
|
-
|
195
|
+
# stringify
|
196
|
+
options.keys.each do |key|
|
197
|
+
options[key.to_s] = options.delete(key)
|
198
|
+
end
|
199
|
+
|
160
200
|
params.merge(options).map do |key, value|
|
161
|
-
SAFE_QPARAMS.include?(key) ? "#{key}=#{value}" : next
|
201
|
+
SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
|
162
202
|
end.compact.join("&")
|
163
203
|
end
|
164
204
|
|
@@ -246,6 +286,10 @@ module Sidekiq
|
|
246
286
|
"Sidekiq v#{Sidekiq::VERSION}"
|
247
287
|
end
|
248
288
|
|
289
|
+
def server_utc_time
|
290
|
+
Time.now.utc.strftime('%H:%M:%S UTC')
|
291
|
+
end
|
292
|
+
|
249
293
|
def redis_connection_and_namespace
|
250
294
|
@redis_connection_and_namespace ||= begin
|
251
295
|
namespace_suffix = namespace == nil ? '' : "##{namespace}"
|
data/lib/sidekiq/web/router.rb
CHANGED
@@ -3,16 +3,16 @@ require 'rack'
|
|
3
3
|
|
4
4
|
module Sidekiq
|
5
5
|
module WebRouter
|
6
|
-
GET = 'GET'
|
7
|
-
DELETE = 'DELETE'
|
8
|
-
POST = 'POST'
|
9
|
-
PUT = 'PUT'
|
10
|
-
PATCH = 'PATCH'
|
11
|
-
HEAD = 'HEAD'
|
6
|
+
GET = 'GET'
|
7
|
+
DELETE = 'DELETE'
|
8
|
+
POST = 'POST'
|
9
|
+
PUT = 'PUT'
|
10
|
+
PATCH = 'PATCH'
|
11
|
+
HEAD = 'HEAD'
|
12
12
|
|
13
|
-
ROUTE_PARAMS = 'rack.route_params'
|
14
|
-
REQUEST_METHOD = 'REQUEST_METHOD'
|
15
|
-
PATH_INFO = 'PATH_INFO'
|
13
|
+
ROUTE_PARAMS = 'rack.route_params'
|
14
|
+
REQUEST_METHOD = 'REQUEST_METHOD'
|
15
|
+
PATH_INFO = 'PATH_INFO'
|
16
16
|
|
17
17
|
def get(path, &block)
|
18
18
|
route(GET, path, &block)
|
@@ -45,6 +45,10 @@ module Sidekiq
|
|
45
45
|
request_method = env[REQUEST_METHOD]
|
46
46
|
path_info = ::Rack::Utils.unescape env[PATH_INFO]
|
47
47
|
|
48
|
+
# There are servers which send an empty string when requesting the root.
|
49
|
+
# These servers should be ashamed of themselves.
|
50
|
+
path_info = "/" if path_info == ""
|
51
|
+
|
48
52
|
@routes[request_method].each do |route|
|
49
53
|
if params = route.match(request_method, path_info)
|
50
54
|
env[ROUTE_PARAMS] = params
|
@@ -60,7 +64,7 @@ module Sidekiq
|
|
60
64
|
class WebRoute
|
61
65
|
attr_accessor :request_method, :pattern, :block, :name
|
62
66
|
|
63
|
-
NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)
|
67
|
+
NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)/
|
64
68
|
|
65
69
|
def initialize(request_method, pattern, block)
|
66
70
|
@request_method = request_method
|