sidekiq 4.2.10 → 6.1.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 +5 -5
- data/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
- data/.github/workflows/ci.yml +41 -0
- data/.gitignore +2 -1
- data/.standard.yml +20 -0
- data/5.0-Upgrade.md +56 -0
- data/6.0-Upgrade.md +72 -0
- data/COMM-LICENSE +12 -10
- data/Changes.md +354 -1
- data/Ent-2.0-Upgrade.md +37 -0
- data/Ent-Changes.md +111 -3
- data/Gemfile +16 -21
- data/Gemfile.lock +192 -0
- data/LICENSE +1 -1
- data/Pro-4.0-Upgrade.md +35 -0
- data/Pro-5.0-Upgrade.md +25 -0
- data/Pro-Changes.md +181 -4
- data/README.md +19 -33
- data/Rakefile +6 -8
- data/bin/sidekiq +26 -2
- data/bin/sidekiqload +37 -34
- data/bin/sidekiqmon +8 -0
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +1 -1
- data/lib/generators/sidekiq/worker_generator.rb +21 -13
- data/lib/sidekiq.rb +86 -61
- data/lib/sidekiq/api.rb +320 -209
- data/lib/sidekiq/cli.rb +207 -217
- data/lib/sidekiq/client.rb +78 -51
- data/lib/sidekiq/delay.rb +41 -0
- data/lib/sidekiq/exception_handler.rb +12 -16
- 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 +10 -4
- data/lib/sidekiq/fetch.rb +29 -30
- data/lib/sidekiq/job_logger.rb +63 -0
- data/lib/sidekiq/job_retry.rb +262 -0
- data/lib/sidekiq/launcher.rb +102 -69
- data/lib/sidekiq/logger.rb +165 -0
- data/lib/sidekiq/manager.rb +16 -19
- data/lib/sidekiq/middleware/chain.rb +15 -5
- 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 +161 -82
- data/lib/sidekiq/rails.rb +27 -100
- data/lib/sidekiq/redis_connection.rb +60 -20
- data/lib/sidekiq/scheduled.rb +61 -35
- data/lib/sidekiq/sd_notify.rb +149 -0
- data/lib/sidekiq/systemd.rb +24 -0
- data/lib/sidekiq/testing.rb +48 -28
- data/lib/sidekiq/testing/inline.rb +2 -1
- data/lib/sidekiq/util.rb +20 -16
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web.rb +57 -57
- data/lib/sidekiq/web/action.rb +14 -14
- data/lib/sidekiq/web/application.rb +103 -84
- data/lib/sidekiq/web/csrf_protection.rb +158 -0
- data/lib/sidekiq/web/helpers.rb +126 -71
- data/lib/sidekiq/web/router.rb +18 -17
- data/lib/sidekiq/worker.rb +164 -41
- data/sidekiq.gemspec +15 -27
- data/web/assets/javascripts/application.js +25 -27
- data/web/assets/javascripts/dashboard.js +33 -37
- data/web/assets/stylesheets/application-dark.css +143 -0
- data/web/assets/stylesheets/application-rtl.css +246 -0
- data/web/assets/stylesheets/application.css +385 -10
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +81 -0
- data/web/locales/de.yml +14 -2
- data/web/locales/en.yml +4 -0
- data/web/locales/es.yml +4 -3
- data/web/locales/fa.yml +1 -0
- data/web/locales/fr.yml +2 -2
- data/web/locales/he.yml +79 -0
- data/web/locales/ja.yml +9 -4
- data/web/locales/lt.yml +83 -0
- data/web/locales/pl.yml +4 -4
- data/web/locales/ru.yml +4 -0
- data/web/locales/ur.yml +80 -0
- data/web/locales/vi.yml +83 -0
- data/web/views/_footer.erb +5 -2
- data/web/views/_job_info.erb +2 -1
- data/web/views/_nav.erb +4 -18
- data/web/views/_paging.erb +1 -1
- data/web/views/busy.erb +15 -8
- data/web/views/dashboard.erb +1 -1
- data/web/views/dead.erb +2 -2
- data/web/views/layout.erb +12 -2
- data/web/views/morgue.erb +9 -6
- data/web/views/queue.erb +18 -8
- data/web/views/queues.erb +11 -1
- data/web/views/retries.erb +14 -7
- data/web/views/retry.erb +2 -2
- data/web/views/scheduled.erb +7 -4
- metadata +41 -188
- data/.github/issue_template.md +0 -9
- data/.travis.yml +0 -18
- data/bin/sidekiqctl +0 -99
- data/lib/sidekiq/core_ext.rb +0 -119
- data/lib/sidekiq/logging.rb +0 -106
- data/lib/sidekiq/middleware/server/active_record.rb +0 -13
- data/lib/sidekiq/middleware/server/logging.rb +0 -31
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -1,35 +1,41 @@
|
|
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
|
-
|
12
|
+
@strings ||= {}
|
13
|
+
@strings[lang] ||= begin
|
13
14
|
# Allow sidekiq-web extensions to add locale paths
|
14
15
|
# so extensions can be localized
|
15
16
|
settings.locales.each_with_object({}) do |path, global|
|
16
17
|
find_locale_files(lang).each do |file|
|
17
18
|
strs = YAML.load(File.open(file))
|
18
|
-
global.
|
19
|
+
global.merge!(strs[lang])
|
19
20
|
end
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
25
|
def clear_caches
|
25
|
-
|
26
|
-
|
26
|
+
@strings = nil
|
27
|
+
@locale_files = nil
|
28
|
+
@available_locales = nil
|
27
29
|
end
|
28
30
|
|
29
31
|
def locale_files
|
30
|
-
|
32
|
+
@locale_files ||= settings.locales.flat_map { |path|
|
31
33
|
Dir["#{path}/*.yml"]
|
32
|
-
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def available_locales
|
38
|
+
@available_locales ||= locale_files.map { |path| File.basename(path, ".yml") }.uniq
|
33
39
|
end
|
34
40
|
|
35
41
|
def find_locale_files(lang)
|
@@ -58,41 +64,75 @@ module Sidekiq
|
|
58
64
|
end
|
59
65
|
|
60
66
|
def poll_path
|
61
|
-
if current_path !=
|
62
|
-
root_path + current_path
|
67
|
+
if current_path != "" && params["poll"]
|
68
|
+
path = root_path + current_path
|
69
|
+
query_string = to_query_string(params.slice(*params.keys - %w[page poll]))
|
70
|
+
path += "?#{query_string}" unless query_string.empty?
|
71
|
+
path
|
63
72
|
else
|
64
73
|
""
|
65
74
|
end
|
66
75
|
end
|
67
76
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
77
|
+
def text_direction
|
78
|
+
get_locale["TextDirection"] || "ltr"
|
79
|
+
end
|
80
|
+
|
81
|
+
def rtl?
|
82
|
+
text_direction == "rtl"
|
83
|
+
end
|
84
|
+
|
85
|
+
# See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
86
|
+
def user_preferred_languages
|
87
|
+
languages = env["HTTP_ACCEPT_LANGUAGE"]
|
88
|
+
languages.to_s.downcase.gsub(/\s+/, "").split(",").map { |language|
|
89
|
+
locale, quality = language.split(";q=", 2)
|
90
|
+
locale = nil if locale == "*" # Ignore wildcards
|
91
|
+
quality = quality ? quality.to_f : 1.0
|
92
|
+
[locale, quality]
|
93
|
+
}.sort { |(_, left), (_, right)|
|
94
|
+
right <=> left
|
95
|
+
}.map(&:first).compact
|
96
|
+
end
|
97
|
+
|
98
|
+
# 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"
|
99
|
+
# this method will try to best match the available locales to the user's preferred languages.
|
100
|
+
#
|
101
|
+
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
72
102
|
def locale
|
73
103
|
@locale ||= begin
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
104
|
+
matched_locale = user_preferred_languages.map { |preferred|
|
105
|
+
preferred_language = preferred.split("-", 2).first
|
106
|
+
|
107
|
+
lang_group = available_locales.select { |available|
|
108
|
+
preferred_language == available.split("-", 2).first
|
109
|
+
}
|
110
|
+
|
111
|
+
lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
|
112
|
+
}.compact.first
|
113
|
+
|
114
|
+
matched_locale || "en"
|
82
115
|
end
|
83
116
|
end
|
84
117
|
|
118
|
+
# within is used by Sidekiq Pro
|
119
|
+
def display_tags(job, within = nil)
|
120
|
+
job.tags.map { |tag|
|
121
|
+
"<span class='jobtag label label-info'>#{::Rack::Utils.escape_html(tag)}</span>"
|
122
|
+
}.join(" ")
|
123
|
+
end
|
124
|
+
|
85
125
|
# mperham/sidekiq#3243
|
86
126
|
def unfiltered?
|
87
|
-
yield unless env[
|
127
|
+
yield unless env["PATH_INFO"].start_with?("/filter/")
|
88
128
|
end
|
89
129
|
|
90
130
|
def get_locale
|
91
131
|
strings(locale)
|
92
132
|
end
|
93
133
|
|
94
|
-
def t(msg, options={})
|
95
|
-
string = get_locale[msg] || msg
|
134
|
+
def t(msg, options = {})
|
135
|
+
string = get_locale[msg] || strings("en")[msg] || msg
|
96
136
|
if options.empty?
|
97
137
|
string
|
98
138
|
else
|
@@ -100,6 +140,10 @@ module Sidekiq
|
|
100
140
|
end
|
101
141
|
end
|
102
142
|
|
143
|
+
def sort_direction_label
|
144
|
+
params[:direction] == "asc" ? "↑" : "↓"
|
145
|
+
end
|
146
|
+
|
103
147
|
def workers
|
104
148
|
@workers ||= Sidekiq::Workers.new
|
105
149
|
end
|
@@ -112,18 +156,15 @@ module Sidekiq
|
|
112
156
|
@stats ||= Sidekiq::Stats.new
|
113
157
|
end
|
114
158
|
|
115
|
-
def retries_with_score(score)
|
116
|
-
Sidekiq.redis do |conn|
|
117
|
-
conn.zrangebyscore('retry', score, score)
|
118
|
-
end.map { |msg| Sidekiq.load_json(msg) }
|
119
|
-
end
|
120
|
-
|
121
159
|
def redis_connection
|
122
|
-
Sidekiq.redis
|
160
|
+
Sidekiq.redis do |conn|
|
161
|
+
c = conn.connection
|
162
|
+
"redis://#{c[:location]}/#{c[:db]}"
|
163
|
+
end
|
123
164
|
end
|
124
165
|
|
125
166
|
def namespace
|
126
|
-
|
167
|
+
@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
|
127
168
|
end
|
128
169
|
|
129
170
|
def redis_info
|
@@ -131,39 +172,44 @@ module Sidekiq
|
|
131
172
|
end
|
132
173
|
|
133
174
|
def root_path
|
134
|
-
"#{env[
|
175
|
+
"#{env["SCRIPT_NAME"]}/"
|
135
176
|
end
|
136
177
|
|
137
178
|
def current_path
|
138
|
-
@current_path ||= request.path_info.gsub(/^\//,
|
179
|
+
@current_path ||= request.path_info.gsub(/^\//, "")
|
139
180
|
end
|
140
181
|
|
141
182
|
def current_status
|
142
|
-
workers.size == 0 ?
|
183
|
+
workers.size == 0 ? "idle" : "active"
|
143
184
|
end
|
144
185
|
|
145
186
|
def relative_time(time)
|
146
187
|
stamp = time.getutc.iso8601
|
147
|
-
%
|
188
|
+
%(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
|
148
189
|
end
|
149
190
|
|
150
191
|
def job_params(job, score)
|
151
|
-
"#{score}-#{job[
|
192
|
+
"#{score}-#{job["jid"]}"
|
152
193
|
end
|
153
194
|
|
154
195
|
def parse_params(params)
|
155
|
-
score, jid = params.split("-")
|
196
|
+
score, jid = params.split("-", 2)
|
156
197
|
[score.to_f, jid]
|
157
198
|
end
|
158
199
|
|
159
|
-
SAFE_QPARAMS = %w
|
200
|
+
SAFE_QPARAMS = %w[page poll direction]
|
160
201
|
|
161
202
|
# Merge options with current params, filter safe params, and stringify to query string
|
162
203
|
def qparams(options)
|
163
|
-
|
164
|
-
|
204
|
+
stringified_options = options.transform_keys(&:to_s)
|
205
|
+
|
206
|
+
to_query_string(params.merge(stringified_options))
|
207
|
+
end
|
208
|
+
|
209
|
+
def to_query_string(params)
|
210
|
+
params.map { |key, value|
|
165
211
|
SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
|
166
|
-
|
212
|
+
}.compact.join("&")
|
167
213
|
end
|
168
214
|
|
169
215
|
def truncate(text, truncate_after_chars = 2000)
|
@@ -171,33 +217,38 @@ module Sidekiq
|
|
171
217
|
end
|
172
218
|
|
173
219
|
def display_args(args, truncate_after_chars = 2000)
|
174
|
-
args
|
175
|
-
|
176
|
-
|
220
|
+
return "Invalid job payload, args is nil" if args.nil?
|
221
|
+
return "Invalid job payload, args must be an Array, not #{args.class.name}" unless args.is_a?(Array)
|
222
|
+
|
223
|
+
begin
|
224
|
+
args.map { |arg|
|
225
|
+
h(truncate(to_display(arg), truncate_after_chars))
|
226
|
+
}.join(", ")
|
227
|
+
rescue
|
228
|
+
"Illegal job arguments: #{h args.inspect}"
|
229
|
+
end
|
177
230
|
end
|
178
231
|
|
179
232
|
def csrf_tag
|
180
|
-
"<input type='hidden' name='authenticity_token' value='#{
|
233
|
+
"<input type='hidden' name='authenticity_token' value='#{env[:csrf_token]}'/>"
|
181
234
|
end
|
182
235
|
|
183
236
|
def to_display(arg)
|
237
|
+
arg.inspect
|
238
|
+
rescue
|
184
239
|
begin
|
185
|
-
arg.
|
186
|
-
rescue
|
187
|
-
|
188
|
-
arg.to_s
|
189
|
-
rescue => ex
|
190
|
-
"Cannot display argument: [#{ex.class.name}] #{ex.message}"
|
191
|
-
end
|
240
|
+
arg.to_s
|
241
|
+
rescue => ex
|
242
|
+
"Cannot display argument: [#{ex.class.name}] #{ex.message}"
|
192
243
|
end
|
193
244
|
end
|
194
245
|
|
195
|
-
RETRY_JOB_KEYS = Set.new(%w
|
246
|
+
RETRY_JOB_KEYS = Set.new(%w[
|
196
247
|
queue class args retry_count retried_at failed_at
|
197
248
|
jid error_message error_class backtrace
|
198
249
|
error_backtrace enqueued_at retry wrapped
|
199
|
-
created_at
|
200
|
-
)
|
250
|
+
created_at tags
|
251
|
+
])
|
201
252
|
|
202
253
|
def retry_extra_items(retry_job)
|
203
254
|
@retry_extra_items ||= {}.tap do |extra|
|
@@ -214,8 +265,8 @@ module Sidekiq
|
|
214
265
|
return number
|
215
266
|
end
|
216
267
|
|
217
|
-
options = {delimiter:
|
218
|
-
parts = number.to_s.to_str.split(
|
268
|
+
options = {delimiter: ",", separator: "."}
|
269
|
+
parts = number.to_s.to_str.split(".")
|
219
270
|
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
|
220
271
|
parts.join(options[:separator])
|
221
272
|
end
|
@@ -223,8 +274,8 @@ module Sidekiq
|
|
223
274
|
def h(text)
|
224
275
|
::Rack::Utils.escape_html(text)
|
225
276
|
rescue ArgumentError => e
|
226
|
-
raise unless e.message.eql?(
|
227
|
-
text.encode!(
|
277
|
+
raise unless e.message.eql?("invalid byte sequence in UTF-8")
|
278
|
+
text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
|
228
279
|
retry
|
229
280
|
end
|
230
281
|
|
@@ -241,7 +292,7 @@ module Sidekiq
|
|
241
292
|
end
|
242
293
|
|
243
294
|
def environment_title_prefix
|
244
|
-
environment = Sidekiq.options[:environment] || ENV[
|
295
|
+
environment = Sidekiq.options[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
245
296
|
|
246
297
|
"[#{environment.upcase}] " unless environment == "production"
|
247
298
|
end
|
@@ -250,27 +301,31 @@ module Sidekiq
|
|
250
301
|
"Sidekiq v#{Sidekiq::VERSION}"
|
251
302
|
end
|
252
303
|
|
304
|
+
def server_utc_time
|
305
|
+
Time.now.utc.strftime("%H:%M:%S UTC")
|
306
|
+
end
|
307
|
+
|
253
308
|
def redis_connection_and_namespace
|
254
309
|
@redis_connection_and_namespace ||= begin
|
255
|
-
namespace_suffix = namespace
|
310
|
+
namespace_suffix = namespace.nil? ? "" : "##{namespace}"
|
256
311
|
"#{redis_connection}#{namespace_suffix}"
|
257
312
|
end
|
258
313
|
end
|
259
314
|
|
260
315
|
def retry_or_delete_or_kill(job, params)
|
261
|
-
if params[
|
316
|
+
if params["retry"]
|
262
317
|
job.retry
|
263
|
-
elsif params[
|
318
|
+
elsif params["delete"]
|
264
319
|
job.delete
|
265
|
-
elsif params[
|
320
|
+
elsif params["kill"]
|
266
321
|
job.kill
|
267
322
|
end
|
268
323
|
end
|
269
324
|
|
270
325
|
def delete_or_add_queue(job, params)
|
271
|
-
if params[
|
326
|
+
if params["delete"]
|
272
327
|
job.delete
|
273
|
-
elsif params[
|
328
|
+
elsif params["add_to_queue"]
|
274
329
|
job.add_to_queue
|
275
330
|
end
|
276
331
|
end
|
data/lib/sidekiq/web/router.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require "rack"
|
3
4
|
|
4
5
|
module Sidekiq
|
5
6
|
module WebRouter
|
6
|
-
GET =
|
7
|
-
DELETE =
|
8
|
-
POST =
|
9
|
-
PUT =
|
10
|
-
PATCH =
|
11
|
-
HEAD =
|
7
|
+
GET = "GET"
|
8
|
+
DELETE = "DELETE"
|
9
|
+
POST = "POST"
|
10
|
+
PUT = "PUT"
|
11
|
+
PATCH = "PATCH"
|
12
|
+
HEAD = "HEAD"
|
12
13
|
|
13
|
-
ROUTE_PARAMS =
|
14
|
-
REQUEST_METHOD =
|
15
|
-
PATH_INFO =
|
14
|
+
ROUTE_PARAMS = "rack.route_params"
|
15
|
+
REQUEST_METHOD = "REQUEST_METHOD"
|
16
|
+
PATH_INFO = "PATH_INFO"
|
16
17
|
|
17
18
|
def get(path, &block)
|
18
19
|
route(GET, path, &block)
|
@@ -35,7 +36,7 @@ module Sidekiq
|
|
35
36
|
end
|
36
37
|
|
37
38
|
def route(method, path, &block)
|
38
|
-
@routes ||= {
|
39
|
+
@routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []}
|
39
40
|
|
40
41
|
@routes[method] << WebRoute.new(method, path, block)
|
41
42
|
@routes[HEAD] << WebRoute.new(method, path, block) if method == GET
|
@@ -50,7 +51,8 @@ module Sidekiq
|
|
50
51
|
path_info = "/" if path_info == ""
|
51
52
|
|
52
53
|
@routes[request_method].each do |route|
|
53
|
-
|
54
|
+
params = route.match(request_method, path_info)
|
55
|
+
if params
|
54
56
|
env[ROUTE_PARAMS] = params
|
55
57
|
|
56
58
|
return WebAction.new(env, route.block)
|
@@ -64,7 +66,7 @@ module Sidekiq
|
|
64
66
|
class WebRoute
|
65
67
|
attr_accessor :request_method, :pattern, :block, :name
|
66
68
|
|
67
|
-
NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([
|
69
|
+
NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^.:$\/]+)/
|
68
70
|
|
69
71
|
def initialize(request_method, pattern, block)
|
70
72
|
@request_method = request_method
|
@@ -77,7 +79,7 @@ module Sidekiq
|
|
77
79
|
end
|
78
80
|
|
79
81
|
def compile
|
80
|
-
if pattern.match(NAMED_SEGMENTS_PATTERN)
|
82
|
+
if pattern.match?(NAMED_SEGMENTS_PATTERN)
|
81
83
|
p = pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)')
|
82
84
|
|
83
85
|
Regexp.new("\\A#{p}\\Z")
|
@@ -91,9 +93,8 @@ module Sidekiq
|
|
91
93
|
when String
|
92
94
|
{} if path == matcher
|
93
95
|
else
|
94
|
-
|
95
|
-
|
96
|
-
end
|
96
|
+
path_match = path.match(matcher)
|
97
|
+
path_match&.named_captures&.transform_keys(&:to_sym)
|
97
98
|
end
|
98
99
|
end
|
99
100
|
end
|
data/lib/sidekiq/worker.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'sidekiq/client'
|
3
|
-
require 'sidekiq/core_ext'
|
4
2
|
|
5
|
-
|
3
|
+
require "sidekiq/client"
|
6
4
|
|
5
|
+
module Sidekiq
|
7
6
|
##
|
8
7
|
# Include this module in your worker class and you can easily create
|
9
8
|
# asynchronous jobs:
|
10
9
|
#
|
11
|
-
#
|
12
|
-
#
|
10
|
+
# class HardWorker
|
11
|
+
# include Sidekiq::Worker
|
13
12
|
#
|
14
|
-
#
|
15
|
-
#
|
13
|
+
# def perform(*args)
|
14
|
+
# # do some work
|
15
|
+
# end
|
16
16
|
# end
|
17
|
-
# end
|
18
17
|
#
|
19
18
|
# Then in your Rails app, you can do this:
|
20
19
|
#
|
@@ -22,23 +21,165 @@ module Sidekiq
|
|
22
21
|
#
|
23
22
|
# Note that perform_async is a class method, perform is an instance method.
|
24
23
|
module Worker
|
24
|
+
##
|
25
|
+
# The Options module is extracted so we can include it in ActiveJob::Base
|
26
|
+
# and allow native AJs to configure Sidekiq features/internals.
|
27
|
+
module Options
|
28
|
+
def self.included(base)
|
29
|
+
base.extend(ClassMethods)
|
30
|
+
base.sidekiq_class_attribute :sidekiq_options_hash
|
31
|
+
base.sidekiq_class_attribute :sidekiq_retry_in_block
|
32
|
+
base.sidekiq_class_attribute :sidekiq_retries_exhausted_block
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
ACCESSOR_MUTEX = Mutex.new
|
37
|
+
|
38
|
+
##
|
39
|
+
# Allows customization for this type of Worker.
|
40
|
+
# Legal options:
|
41
|
+
#
|
42
|
+
# queue - name of queue to use for this job type, default *default*
|
43
|
+
# retry - enable retries for this Worker in case of error during execution,
|
44
|
+
# *true* to use the default or *Integer* count
|
45
|
+
# backtrace - whether to save any error backtrace in the retry payload to display in web UI,
|
46
|
+
# can be true, false or an integer number of lines to save, default *false*
|
47
|
+
#
|
48
|
+
# In practice, any option is allowed. This is the main mechanism to configure the
|
49
|
+
# options for a specific job.
|
50
|
+
def sidekiq_options(opts = {})
|
51
|
+
opts = opts.transform_keys(&:to_s) # stringify
|
52
|
+
self.sidekiq_options_hash = get_sidekiq_options.merge(opts)
|
53
|
+
end
|
54
|
+
|
55
|
+
def sidekiq_retry_in(&block)
|
56
|
+
self.sidekiq_retry_in_block = block
|
57
|
+
end
|
58
|
+
|
59
|
+
def sidekiq_retries_exhausted(&block)
|
60
|
+
self.sidekiq_retries_exhausted_block = block
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_sidekiq_options # :nodoc:
|
64
|
+
self.sidekiq_options_hash ||= Sidekiq.default_worker_options
|
65
|
+
end
|
66
|
+
|
67
|
+
def sidekiq_class_attribute(*attrs)
|
68
|
+
instance_reader = true
|
69
|
+
instance_writer = true
|
70
|
+
|
71
|
+
attrs.each do |name|
|
72
|
+
synchronized_getter = "__synchronized_#{name}"
|
73
|
+
|
74
|
+
singleton_class.instance_eval do
|
75
|
+
undef_method(name) if method_defined?(name) || private_method_defined?(name)
|
76
|
+
end
|
77
|
+
|
78
|
+
define_singleton_method(synchronized_getter) { nil }
|
79
|
+
singleton_class.class_eval do
|
80
|
+
private(synchronized_getter)
|
81
|
+
end
|
82
|
+
|
83
|
+
define_singleton_method(name) { ACCESSOR_MUTEX.synchronize { send synchronized_getter } }
|
84
|
+
|
85
|
+
ivar = "@#{name}"
|
86
|
+
|
87
|
+
singleton_class.instance_eval do
|
88
|
+
m = "#{name}="
|
89
|
+
undef_method(m) if method_defined?(m) || private_method_defined?(m)
|
90
|
+
end
|
91
|
+
define_singleton_method("#{name}=") do |val|
|
92
|
+
singleton_class.class_eval do
|
93
|
+
ACCESSOR_MUTEX.synchronize do
|
94
|
+
undef_method(synchronized_getter) if method_defined?(synchronized_getter) || private_method_defined?(synchronized_getter)
|
95
|
+
define_method(synchronized_getter) { val }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
if singleton_class?
|
100
|
+
class_eval do
|
101
|
+
undef_method(name) if method_defined?(name) || private_method_defined?(name)
|
102
|
+
define_method(name) do
|
103
|
+
if instance_variable_defined? ivar
|
104
|
+
instance_variable_get ivar
|
105
|
+
else
|
106
|
+
singleton_class.send name
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
val
|
112
|
+
end
|
113
|
+
|
114
|
+
if instance_reader
|
115
|
+
undef_method(name) if method_defined?(name) || private_method_defined?(name)
|
116
|
+
define_method(name) do
|
117
|
+
if instance_variable_defined?(ivar)
|
118
|
+
instance_variable_get ivar
|
119
|
+
else
|
120
|
+
self.class.public_send name
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
if instance_writer
|
126
|
+
m = "#{name}="
|
127
|
+
undef_method(m) if method_defined?(m) || private_method_defined?(m)
|
128
|
+
attr_writer name
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
25
135
|
attr_accessor :jid
|
26
136
|
|
27
137
|
def self.included(base)
|
28
|
-
raise ArgumentError, "
|
138
|
+
raise ArgumentError, "Sidekiq::Worker cannot be included in an ActiveJob: #{base.name}" if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
|
29
139
|
|
140
|
+
base.include(Options)
|
30
141
|
base.extend(ClassMethods)
|
31
|
-
base.class_attribute :sidekiq_options_hash
|
32
|
-
base.class_attribute :sidekiq_retry_in_block
|
33
|
-
base.class_attribute :sidekiq_retries_exhausted_block
|
34
142
|
end
|
35
143
|
|
36
144
|
def logger
|
37
145
|
Sidekiq.logger
|
38
146
|
end
|
39
147
|
|
40
|
-
|
148
|
+
# This helper class encapsulates the set options for `set`, e.g.
|
149
|
+
#
|
150
|
+
# SomeWorker.set(queue: 'foo').perform_async(....)
|
151
|
+
#
|
152
|
+
class Setter
|
153
|
+
def initialize(klass, opts)
|
154
|
+
@klass = klass
|
155
|
+
@opts = opts
|
156
|
+
end
|
157
|
+
|
158
|
+
def set(options)
|
159
|
+
@opts.merge!(options)
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
163
|
+
def perform_async(*args)
|
164
|
+
@klass.client_push(@opts.merge("args" => args, "class" => @klass))
|
165
|
+
end
|
166
|
+
|
167
|
+
# +interval+ must be a timestamp, numeric or something that acts
|
168
|
+
# numeric (like an activesupport time interval).
|
169
|
+
def perform_in(interval, *args)
|
170
|
+
int = interval.to_f
|
171
|
+
now = Time.now.to_f
|
172
|
+
ts = (int < 1_000_000_000 ? now + int : int)
|
173
|
+
|
174
|
+
payload = @opts.merge("class" => @klass, "args" => args)
|
175
|
+
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
176
|
+
payload["at"] = ts if ts > now
|
177
|
+
@klass.client_push(payload)
|
178
|
+
end
|
179
|
+
alias_method :perform_at, :perform_in
|
180
|
+
end
|
41
181
|
|
182
|
+
module ClassMethods
|
42
183
|
def delay(*args)
|
43
184
|
raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
|
44
185
|
end
|
@@ -52,12 +193,11 @@ module Sidekiq
|
|
52
193
|
end
|
53
194
|
|
54
195
|
def set(options)
|
55
|
-
|
56
|
-
self
|
196
|
+
Setter.new(self, options)
|
57
197
|
end
|
58
198
|
|
59
199
|
def perform_async(*args)
|
60
|
-
client_push(
|
200
|
+
client_push("class" => self, "args" => args)
|
61
201
|
end
|
62
202
|
|
63
203
|
# +interval+ must be a timestamp, numeric or something that acts
|
@@ -67,10 +207,10 @@ module Sidekiq
|
|
67
207
|
now = Time.now.to_f
|
68
208
|
ts = (int < 1_000_000_000 ? now + int : int)
|
69
209
|
|
70
|
-
item = {
|
210
|
+
item = {"class" => self, "args" => args}
|
71
211
|
|
72
212
|
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
73
|
-
item
|
213
|
+
item["at"] = ts if ts > now
|
74
214
|
|
75
215
|
client_push(item)
|
76
216
|
end
|
@@ -89,33 +229,16 @@ module Sidekiq
|
|
89
229
|
#
|
90
230
|
# In practice, any option is allowed. This is the main mechanism to configure the
|
91
231
|
# options for a specific job.
|
92
|
-
def sidekiq_options(opts={})
|
93
|
-
|
94
|
-
end
|
95
|
-
|
96
|
-
def sidekiq_retry_in(&block)
|
97
|
-
self.sidekiq_retry_in_block = block
|
98
|
-
end
|
99
|
-
|
100
|
-
def sidekiq_retries_exhausted(&block)
|
101
|
-
self.sidekiq_retries_exhausted_block = block
|
102
|
-
end
|
103
|
-
|
104
|
-
def get_sidekiq_options # :nodoc:
|
105
|
-
self.sidekiq_options_hash ||= Sidekiq.default_worker_options
|
232
|
+
def sidekiq_options(opts = {})
|
233
|
+
super
|
106
234
|
end
|
107
235
|
|
108
236
|
def client_push(item) # :nodoc:
|
109
|
-
pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options[
|
110
|
-
|
111
|
-
x, Thread.current[:sidekiq_worker_set] = Thread.current[:sidekiq_worker_set], nil
|
112
|
-
x.stringify_keys.merge(item.stringify_keys)
|
113
|
-
else
|
114
|
-
item.stringify_keys
|
115
|
-
end
|
116
|
-
Sidekiq::Client.new(pool).push(hash)
|
117
|
-
end
|
237
|
+
pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
|
238
|
+
stringified_item = item.transform_keys(&:to_s)
|
118
239
|
|
240
|
+
Sidekiq::Client.new(pool).push(stringified_item)
|
241
|
+
end
|
119
242
|
end
|
120
243
|
end
|
121
244
|
end
|