sidekiq 5.2.8 → 6.2.2
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/Changes.md +248 -0
- data/LICENSE +1 -1
- data/README.md +18 -34
- data/bin/sidekiq +26 -2
- data/bin/sidekiqload +32 -24
- data/bin/sidekiqmon +8 -0
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +1 -1
- data/lib/generators/sidekiq/worker_generator.rb +21 -13
- data/lib/sidekiq/api.rb +310 -249
- data/lib/sidekiq/cli.rb +144 -180
- data/lib/sidekiq/client.rb +64 -48
- data/lib/sidekiq/delay.rb +5 -6
- data/lib/sidekiq/exception_handler.rb +10 -12
- data/lib/sidekiq/extensions/action_mailer.rb +13 -22
- data/lib/sidekiq/extensions/active_record.rb +13 -10
- data/lib/sidekiq/extensions/class_methods.rb +14 -11
- data/lib/sidekiq/extensions/generic_proxy.rb +6 -4
- data/lib/sidekiq/fetch.rb +38 -31
- data/lib/sidekiq/job.rb +8 -0
- data/lib/sidekiq/job_logger.rb +45 -7
- data/lib/sidekiq/job_retry.rb +64 -67
- data/lib/sidekiq/launcher.rb +146 -60
- data/lib/sidekiq/logger.rb +166 -0
- data/lib/sidekiq/manager.rb +11 -13
- data/lib/sidekiq/middleware/chain.rb +20 -8
- data/lib/sidekiq/middleware/i18n.rb +5 -7
- data/lib/sidekiq/monitor.rb +133 -0
- data/lib/sidekiq/paginator.rb +18 -14
- data/lib/sidekiq/processor.rb +71 -70
- data/lib/sidekiq/rails.rb +29 -37
- data/lib/sidekiq/redis_connection.rb +50 -48
- data/lib/sidekiq/scheduled.rb +35 -30
- data/lib/sidekiq/sd_notify.rb +149 -0
- data/lib/sidekiq/systemd.rb +24 -0
- data/lib/sidekiq/testing/inline.rb +2 -1
- data/lib/sidekiq/testing.rb +36 -27
- data/lib/sidekiq/util.rb +45 -16
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web/action.rb +15 -11
- data/lib/sidekiq/web/application.rb +86 -76
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +114 -86
- data/lib/sidekiq/web/router.rb +23 -19
- data/lib/sidekiq/web.rb +61 -105
- data/lib/sidekiq/worker.rb +126 -102
- data/lib/sidekiq.rb +69 -44
- data/sidekiq.gemspec +23 -16
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +25 -27
- data/web/assets/javascripts/dashboard.js +4 -23
- data/web/assets/stylesheets/application-dark.css +147 -0
- data/web/assets/stylesheets/application.css +37 -128
- data/web/locales/ar.yml +8 -2
- data/web/locales/de.yml +14 -2
- data/web/locales/en.yml +5 -0
- data/web/locales/es.yml +18 -2
- data/web/locales/fr.yml +10 -3
- data/web/locales/ja.yml +7 -1
- data/web/locales/lt.yml +83 -0
- data/web/locales/pl.yml +4 -4
- data/web/locales/ru.yml +4 -0
- data/web/locales/vi.yml +83 -0
- data/web/views/_job_info.erb +3 -2
- data/web/views/busy.erb +54 -20
- data/web/views/dashboard.erb +14 -6
- data/web/views/dead.erb +3 -3
- data/web/views/layout.erb +2 -0
- data/web/views/morgue.erb +9 -6
- data/web/views/queue.erb +11 -2
- data/web/views/queues.erb +10 -2
- data/web/views/retries.erb +11 -8
- data/web/views/retry.erb +3 -3
- data/web/views/scheduled.erb +5 -2
- metadata +32 -64
- data/.circleci/config.yml +0 -61
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -11
- data/.gitignore +0 -15
- data/.travis.yml +0 -11
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/5.0-Upgrade.md +0 -56
- data/COMM-LICENSE +0 -97
- data/Ent-Changes.md +0 -238
- data/Gemfile +0 -23
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-3.0-Upgrade.md +0 -44
- data/Pro-4.0-Upgrade.md +0 -35
- data/Pro-Changes.md +0 -759
- data/Rakefile +0 -9
- data/bin/sidekiqctl +0 -20
- data/code_of_conduct.md +0 -50
- data/lib/sidekiq/core_ext.rb +0 -1
- data/lib/sidekiq/ctl.rb +0 -221
- data/lib/sidekiq/logging.rb +0 -122
- data/lib/sidekiq/middleware/server/active_record.rb +0 -23
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# this file originally based on authenticity_token.rb from the sinatra/rack-protection project
|
4
|
+
#
|
5
|
+
# The MIT License (MIT)
|
6
|
+
#
|
7
|
+
# Copyright (c) 2011-2017 Konstantin Haase
|
8
|
+
# Copyright (c) 2015-2017 Zachary Scott
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
11
|
+
# a copy of this software and associated documentation files (the
|
12
|
+
# 'Software'), to deal in the Software without restriction, including
|
13
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
14
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
15
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
16
|
+
# the following conditions:
|
17
|
+
#
|
18
|
+
# The above copyright notice and this permission notice shall be
|
19
|
+
# included in all copies or substantial portions of the Software.
|
20
|
+
#
|
21
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
22
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
23
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
24
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
25
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
26
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
27
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
|
+
|
29
|
+
require "securerandom"
|
30
|
+
require "base64"
|
31
|
+
require "rack/request"
|
32
|
+
|
33
|
+
module Sidekiq
|
34
|
+
class Web
|
35
|
+
class CsrfProtection
|
36
|
+
def initialize(app, options = nil)
|
37
|
+
@app = app
|
38
|
+
end
|
39
|
+
|
40
|
+
def call(env)
|
41
|
+
accept?(env) ? admit(env) : deny(env)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def admit(env)
|
47
|
+
# On each successful request, we create a fresh masked token
|
48
|
+
# which will be used in any forms rendered for this request.
|
49
|
+
s = session(env)
|
50
|
+
s[:csrf] ||= SecureRandom.base64(TOKEN_LENGTH)
|
51
|
+
env[:csrf_token] = mask_token(s[:csrf])
|
52
|
+
@app.call(env)
|
53
|
+
end
|
54
|
+
|
55
|
+
def safe?(env)
|
56
|
+
%w[GET HEAD OPTIONS TRACE].include? env["REQUEST_METHOD"]
|
57
|
+
end
|
58
|
+
|
59
|
+
def logger(env)
|
60
|
+
@logger ||= (env["rack.logger"] || ::Logger.new(env["rack.errors"]))
|
61
|
+
end
|
62
|
+
|
63
|
+
def deny(env)
|
64
|
+
logger(env).warn "attack prevented by #{self.class}"
|
65
|
+
[403, {"Content-Type" => "text/plain"}, ["Forbidden"]]
|
66
|
+
end
|
67
|
+
|
68
|
+
def session(env)
|
69
|
+
env["rack.session"] || fail(<<~EOM)
|
70
|
+
Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app,
|
71
|
+
make sure you mount Sidekiq::Web *inside* your application routes:
|
72
|
+
|
73
|
+
|
74
|
+
Rails.application.routes.draw do
|
75
|
+
mount Sidekiq::Web => "/sidekiq"
|
76
|
+
....
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
If this is a Rails app in API mode, you need to enable sessions.
|
81
|
+
|
82
|
+
https://guides.rubyonrails.org/api_app.html#using-session-middlewares
|
83
|
+
|
84
|
+
If this is a bare Rack app, use a session middleware before Sidekiq::Web:
|
85
|
+
|
86
|
+
# first, use IRB to create a shared secret key for sessions and commit it
|
87
|
+
require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) }
|
88
|
+
|
89
|
+
# now use the secret with a session cookie middleware
|
90
|
+
use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
|
91
|
+
run Sidekiq::Web
|
92
|
+
|
93
|
+
EOM
|
94
|
+
end
|
95
|
+
|
96
|
+
def accept?(env)
|
97
|
+
return true if safe?(env)
|
98
|
+
|
99
|
+
giventoken = ::Rack::Request.new(env).params["authenticity_token"]
|
100
|
+
valid_token?(env, giventoken)
|
101
|
+
end
|
102
|
+
|
103
|
+
TOKEN_LENGTH = 32
|
104
|
+
|
105
|
+
# Checks that the token given to us as a parameter matches
|
106
|
+
# the token stored in the session.
|
107
|
+
def valid_token?(env, giventoken)
|
108
|
+
return false if giventoken.nil? || giventoken.empty?
|
109
|
+
|
110
|
+
begin
|
111
|
+
token = decode_token(giventoken)
|
112
|
+
rescue ArgumentError # client input is invalid
|
113
|
+
return false
|
114
|
+
end
|
115
|
+
|
116
|
+
sess = session(env)
|
117
|
+
localtoken = sess[:csrf]
|
118
|
+
|
119
|
+
# Checks that Rack::Session::Cookie actualy contains the csrf toekn
|
120
|
+
return false if localtoken.nil?
|
121
|
+
|
122
|
+
# Rotate the session token after every use
|
123
|
+
sess[:csrf] = SecureRandom.base64(TOKEN_LENGTH)
|
124
|
+
|
125
|
+
# See if it's actually a masked token or not. We should be able
|
126
|
+
# to handle any unmasked tokens that we've issued without error.
|
127
|
+
|
128
|
+
if unmasked_token?(token)
|
129
|
+
compare_with_real_token token, localtoken
|
130
|
+
elsif masked_token?(token)
|
131
|
+
unmasked = unmask_token(token)
|
132
|
+
compare_with_real_token unmasked, localtoken
|
133
|
+
else
|
134
|
+
false # Token is malformed
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Creates a masked version of the authenticity token that varies
|
139
|
+
# on each request. The masking is used to mitigate SSL attacks
|
140
|
+
# like BREACH.
|
141
|
+
def mask_token(token)
|
142
|
+
token = decode_token(token)
|
143
|
+
one_time_pad = SecureRandom.random_bytes(token.length)
|
144
|
+
encrypted_token = xor_byte_strings(one_time_pad, token)
|
145
|
+
masked_token = one_time_pad + encrypted_token
|
146
|
+
Base64.strict_encode64(masked_token)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Essentially the inverse of +mask_token+.
|
150
|
+
def unmask_token(masked_token)
|
151
|
+
# Split the token into the one-time pad and the encrypted
|
152
|
+
# value and decrypt it
|
153
|
+
token_length = masked_token.length / 2
|
154
|
+
one_time_pad = masked_token[0...token_length]
|
155
|
+
encrypted_token = masked_token[token_length..-1]
|
156
|
+
xor_byte_strings(one_time_pad, encrypted_token)
|
157
|
+
end
|
158
|
+
|
159
|
+
def unmasked_token?(token)
|
160
|
+
token.length == TOKEN_LENGTH
|
161
|
+
end
|
162
|
+
|
163
|
+
def masked_token?(token)
|
164
|
+
token.length == TOKEN_LENGTH * 2
|
165
|
+
end
|
166
|
+
|
167
|
+
def compare_with_real_token(token, local)
|
168
|
+
::Rack::Utils.secure_compare(token.to_s, decode_token(local).to_s)
|
169
|
+
end
|
170
|
+
|
171
|
+
def decode_token(token)
|
172
|
+
Base64.strict_decode64(token)
|
173
|
+
end
|
174
|
+
|
175
|
+
def xor_byte_strings(s1, s2)
|
176
|
+
s1.bytes.zip(s2.bytes).map { |(c1, c2)| c1 ^ c2 }.pack("c*")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -1,40 +1,48 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
require "set"
|
5
|
+
require "yaml"
|
6
|
+
require "cgi"
|
6
7
|
|
7
8
|
module Sidekiq
|
8
9
|
# This is not a public API
|
9
10
|
module WebHelpers
|
10
11
|
def strings(lang)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
12
|
+
@strings ||= {}
|
13
|
+
|
14
|
+
# Allow sidekiq-web extensions to add locale paths
|
15
|
+
# so extensions can be localized
|
16
|
+
@strings[lang] ||= settings.locales.each_with_object({}) do |path, global|
|
17
|
+
find_locale_files(lang).each do |file|
|
18
|
+
strs = YAML.load(File.open(file))
|
19
|
+
global.merge!(strs[lang])
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def singularize(str, count)
|
25
|
+
if count == 1 && str.respond_to?(:singularize) # rails
|
26
|
+
str.singularize
|
27
|
+
else
|
28
|
+
str
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
24
32
|
def clear_caches
|
25
|
-
|
26
|
-
|
27
|
-
|
33
|
+
@strings = nil
|
34
|
+
@locale_files = nil
|
35
|
+
@available_locales = nil
|
28
36
|
end
|
29
37
|
|
30
38
|
def locale_files
|
31
|
-
|
39
|
+
@locale_files ||= settings.locales.flat_map { |path|
|
32
40
|
Dir["#{path}/*.yml"]
|
33
|
-
|
41
|
+
}
|
34
42
|
end
|
35
43
|
|
36
44
|
def available_locales
|
37
|
-
|
45
|
+
@available_locales ||= locale_files.map { |path| File.basename(path, ".yml") }.uniq
|
38
46
|
end
|
39
47
|
|
40
48
|
def find_locale_files(lang)
|
@@ -63,32 +71,35 @@ module Sidekiq
|
|
63
71
|
end
|
64
72
|
|
65
73
|
def poll_path
|
66
|
-
if current_path !=
|
67
|
-
root_path + current_path
|
74
|
+
if current_path != "" && params["poll"]
|
75
|
+
path = root_path + current_path
|
76
|
+
query_string = to_query_string(params.slice(*params.keys - %w[page poll]))
|
77
|
+
path += "?#{query_string}" unless query_string.empty?
|
78
|
+
path
|
68
79
|
else
|
69
80
|
""
|
70
81
|
end
|
71
82
|
end
|
72
83
|
|
73
84
|
def text_direction
|
74
|
-
get_locale[
|
85
|
+
get_locale["TextDirection"] || "ltr"
|
75
86
|
end
|
76
87
|
|
77
88
|
def rtl?
|
78
|
-
text_direction ==
|
89
|
+
text_direction == "rtl"
|
79
90
|
end
|
80
91
|
|
81
92
|
# See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
82
93
|
def user_preferred_languages
|
83
|
-
languages = env[
|
84
|
-
languages.to_s.downcase.gsub(/\s+/,
|
85
|
-
locale, quality = language.split(
|
86
|
-
locale
|
94
|
+
languages = env["HTTP_ACCEPT_LANGUAGE"]
|
95
|
+
languages.to_s.downcase.gsub(/\s+/, "").split(",").map { |language|
|
96
|
+
locale, quality = language.split(";q=", 2)
|
97
|
+
locale = nil if locale == "*" # Ignore wildcards
|
87
98
|
quality = quality ? quality.to_f : 1.0
|
88
99
|
[locale, quality]
|
89
|
-
|
100
|
+
}.sort { |(_, left), (_, right)|
|
90
101
|
right <=> left
|
91
|
-
|
102
|
+
}.map(&:first).compact
|
92
103
|
end
|
93
104
|
|
94
105
|
# 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"
|
@@ -97,31 +108,38 @@ module Sidekiq
|
|
97
108
|
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
98
109
|
def locale
|
99
110
|
@locale ||= begin
|
100
|
-
matched_locale = user_preferred_languages.map
|
101
|
-
preferred_language = preferred.split(
|
111
|
+
matched_locale = user_preferred_languages.map { |preferred|
|
112
|
+
preferred_language = preferred.split("-", 2).first
|
102
113
|
|
103
|
-
lang_group = available_locales.select
|
104
|
-
preferred_language == available.split(
|
105
|
-
|
114
|
+
lang_group = available_locales.select { |available|
|
115
|
+
preferred_language == available.split("-", 2).first
|
116
|
+
}
|
106
117
|
|
107
118
|
lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
|
108
|
-
|
119
|
+
}.compact.first
|
109
120
|
|
110
|
-
matched_locale ||
|
121
|
+
matched_locale || "en"
|
111
122
|
end
|
112
123
|
end
|
113
124
|
|
125
|
+
# within is used by Sidekiq Pro
|
126
|
+
def display_tags(job, within = nil)
|
127
|
+
job.tags.map { |tag|
|
128
|
+
"<span class='label label-info jobtag'>#{::Rack::Utils.escape_html(tag)}</span>"
|
129
|
+
}.join(" ")
|
130
|
+
end
|
131
|
+
|
114
132
|
# mperham/sidekiq#3243
|
115
133
|
def unfiltered?
|
116
|
-
yield unless env[
|
134
|
+
yield unless env["PATH_INFO"].start_with?("/filter/")
|
117
135
|
end
|
118
136
|
|
119
137
|
def get_locale
|
120
138
|
strings(locale)
|
121
139
|
end
|
122
140
|
|
123
|
-
def t(msg, options={})
|
124
|
-
string = get_locale[msg] || strings(
|
141
|
+
def t(msg, options = {})
|
142
|
+
string = get_locale[msg] || strings("en")[msg] || msg
|
125
143
|
if options.empty?
|
126
144
|
string
|
127
145
|
else
|
@@ -129,6 +147,10 @@ module Sidekiq
|
|
129
147
|
end
|
130
148
|
end
|
131
149
|
|
150
|
+
def sort_direction_label
|
151
|
+
params[:direction] == "asc" ? "↑" : "↓"
|
152
|
+
end
|
153
|
+
|
132
154
|
def workers
|
133
155
|
@workers ||= Sidekiq::Workers.new
|
134
156
|
end
|
@@ -141,16 +163,9 @@ module Sidekiq
|
|
141
163
|
@stats ||= Sidekiq::Stats.new
|
142
164
|
end
|
143
165
|
|
144
|
-
def retries_with_score(score)
|
145
|
-
Sidekiq.redis do |conn|
|
146
|
-
conn.zrangebyscore('retry', score, score)
|
147
|
-
end.map { |msg| Sidekiq.load_json(msg) }
|
148
|
-
end
|
149
|
-
|
150
166
|
def redis_connection
|
151
167
|
Sidekiq.redis do |conn|
|
152
|
-
|
153
|
-
"redis://#{c[:location]}/#{c[:db]}"
|
168
|
+
conn.connection[:id]
|
154
169
|
end
|
155
170
|
end
|
156
171
|
|
@@ -163,24 +178,24 @@ module Sidekiq
|
|
163
178
|
end
|
164
179
|
|
165
180
|
def root_path
|
166
|
-
"#{env[
|
181
|
+
"#{env["SCRIPT_NAME"]}/"
|
167
182
|
end
|
168
183
|
|
169
184
|
def current_path
|
170
|
-
@current_path ||= request.path_info.gsub(/^\//,
|
185
|
+
@current_path ||= request.path_info.gsub(/^\//, "")
|
171
186
|
end
|
172
187
|
|
173
188
|
def current_status
|
174
|
-
workers.size == 0 ?
|
189
|
+
workers.size == 0 ? "idle" : "active"
|
175
190
|
end
|
176
191
|
|
177
192
|
def relative_time(time)
|
178
193
|
stamp = time.getutc.iso8601
|
179
|
-
%
|
194
|
+
%(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
|
180
195
|
end
|
181
196
|
|
182
197
|
def job_params(job, score)
|
183
|
-
"#{score}-#{job[
|
198
|
+
"#{score}-#{job["jid"]}"
|
184
199
|
end
|
185
200
|
|
186
201
|
def parse_params(params)
|
@@ -188,18 +203,19 @@ module Sidekiq
|
|
188
203
|
[score.to_f, jid]
|
189
204
|
end
|
190
205
|
|
191
|
-
SAFE_QPARAMS = %w
|
206
|
+
SAFE_QPARAMS = %w[page poll direction]
|
192
207
|
|
193
208
|
# Merge options with current params, filter safe params, and stringify to query string
|
194
209
|
def qparams(options)
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
210
|
+
stringified_options = options.transform_keys(&:to_s)
|
211
|
+
|
212
|
+
to_query_string(params.merge(stringified_options))
|
213
|
+
end
|
199
214
|
|
200
|
-
|
215
|
+
def to_query_string(params)
|
216
|
+
params.map { |key, value|
|
201
217
|
SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
|
202
|
-
|
218
|
+
}.compact.join("&")
|
203
219
|
end
|
204
220
|
|
205
221
|
def truncate(text, truncate_after_chars = 2000)
|
@@ -207,40 +223,38 @@ module Sidekiq
|
|
207
223
|
end
|
208
224
|
|
209
225
|
def display_args(args, truncate_after_chars = 2000)
|
210
|
-
return "Invalid job payload, args is nil" if args
|
211
|
-
return "Invalid job payload, args must be an Array, not #{args.class.name}"
|
226
|
+
return "Invalid job payload, args is nil" if args.nil?
|
227
|
+
return "Invalid job payload, args must be an Array, not #{args.class.name}" unless args.is_a?(Array)
|
212
228
|
|
213
229
|
begin
|
214
|
-
args.map
|
230
|
+
args.map { |arg|
|
215
231
|
h(truncate(to_display(arg), truncate_after_chars))
|
216
|
-
|
232
|
+
}.join(", ")
|
217
233
|
rescue
|
218
234
|
"Illegal job arguments: #{h args.inspect}"
|
219
235
|
end
|
220
236
|
end
|
221
237
|
|
222
238
|
def csrf_tag
|
223
|
-
"<input type='hidden' name='authenticity_token' value='#{
|
239
|
+
"<input type='hidden' name='authenticity_token' value='#{env[:csrf_token]}'/>"
|
224
240
|
end
|
225
241
|
|
226
242
|
def to_display(arg)
|
243
|
+
arg.inspect
|
244
|
+
rescue
|
227
245
|
begin
|
228
|
-
arg.
|
229
|
-
rescue
|
230
|
-
|
231
|
-
arg.to_s
|
232
|
-
rescue => ex
|
233
|
-
"Cannot display argument: [#{ex.class.name}] #{ex.message}"
|
234
|
-
end
|
246
|
+
arg.to_s
|
247
|
+
rescue => ex
|
248
|
+
"Cannot display argument: [#{ex.class.name}] #{ex.message}"
|
235
249
|
end
|
236
250
|
end
|
237
251
|
|
238
|
-
RETRY_JOB_KEYS = Set.new(%w
|
252
|
+
RETRY_JOB_KEYS = Set.new(%w[
|
239
253
|
queue class args retry_count retried_at failed_at
|
240
254
|
jid error_message error_class backtrace
|
241
255
|
error_backtrace enqueued_at retry wrapped
|
242
|
-
created_at
|
243
|
-
)
|
256
|
+
created_at tags
|
257
|
+
])
|
244
258
|
|
245
259
|
def retry_extra_items(retry_job)
|
246
260
|
@retry_extra_items ||= {}.tap do |extra|
|
@@ -250,15 +264,29 @@ module Sidekiq
|
|
250
264
|
end
|
251
265
|
end
|
252
266
|
|
267
|
+
def format_memory(rss_kb)
|
268
|
+
return "0" if rss_kb.nil? || rss_kb == 0
|
269
|
+
|
270
|
+
if rss_kb < 100_000
|
271
|
+
"#{number_with_delimiter(rss_kb)} KB"
|
272
|
+
elsif rss_kb < 10_000_000
|
273
|
+
"#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
|
274
|
+
else
|
275
|
+
"#{number_with_delimiter((rss_kb / (1024.0 * 1024.0)).round(1))} GB"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
253
279
|
def number_with_delimiter(number)
|
280
|
+
return "" if number.nil?
|
281
|
+
|
254
282
|
begin
|
255
283
|
Float(number)
|
256
284
|
rescue ArgumentError, TypeError
|
257
285
|
return number
|
258
286
|
end
|
259
287
|
|
260
|
-
options = {delimiter:
|
261
|
-
parts = number.to_s.to_str.split(
|
288
|
+
options = {delimiter: ",", separator: "."}
|
289
|
+
parts = number.to_s.to_str.split(".")
|
262
290
|
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
|
263
291
|
parts.join(options[:separator])
|
264
292
|
end
|
@@ -266,8 +294,8 @@ module Sidekiq
|
|
266
294
|
def h(text)
|
267
295
|
::Rack::Utils.escape_html(text)
|
268
296
|
rescue ArgumentError => e
|
269
|
-
raise unless e.message.eql?(
|
270
|
-
text.encode!(
|
297
|
+
raise unless e.message.eql?("invalid byte sequence in UTF-8")
|
298
|
+
text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
|
271
299
|
retry
|
272
300
|
end
|
273
301
|
|
@@ -284,7 +312,7 @@ module Sidekiq
|
|
284
312
|
end
|
285
313
|
|
286
314
|
def environment_title_prefix
|
287
|
-
environment = Sidekiq.options[:environment] || ENV[
|
315
|
+
environment = Sidekiq.options[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
288
316
|
|
289
317
|
"[#{environment.upcase}] " unless environment == "production"
|
290
318
|
end
|
@@ -294,30 +322,30 @@ module Sidekiq
|
|
294
322
|
end
|
295
323
|
|
296
324
|
def server_utc_time
|
297
|
-
Time.now.utc.strftime(
|
325
|
+
Time.now.utc.strftime("%H:%M:%S UTC")
|
298
326
|
end
|
299
327
|
|
300
328
|
def redis_connection_and_namespace
|
301
329
|
@redis_connection_and_namespace ||= begin
|
302
|
-
namespace_suffix = namespace
|
330
|
+
namespace_suffix = namespace.nil? ? "" : "##{namespace}"
|
303
331
|
"#{redis_connection}#{namespace_suffix}"
|
304
332
|
end
|
305
333
|
end
|
306
334
|
|
307
335
|
def retry_or_delete_or_kill(job, params)
|
308
|
-
if params[
|
336
|
+
if params["retry"]
|
309
337
|
job.retry
|
310
|
-
elsif params[
|
338
|
+
elsif params["delete"]
|
311
339
|
job.delete
|
312
|
-
elsif params[
|
340
|
+
elsif params["kill"]
|
313
341
|
job.kill
|
314
342
|
end
|
315
343
|
end
|
316
344
|
|
317
345
|
def delete_or_add_queue(job, params)
|
318
|
-
if params[
|
346
|
+
if params["delete"]
|
319
347
|
job.delete
|
320
|
-
elsif params[
|
348
|
+
elsif params["add_to_queue"]
|
321
349
|
job.add_to_queue
|
322
350
|
end
|
323
351
|
end
|