sidekiq 5.0.0 → 6.0.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 +5 -5
- data/.circleci/config.yml +61 -0
- data/.github/issue_template.md +3 -1
- data/.gitignore +1 -1
- data/.standard.yml +20 -0
- data/6.0-Upgrade.md +70 -0
- data/COMM-LICENSE +12 -10
- data/Changes.md +169 -1
- data/Ent-2.0-Upgrade.md +37 -0
- data/Ent-Changes.md +76 -0
- data/Gemfile +16 -21
- data/Gemfile.lock +196 -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 +137 -1
- data/README.md +18 -30
- data/Rakefile +6 -8
- data/bin/sidekiqload +28 -24
- data/bin/sidekiqmon +9 -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 +12 -14
- data/lib/sidekiq.rb +69 -49
- data/lib/sidekiq/api.rb +216 -160
- data/lib/sidekiq/cli.rb +174 -207
- data/lib/sidekiq/client.rb +55 -51
- data/lib/sidekiq/delay.rb +24 -4
- data/lib/sidekiq/exception_handler.rb +12 -16
- data/lib/sidekiq/extensions/action_mailer.rb +10 -20
- data/lib/sidekiq/extensions/active_record.rb +9 -7
- data/lib/sidekiq/extensions/class_methods.rb +9 -7
- data/lib/sidekiq/extensions/generic_proxy.rb +4 -4
- data/lib/sidekiq/fetch.rb +5 -6
- data/lib/sidekiq/job_logger.rb +42 -14
- data/lib/sidekiq/job_retry.rb +71 -57
- data/lib/sidekiq/launcher.rb +74 -60
- data/lib/sidekiq/logger.rb +69 -0
- data/lib/sidekiq/manager.rb +12 -15
- data/lib/sidekiq/middleware/chain.rb +3 -2
- data/lib/sidekiq/middleware/i18n.rb +5 -7
- data/lib/sidekiq/monitor.rb +148 -0
- data/lib/sidekiq/paginator.rb +11 -12
- data/lib/sidekiq/processor.rb +126 -82
- data/lib/sidekiq/rails.rb +24 -32
- data/lib/sidekiq/redis_connection.rb +46 -14
- data/lib/sidekiq/scheduled.rb +50 -25
- data/lib/sidekiq/testing.rb +35 -27
- data/lib/sidekiq/testing/inline.rb +2 -1
- data/lib/sidekiq/util.rb +20 -14
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web.rb +45 -53
- data/lib/sidekiq/web/action.rb +14 -10
- data/lib/sidekiq/web/application.rb +83 -58
- data/lib/sidekiq/web/helpers.rb +105 -67
- data/lib/sidekiq/web/router.rb +18 -15
- data/lib/sidekiq/worker.rb +144 -41
- data/sidekiq.gemspec +16 -27
- data/web/assets/javascripts/application.js +0 -0
- data/web/assets/javascripts/dashboard.js +21 -23
- data/web/assets/stylesheets/application.css +35 -2
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +1 -0
- data/web/locales/en.yml +2 -0
- data/web/locales/es.yml +4 -3
- data/web/locales/ja.yml +7 -4
- data/web/views/_footer.erb +4 -1
- data/web/views/_nav.erb +3 -17
- data/web/views/busy.erb +5 -1
- data/web/views/layout.erb +1 -1
- data/web/views/queue.erb +1 -0
- data/web/views/queues.erb +2 -0
- data/web/views/retries.erb +4 -0
- metadata +25 -171
- 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 -22
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,7 +64,7 @@ module Sidekiq
|
|
58
64
|
end
|
59
65
|
|
60
66
|
def poll_path
|
61
|
-
if current_path !=
|
67
|
+
if current_path != "" && params["poll"]
|
62
68
|
root_path + current_path
|
63
69
|
else
|
64
70
|
""
|
@@ -66,41 +72,57 @@ module Sidekiq
|
|
66
72
|
end
|
67
73
|
|
68
74
|
def text_direction
|
69
|
-
get_locale[
|
75
|
+
get_locale["TextDirection"] || "ltr"
|
70
76
|
end
|
71
77
|
|
72
78
|
def rtl?
|
73
|
-
text_direction ==
|
79
|
+
text_direction == "rtl"
|
80
|
+
end
|
81
|
+
|
82
|
+
# See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
83
|
+
def user_preferred_languages
|
84
|
+
languages = env["HTTP_ACCEPT_LANGUAGE"]
|
85
|
+
languages.to_s.downcase.gsub(/\s+/, "").split(",").map { |language|
|
86
|
+
locale, quality = language.split(";q=", 2)
|
87
|
+
locale = nil if locale == "*" # Ignore wildcards
|
88
|
+
quality = quality ? quality.to_f : 1.0
|
89
|
+
[locale, quality]
|
90
|
+
}.sort { |(_, left), (_, right)|
|
91
|
+
right <=> left
|
92
|
+
}.map(&:first).compact
|
74
93
|
end
|
75
94
|
|
76
|
-
# Given
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
95
|
+
# 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"
|
96
|
+
# this method will try to best match the available locales to the user's preferred languages.
|
97
|
+
#
|
98
|
+
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
80
99
|
def locale
|
81
100
|
@locale ||= begin
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
101
|
+
matched_locale = user_preferred_languages.map { |preferred|
|
102
|
+
preferred_language = preferred.split("-", 2).first
|
103
|
+
|
104
|
+
lang_group = available_locales.select { |available|
|
105
|
+
preferred_language == available.split("-", 2).first
|
106
|
+
}
|
107
|
+
|
108
|
+
lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
|
109
|
+
}.compact.first
|
110
|
+
|
111
|
+
matched_locale || "en"
|
90
112
|
end
|
91
113
|
end
|
92
114
|
|
93
115
|
# mperham/sidekiq#3243
|
94
116
|
def unfiltered?
|
95
|
-
yield unless env[
|
117
|
+
yield unless env["PATH_INFO"].start_with?("/filter/")
|
96
118
|
end
|
97
119
|
|
98
120
|
def get_locale
|
99
121
|
strings(locale)
|
100
122
|
end
|
101
123
|
|
102
|
-
def t(msg, options={})
|
103
|
-
string = get_locale[msg] || msg
|
124
|
+
def t(msg, options = {})
|
125
|
+
string = get_locale[msg] || strings("en")[msg] || msg
|
104
126
|
if options.empty?
|
105
127
|
string
|
106
128
|
else
|
@@ -121,17 +143,20 @@ module Sidekiq
|
|
121
143
|
end
|
122
144
|
|
123
145
|
def retries_with_score(score)
|
124
|
-
Sidekiq.redis
|
125
|
-
conn.zrangebyscore(
|
126
|
-
|
146
|
+
Sidekiq.redis { |conn|
|
147
|
+
conn.zrangebyscore("retry", score, score)
|
148
|
+
}.map { |msg| Sidekiq.load_json(msg) }
|
127
149
|
end
|
128
150
|
|
129
151
|
def redis_connection
|
130
|
-
Sidekiq.redis
|
152
|
+
Sidekiq.redis do |conn|
|
153
|
+
c = conn.connection
|
154
|
+
"redis://#{c[:location]}/#{c[:db]}"
|
155
|
+
end
|
131
156
|
end
|
132
157
|
|
133
158
|
def namespace
|
134
|
-
|
159
|
+
@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
|
135
160
|
end
|
136
161
|
|
137
162
|
def redis_info
|
@@ -139,39 +164,43 @@ module Sidekiq
|
|
139
164
|
end
|
140
165
|
|
141
166
|
def root_path
|
142
|
-
"#{env[
|
167
|
+
"#{env["SCRIPT_NAME"]}/"
|
143
168
|
end
|
144
169
|
|
145
170
|
def current_path
|
146
|
-
@current_path ||= request.path_info.gsub(/^\//,
|
171
|
+
@current_path ||= request.path_info.gsub(/^\//, "")
|
147
172
|
end
|
148
173
|
|
149
174
|
def current_status
|
150
|
-
workers.size == 0 ?
|
175
|
+
workers.size == 0 ? "idle" : "active"
|
151
176
|
end
|
152
177
|
|
153
178
|
def relative_time(time)
|
154
179
|
stamp = time.getutc.iso8601
|
155
|
-
%
|
180
|
+
%(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
|
156
181
|
end
|
157
182
|
|
158
183
|
def job_params(job, score)
|
159
|
-
"#{score}-#{job[
|
184
|
+
"#{score}-#{job["jid"]}"
|
160
185
|
end
|
161
186
|
|
162
187
|
def parse_params(params)
|
163
|
-
score, jid = params.split("-")
|
188
|
+
score, jid = params.split("-", 2)
|
164
189
|
[score.to_f, jid]
|
165
190
|
end
|
166
191
|
|
167
|
-
SAFE_QPARAMS = %w
|
192
|
+
SAFE_QPARAMS = %w[page poll]
|
168
193
|
|
169
194
|
# Merge options with current params, filter safe params, and stringify to query string
|
170
195
|
def qparams(options)
|
171
|
-
|
172
|
-
|
196
|
+
# stringify
|
197
|
+
options.keys.each do |key|
|
198
|
+
options[key.to_s] = options.delete(key)
|
199
|
+
end
|
200
|
+
|
201
|
+
params.merge(options).map { |key, value|
|
173
202
|
SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
|
174
|
-
|
203
|
+
}.compact.join("&")
|
175
204
|
end
|
176
205
|
|
177
206
|
def truncate(text, truncate_after_chars = 2000)
|
@@ -179,9 +208,16 @@ module Sidekiq
|
|
179
208
|
end
|
180
209
|
|
181
210
|
def display_args(args, truncate_after_chars = 2000)
|
182
|
-
args
|
183
|
-
|
184
|
-
|
211
|
+
return "Invalid job payload, args is nil" if args.nil?
|
212
|
+
return "Invalid job payload, args must be an Array, not #{args.class.name}" unless args.is_a?(Array)
|
213
|
+
|
214
|
+
begin
|
215
|
+
args.map { |arg|
|
216
|
+
h(truncate(to_display(arg), truncate_after_chars))
|
217
|
+
}.join(", ")
|
218
|
+
rescue
|
219
|
+
"Illegal job arguments: #{h args.inspect}"
|
220
|
+
end
|
185
221
|
end
|
186
222
|
|
187
223
|
def csrf_tag
|
@@ -189,23 +225,21 @@ module Sidekiq
|
|
189
225
|
end
|
190
226
|
|
191
227
|
def to_display(arg)
|
228
|
+
arg.inspect
|
229
|
+
rescue
|
192
230
|
begin
|
193
|
-
arg.
|
194
|
-
rescue
|
195
|
-
|
196
|
-
arg.to_s
|
197
|
-
rescue => ex
|
198
|
-
"Cannot display argument: [#{ex.class.name}] #{ex.message}"
|
199
|
-
end
|
231
|
+
arg.to_s
|
232
|
+
rescue => ex
|
233
|
+
"Cannot display argument: [#{ex.class.name}] #{ex.message}"
|
200
234
|
end
|
201
235
|
end
|
202
236
|
|
203
|
-
RETRY_JOB_KEYS = Set.new(%w
|
237
|
+
RETRY_JOB_KEYS = Set.new(%w[
|
204
238
|
queue class args retry_count retried_at failed_at
|
205
239
|
jid error_message error_class backtrace
|
206
240
|
error_backtrace enqueued_at retry wrapped
|
207
241
|
created_at
|
208
|
-
)
|
242
|
+
])
|
209
243
|
|
210
244
|
def retry_extra_items(retry_job)
|
211
245
|
@retry_extra_items ||= {}.tap do |extra|
|
@@ -222,8 +256,8 @@ module Sidekiq
|
|
222
256
|
return number
|
223
257
|
end
|
224
258
|
|
225
|
-
options = {delimiter:
|
226
|
-
parts = number.to_s.to_str.split(
|
259
|
+
options = {delimiter: ",", separator: "."}
|
260
|
+
parts = number.to_s.to_str.split(".")
|
227
261
|
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
|
228
262
|
parts.join(options[:separator])
|
229
263
|
end
|
@@ -231,8 +265,8 @@ module Sidekiq
|
|
231
265
|
def h(text)
|
232
266
|
::Rack::Utils.escape_html(text)
|
233
267
|
rescue ArgumentError => e
|
234
|
-
raise unless e.message.eql?(
|
235
|
-
text.encode!(
|
268
|
+
raise unless e.message.eql?("invalid byte sequence in UTF-8")
|
269
|
+
text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
|
236
270
|
retry
|
237
271
|
end
|
238
272
|
|
@@ -249,7 +283,7 @@ module Sidekiq
|
|
249
283
|
end
|
250
284
|
|
251
285
|
def environment_title_prefix
|
252
|
-
environment = Sidekiq.options[:environment] || ENV[
|
286
|
+
environment = Sidekiq.options[:environment] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
253
287
|
|
254
288
|
"[#{environment.upcase}] " unless environment == "production"
|
255
289
|
end
|
@@ -258,27 +292,31 @@ module Sidekiq
|
|
258
292
|
"Sidekiq v#{Sidekiq::VERSION}"
|
259
293
|
end
|
260
294
|
|
295
|
+
def server_utc_time
|
296
|
+
Time.now.utc.strftime("%H:%M:%S UTC")
|
297
|
+
end
|
298
|
+
|
261
299
|
def redis_connection_and_namespace
|
262
300
|
@redis_connection_and_namespace ||= begin
|
263
|
-
namespace_suffix = namespace
|
301
|
+
namespace_suffix = namespace.nil? ? "" : "##{namespace}"
|
264
302
|
"#{redis_connection}#{namespace_suffix}"
|
265
303
|
end
|
266
304
|
end
|
267
305
|
|
268
306
|
def retry_or_delete_or_kill(job, params)
|
269
|
-
if params[
|
307
|
+
if params["retry"]
|
270
308
|
job.retry
|
271
|
-
elsif params[
|
309
|
+
elsif params["delete"]
|
272
310
|
job.delete
|
273
|
-
elsif params[
|
311
|
+
elsif params["kill"]
|
274
312
|
job.kill
|
275
313
|
end
|
276
314
|
end
|
277
315
|
|
278
316
|
def delete_or_add_queue(job, params)
|
279
|
-
if params[
|
317
|
+
if params["delete"]
|
280
318
|
job.delete
|
281
|
-
elsif params[
|
319
|
+
elsif params["add_to_queue"]
|
282
320
|
job.add_to_queue
|
283
321
|
end
|
284
322
|
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,7 +93,8 @@ module Sidekiq
|
|
91
93
|
when String
|
92
94
|
{} if path == matcher
|
93
95
|
else
|
94
|
-
|
96
|
+
path_match = path.match(matcher)
|
97
|
+
if path_match
|
95
98
|
Hash[path_match.names.map(&:to_sym).zip(path_match.captures)]
|
96
99
|
end
|
97
100
|
end
|
data/lib/sidekiq/worker.rb
CHANGED
@@ -1,21 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'sidekiq/client'
|
3
|
-
require 'sidekiq/core_ext'
|
4
|
-
|
5
|
-
module Sidekiq
|
6
2
|
|
3
|
+
require "sidekiq/client"
|
7
4
|
|
5
|
+
module Sidekiq
|
8
6
|
##
|
9
7
|
# Include this module in your worker class and you can easily create
|
10
8
|
# asynchronous jobs:
|
11
9
|
#
|
12
|
-
#
|
13
|
-
#
|
10
|
+
# class HardWorker
|
11
|
+
# include Sidekiq::Worker
|
14
12
|
#
|
15
|
-
#
|
16
|
-
#
|
13
|
+
# def perform(*args)
|
14
|
+
# # do some work
|
15
|
+
# end
|
17
16
|
# end
|
18
|
-
# end
|
19
17
|
#
|
20
18
|
# Then in your Rails app, you can do this:
|
21
19
|
#
|
@@ -23,15 +21,124 @@ module Sidekiq
|
|
23
21
|
#
|
24
22
|
# Note that perform_async is a class method, perform is an instance method.
|
25
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 = Hash[opts.map { |k, v| [k.to_s, v] }] # stringify
|
52
|
+
self.sidekiq_options_hash = get_sidekiq_options.merge(Hash[opts.map { |k, v| [k.to_s, v] }])
|
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
|
+
|
26
135
|
attr_accessor :jid
|
27
136
|
|
28
137
|
def self.included(base)
|
29
|
-
raise ArgumentError, "
|
138
|
+
raise ArgumentError, "Sidekiq::Worker cannot be included in an ActiveJob: #{base.name}" if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
|
30
139
|
|
140
|
+
base.include(Options)
|
31
141
|
base.extend(ClassMethods)
|
32
|
-
base.class_attribute :sidekiq_options_hash
|
33
|
-
base.class_attribute :sidekiq_retry_in_block
|
34
|
-
base.class_attribute :sidekiq_retries_exhausted_block
|
35
142
|
end
|
36
143
|
|
37
144
|
def logger
|
@@ -43,12 +150,18 @@ module Sidekiq
|
|
43
150
|
# SomeWorker.set(queue: 'foo').perform_async(....)
|
44
151
|
#
|
45
152
|
class Setter
|
46
|
-
def initialize(opts)
|
153
|
+
def initialize(klass, opts)
|
154
|
+
@klass = klass
|
47
155
|
@opts = opts
|
48
156
|
end
|
49
157
|
|
158
|
+
def set(options)
|
159
|
+
@opts.merge!(options)
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
50
163
|
def perform_async(*args)
|
51
|
-
@
|
164
|
+
@klass.client_push(@opts.merge("args" => args, "class" => @klass))
|
52
165
|
end
|
53
166
|
|
54
167
|
# +interval+ must be a timestamp, numeric or something that acts
|
@@ -58,16 +171,15 @@ module Sidekiq
|
|
58
171
|
now = Time.now.to_f
|
59
172
|
ts = (int < 1_000_000_000 ? now + int : int)
|
60
173
|
|
61
|
-
@opts.merge
|
174
|
+
payload = @opts.merge("class" => @klass, "args" => args, "at" => ts)
|
62
175
|
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
63
|
-
|
64
|
-
@
|
176
|
+
payload.delete("at") if ts <= now
|
177
|
+
@klass.client_push(payload)
|
65
178
|
end
|
66
179
|
alias_method :perform_at, :perform_in
|
67
180
|
end
|
68
181
|
|
69
182
|
module ClassMethods
|
70
|
-
|
71
183
|
def delay(*args)
|
72
184
|
raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
|
73
185
|
end
|
@@ -81,11 +193,11 @@ module Sidekiq
|
|
81
193
|
end
|
82
194
|
|
83
195
|
def set(options)
|
84
|
-
Setter.new(options
|
196
|
+
Setter.new(self, options)
|
85
197
|
end
|
86
198
|
|
87
199
|
def perform_async(*args)
|
88
|
-
client_push(
|
200
|
+
client_push("class" => self, "args" => args)
|
89
201
|
end
|
90
202
|
|
91
203
|
# +interval+ must be a timestamp, numeric or something that acts
|
@@ -95,10 +207,10 @@ module Sidekiq
|
|
95
207
|
now = Time.now.to_f
|
96
208
|
ts = (int < 1_000_000_000 ? now + int : int)
|
97
209
|
|
98
|
-
item = {
|
210
|
+
item = {"class" => self, "args" => args, "at" => ts}
|
99
211
|
|
100
212
|
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
101
|
-
item.delete(
|
213
|
+
item.delete("at") if ts <= now
|
102
214
|
|
103
215
|
client_push(item)
|
104
216
|
end
|
@@ -117,28 +229,19 @@ module Sidekiq
|
|
117
229
|
#
|
118
230
|
# In practice, any option is allowed. This is the main mechanism to configure the
|
119
231
|
# options for a specific job.
|
120
|
-
def sidekiq_options(opts={})
|
121
|
-
|
122
|
-
end
|
123
|
-
|
124
|
-
def sidekiq_retry_in(&block)
|
125
|
-
self.sidekiq_retry_in_block = block
|
126
|
-
end
|
127
|
-
|
128
|
-
def sidekiq_retries_exhausted(&block)
|
129
|
-
self.sidekiq_retries_exhausted_block = block
|
130
|
-
end
|
131
|
-
|
132
|
-
def get_sidekiq_options # :nodoc:
|
133
|
-
self.sidekiq_options_hash ||= Sidekiq.default_worker_options
|
232
|
+
def sidekiq_options(opts = {})
|
233
|
+
super
|
134
234
|
end
|
135
235
|
|
136
236
|
def client_push(item) # :nodoc:
|
137
|
-
pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options[
|
138
|
-
|
139
|
-
|
140
|
-
|
237
|
+
pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
|
238
|
+
# stringify
|
239
|
+
item.keys.each do |key|
|
240
|
+
item[key.to_s] = item.delete(key)
|
241
|
+
end
|
141
242
|
|
243
|
+
Sidekiq::Client.new(pool).push(item)
|
244
|
+
end
|
142
245
|
end
|
143
246
|
end
|
144
247
|
end
|