sidekiq 6.1.2 → 6.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Changes.md +215 -2
- data/LICENSE +3 -3
- data/README.md +9 -4
- data/bin/sidekiq +3 -3
- data/bin/sidekiqload +70 -66
- data/bin/sidekiqmon +1 -1
- data/lib/generators/sidekiq/job_generator.rb +57 -0
- data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
- data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
- data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
- data/lib/sidekiq/api.rb +321 -145
- data/lib/sidekiq/cli.rb +73 -40
- data/lib/sidekiq/client.rb +48 -72
- data/lib/sidekiq/{util.rb → component.rb} +12 -14
- data/lib/sidekiq/delay.rb +3 -1
- data/lib/sidekiq/extensions/generic_proxy.rb +4 -2
- data/lib/sidekiq/fetch.rb +31 -20
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +16 -28
- data/lib/sidekiq/job_retry.rb +79 -59
- data/lib/sidekiq/job_util.rb +71 -0
- data/lib/sidekiq/launcher.rb +126 -65
- data/lib/sidekiq/logger.rb +11 -20
- data/lib/sidekiq/manager.rb +35 -34
- data/lib/sidekiq/metrics/deploy.rb +47 -0
- data/lib/sidekiq/metrics/query.rb +153 -0
- data/lib/sidekiq/metrics/shared.rb +94 -0
- data/lib/sidekiq/metrics/tracking.rb +134 -0
- data/lib/sidekiq/middleware/chain.rb +87 -41
- data/lib/sidekiq/middleware/current_attributes.rb +63 -0
- data/lib/sidekiq/middleware/i18n.rb +6 -4
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +1 -1
- data/lib/sidekiq/paginator.rb +8 -8
- data/lib/sidekiq/processor.rb +47 -41
- data/lib/sidekiq/rails.rb +22 -4
- data/lib/sidekiq/redis_client_adapter.rb +154 -0
- data/lib/sidekiq/redis_connection.rb +84 -55
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +55 -25
- data/lib/sidekiq/testing/inline.rb +4 -4
- data/lib/sidekiq/testing.rb +38 -39
- data/lib/sidekiq/transaction_aware_client.rb +45 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +3 -3
- data/lib/sidekiq/web/application.rb +37 -13
- data/lib/sidekiq/web/csrf_protection.rb +30 -8
- data/lib/sidekiq/web/helpers.rb +60 -28
- data/lib/sidekiq/web/router.rb +4 -1
- data/lib/sidekiq/web.rb +38 -78
- data/lib/sidekiq/worker.rb +136 -13
- data/lib/sidekiq.rb +114 -31
- data/sidekiq.gemspec +12 -4
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +113 -60
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard.js +50 -67
- data/web/assets/javascripts/graph.js +16 -0
- data/web/assets/javascripts/metrics.js +262 -0
- data/web/assets/stylesheets/application-dark.css +36 -36
- data/web/assets/stylesheets/application-rtl.css +0 -4
- data/web/assets/stylesheets/application.css +82 -237
- data/web/locales/ar.yml +8 -2
- data/web/locales/el.yml +43 -19
- data/web/locales/en.yml +11 -1
- data/web/locales/es.yml +18 -2
- data/web/locales/fr.yml +8 -1
- data/web/locales/ja.yml +3 -0
- data/web/locales/lt.yml +1 -1
- data/web/locales/pt-br.yml +27 -9
- data/web/views/_footer.erb +1 -1
- data/web/views/_job_info.erb +1 -1
- data/web/views/_nav.erb +1 -1
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +50 -19
- data/web/views/dashboard.erb +23 -14
- data/web/views/dead.erb +1 -1
- data/web/views/layout.erb +2 -1
- data/web/views/metrics.erb +69 -0
- data/web/views/metrics_for_job.erb +87 -0
- data/web/views/morgue.erb +6 -6
- data/web/views/queue.erb +15 -11
- data/web/views/queues.erb +3 -3
- data/web/views/retries.erb +7 -7
- data/web/views/retry.erb +1 -1
- data/web/views/scheduled.erb +1 -1
- metadata +43 -36
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
- data/.github/contributing.md +0 -32
- data/.github/workflows/ci.yml +0 -41
- data/.gitignore +0 -13
- data/.standard.yml +0 -20
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/5.0-Upgrade.md +0 -56
- data/6.0-Upgrade.md +0 -72
- data/COMM-LICENSE +0 -97
- data/Ent-2.0-Upgrade.md +0 -37
- data/Ent-Changes.md +0 -281
- data/Gemfile +0 -24
- data/Gemfile.lock +0 -192
- 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-5.0-Upgrade.md +0 -25
- data/Pro-Changes.md +0 -805
- data/Rakefile +0 -10
- data/code_of_conduct.md +0 -50
- data/lib/generators/sidekiq/worker_generator.rb +0 -57
- data/lib/sidekiq/exception_handler.rb +0 -27
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -10,18 +10,25 @@ module Sidekiq
|
|
10
10
|
module WebHelpers
|
11
11
|
def strings(lang)
|
12
12
|
@strings ||= {}
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
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.safe_load(File.open(file))
|
19
|
+
global.merge!(strs[lang])
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
24
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
|
+
|
25
32
|
def clear_caches
|
26
33
|
@strings = nil
|
27
34
|
@locale_files = nil
|
@@ -63,17 +70,6 @@ module Sidekiq
|
|
63
70
|
@head_html.join if defined?(@head_html)
|
64
71
|
end
|
65
72
|
|
66
|
-
def poll_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
|
72
|
-
else
|
73
|
-
""
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
73
|
def text_direction
|
78
74
|
get_locale["TextDirection"] || "ltr"
|
79
75
|
end
|
@@ -118,7 +114,7 @@ module Sidekiq
|
|
118
114
|
# within is used by Sidekiq Pro
|
119
115
|
def display_tags(job, within = nil)
|
120
116
|
job.tags.map { |tag|
|
121
|
-
"<span class='
|
117
|
+
"<span class='label label-info jobtag'>#{::Rack::Utils.escape_html(tag)}</span>"
|
122
118
|
}.join(" ")
|
123
119
|
end
|
124
120
|
|
@@ -144,22 +140,44 @@ module Sidekiq
|
|
144
140
|
params[:direction] == "asc" ? "↑" : "↓"
|
145
141
|
end
|
146
142
|
|
147
|
-
def
|
148
|
-
@
|
143
|
+
def workset
|
144
|
+
@work ||= Sidekiq::WorkSet.new
|
149
145
|
end
|
150
146
|
|
151
147
|
def processes
|
152
148
|
@processes ||= Sidekiq::ProcessSet.new
|
153
149
|
end
|
154
150
|
|
151
|
+
# Sorts processes by hostname following the natural sort order so that
|
152
|
+
# 'worker.1' < 'worker.2' < 'worker.10' < 'worker.20'
|
153
|
+
# '2.1.1.1' < '192.168.0.2' < '192.168.0.10'
|
154
|
+
def sorted_processes
|
155
|
+
@sorted_processes ||= begin
|
156
|
+
return processes unless processes.all? { |p| p["hostname"] }
|
157
|
+
|
158
|
+
split_characters = /[._-]/
|
159
|
+
|
160
|
+
padding = processes.flat_map { |p| p["hostname"].split(split_characters) }.map(&:size).max
|
161
|
+
|
162
|
+
processes.to_a.sort_by do |process|
|
163
|
+
process["hostname"].split(split_characters).map do |substring|
|
164
|
+
# Left-pad the substring with '0' if it starts with a number or 'a'
|
165
|
+
# otherwise, so that '25' < 192' < 'a' ('025' < '192' < 'aaa')
|
166
|
+
padding_char = substring[0].match?(/\d/) ? "0" : "a"
|
167
|
+
|
168
|
+
substring.rjust(padding, padding_char)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
155
174
|
def stats
|
156
175
|
@stats ||= Sidekiq::Stats.new
|
157
176
|
end
|
158
177
|
|
159
178
|
def redis_connection
|
160
179
|
Sidekiq.redis do |conn|
|
161
|
-
|
162
|
-
"redis://#{c[:location]}/#{c[:db]}"
|
180
|
+
conn.connection[:id]
|
163
181
|
end
|
164
182
|
end
|
165
183
|
|
@@ -180,7 +198,7 @@ module Sidekiq
|
|
180
198
|
end
|
181
199
|
|
182
200
|
def current_status
|
183
|
-
|
201
|
+
workset.size == 0 ? "idle" : "active"
|
184
202
|
end
|
185
203
|
|
186
204
|
def relative_time(time)
|
@@ -197,7 +215,7 @@ module Sidekiq
|
|
197
215
|
[score.to_f, jid]
|
198
216
|
end
|
199
217
|
|
200
|
-
SAFE_QPARAMS = %w[page
|
218
|
+
SAFE_QPARAMS = %w[page direction]
|
201
219
|
|
202
220
|
# Merge options with current params, filter safe params, and stringify to query string
|
203
221
|
def qparams(options)
|
@@ -247,7 +265,7 @@ module Sidekiq
|
|
247
265
|
queue class args retry_count retried_at failed_at
|
248
266
|
jid error_message error_class backtrace
|
249
267
|
error_backtrace enqueued_at retry wrapped
|
250
|
-
created_at tags
|
268
|
+
created_at tags display_class
|
251
269
|
])
|
252
270
|
|
253
271
|
def retry_extra_items(retry_job)
|
@@ -258,7 +276,21 @@ module Sidekiq
|
|
258
276
|
end
|
259
277
|
end
|
260
278
|
|
279
|
+
def format_memory(rss_kb)
|
280
|
+
return "0" if rss_kb.nil? || rss_kb == 0
|
281
|
+
|
282
|
+
if rss_kb < 100_000
|
283
|
+
"#{number_with_delimiter(rss_kb)} KB"
|
284
|
+
elsif rss_kb < 10_000_000
|
285
|
+
"#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
|
286
|
+
else
|
287
|
+
"#{number_with_delimiter((rss_kb / (1024.0 * 1024.0)).round(1))} GB"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
261
291
|
def number_with_delimiter(number)
|
292
|
+
return "" if number.nil?
|
293
|
+
|
262
294
|
begin
|
263
295
|
Float(number)
|
264
296
|
rescue ArgumentError, TypeError
|
@@ -292,7 +324,7 @@ module Sidekiq
|
|
292
324
|
end
|
293
325
|
|
294
326
|
def environment_title_prefix
|
295
|
-
environment = Sidekiq
|
327
|
+
environment = Sidekiq[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
296
328
|
|
297
329
|
"[#{environment.upcase}] " unless environment == "production"
|
298
330
|
end
|
data/lib/sidekiq/web/router.rb
CHANGED
@@ -15,6 +15,10 @@ module Sidekiq
|
|
15
15
|
REQUEST_METHOD = "REQUEST_METHOD"
|
16
16
|
PATH_INFO = "PATH_INFO"
|
17
17
|
|
18
|
+
def head(path, &block)
|
19
|
+
route(HEAD, path, &block)
|
20
|
+
end
|
21
|
+
|
18
22
|
def get(path, &block)
|
19
23
|
route(GET, path, &block)
|
20
24
|
end
|
@@ -39,7 +43,6 @@ module Sidekiq
|
|
39
43
|
@routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []}
|
40
44
|
|
41
45
|
@routes[method] << WebRoute.new(method, path, block)
|
42
|
-
@routes[HEAD] << WebRoute.new(method, path, block) if method == GET
|
43
46
|
end
|
44
47
|
|
45
48
|
def match(env)
|
data/lib/sidekiq/web.rb
CHANGED
@@ -13,10 +13,8 @@ require "sidekiq/web/application"
|
|
13
13
|
require "sidekiq/web/csrf_protection"
|
14
14
|
|
15
15
|
require "rack/content_length"
|
16
|
-
|
17
16
|
require "rack/builder"
|
18
|
-
require "rack/
|
19
|
-
require "rack/session/cookie"
|
17
|
+
require "rack/static"
|
20
18
|
|
21
19
|
module Sidekiq
|
22
20
|
class Web
|
@@ -35,19 +33,15 @@ module Sidekiq
|
|
35
33
|
"Dead" => "morgue"
|
36
34
|
}
|
37
35
|
|
36
|
+
if ENV["SIDEKIQ_METRICS_BETA"] == "1"
|
37
|
+
DEFAULT_TABS["Metrics"] = "metrics"
|
38
|
+
end
|
39
|
+
|
38
40
|
class << self
|
39
41
|
def settings
|
40
42
|
self
|
41
43
|
end
|
42
44
|
|
43
|
-
def middlewares
|
44
|
-
@middlewares ||= []
|
45
|
-
end
|
46
|
-
|
47
|
-
def use(*middleware_args, &block)
|
48
|
-
middlewares << [middleware_args, block]
|
49
|
-
end
|
50
|
-
|
51
45
|
def default_tabs
|
52
46
|
DEFAULT_TABS
|
53
47
|
end
|
@@ -73,32 +67,45 @@ module Sidekiq
|
|
73
67
|
opts.each { |key| set(key, false) }
|
74
68
|
end
|
75
69
|
|
76
|
-
|
70
|
+
def middlewares
|
71
|
+
@middlewares ||= []
|
72
|
+
end
|
73
|
+
|
74
|
+
def use(*args, &block)
|
75
|
+
middlewares << [args, block]
|
76
|
+
end
|
77
|
+
|
77
78
|
def set(attribute, value)
|
78
79
|
send(:"#{attribute}=", value)
|
79
80
|
end
|
80
81
|
|
81
|
-
|
82
|
+
def sessions=(val)
|
83
|
+
puts "WARNING: Sidekiq::Web.sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def session_secret=(val)
|
87
|
+
puts "WARNING: Sidekiq::Web.session_secret= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_accessor :app_url, :redis_pool
|
82
91
|
attr_writer :locales, :views
|
83
92
|
end
|
84
93
|
|
85
94
|
def self.inherited(child)
|
86
95
|
child.app_url = app_url
|
87
|
-
child.session_secret = session_secret
|
88
96
|
child.redis_pool = redis_pool
|
89
|
-
child.sessions = sessions
|
90
97
|
end
|
91
98
|
|
92
99
|
def settings
|
93
100
|
self.class.settings
|
94
101
|
end
|
95
102
|
|
96
|
-
def
|
97
|
-
middlewares
|
103
|
+
def middlewares
|
104
|
+
@middlewares ||= self.class.middlewares
|
98
105
|
end
|
99
106
|
|
100
|
-
def
|
101
|
-
|
107
|
+
def use(*args, &block)
|
108
|
+
middlewares << [args, block]
|
102
109
|
end
|
103
110
|
|
104
111
|
def call(env)
|
@@ -126,18 +133,8 @@ module Sidekiq
|
|
126
133
|
send(:"#{attribute}=", value)
|
127
134
|
end
|
128
135
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
attr_writer :sessions
|
133
|
-
|
134
|
-
def sessions
|
135
|
-
unless instance_variable_defined?("@sessions")
|
136
|
-
@sessions = self.class.sessions
|
137
|
-
@sessions = @sessions.to_hash.dup if @sessions.respond_to?(:to_hash)
|
138
|
-
end
|
139
|
-
|
140
|
-
@sessions
|
136
|
+
def sessions=(val)
|
137
|
+
puts "Sidekiq::Web#sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller[2..2].first}"
|
141
138
|
end
|
142
139
|
|
143
140
|
def self.register(extension)
|
@@ -146,57 +143,20 @@ module Sidekiq
|
|
146
143
|
|
147
144
|
private
|
148
145
|
|
149
|
-
def using?(middleware)
|
150
|
-
middlewares.any? do |(m, _)|
|
151
|
-
m.is_a?(Array) && (m[0] == middleware || m[0].is_a?(middleware))
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def build_sessions
|
156
|
-
middlewares = self.middlewares
|
157
|
-
|
158
|
-
s = sessions
|
159
|
-
|
160
|
-
# turn on CSRF protection if sessions are enabled and this is not the test env
|
161
|
-
if s && !using?(CsrfProtection) && ENV["RACK_ENV"] != "test"
|
162
|
-
middlewares.unshift [[CsrfProtection], nil]
|
163
|
-
end
|
164
|
-
|
165
|
-
if s && !using?(::Rack::Session::Cookie)
|
166
|
-
unless (secret = Web.session_secret)
|
167
|
-
require "securerandom"
|
168
|
-
secret = SecureRandom.hex(64)
|
169
|
-
end
|
170
|
-
|
171
|
-
options = {secret: secret}
|
172
|
-
options = options.merge(s.to_hash) if s.respond_to? :to_hash
|
173
|
-
|
174
|
-
middlewares.unshift [[::Rack::Session::Cookie, options], nil]
|
175
|
-
end
|
176
|
-
|
177
|
-
# Since Sidekiq::WebApplication no longer calculates its own
|
178
|
-
# Content-Length response header, we must ensure that the Rack middleware
|
179
|
-
# that does this is loaded
|
180
|
-
unless using? ::Rack::ContentLength
|
181
|
-
middlewares.unshift [[::Rack::ContentLength], nil]
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
146
|
def build
|
186
|
-
build_sessions
|
187
|
-
|
188
|
-
middlewares = self.middlewares
|
189
147
|
klass = self.class
|
148
|
+
m = middlewares
|
190
149
|
|
191
|
-
|
192
|
-
|
193
|
-
map "/#{asset_dir}" do
|
194
|
-
run ::Rack::File.new("#{ASSETS}/#{asset_dir}", {"Cache-Control" => "public, max-age=86400"})
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
middlewares.each { |middleware, block| use(*middleware, &block) }
|
150
|
+
rules = []
|
151
|
+
rules = [[:all, {"cache-control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
|
199
152
|
|
153
|
+
::Rack::Builder.new do
|
154
|
+
use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
|
155
|
+
root: ASSETS,
|
156
|
+
cascade: true,
|
157
|
+
header_rules: rules
|
158
|
+
m.each { |middleware, block| use(*middleware, &block) }
|
159
|
+
use Sidekiq::Web::CsrfProtection unless $TESTING
|
200
160
|
run WebApplication.new(klass)
|
201
161
|
end
|
202
162
|
end
|
data/lib/sidekiq/worker.rb
CHANGED
@@ -9,6 +9,7 @@ module Sidekiq
|
|
9
9
|
#
|
10
10
|
# class HardWorker
|
11
11
|
# include Sidekiq::Worker
|
12
|
+
# sidekiq_options queue: 'critical', retry: 5
|
12
13
|
#
|
13
14
|
# def perform(*args)
|
14
15
|
# # do some work
|
@@ -20,6 +21,26 @@ module Sidekiq
|
|
20
21
|
# HardWorker.perform_async(1, 2, 3)
|
21
22
|
#
|
22
23
|
# Note that perform_async is a class method, perform is an instance method.
|
24
|
+
#
|
25
|
+
# Sidekiq::Worker also includes several APIs to provide compatibility with
|
26
|
+
# ActiveJob.
|
27
|
+
#
|
28
|
+
# class SomeWorker
|
29
|
+
# include Sidekiq::Worker
|
30
|
+
# queue_as :critical
|
31
|
+
#
|
32
|
+
# def perform(...)
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# SomeWorker.set(wait_until: 1.hour).perform_async(123)
|
37
|
+
#
|
38
|
+
# Note that arguments passed to the job must still obey Sidekiq's
|
39
|
+
# best practice for simple, JSON-native data types. Sidekiq will not
|
40
|
+
# implement ActiveJob's more complex argument serialization. For
|
41
|
+
# this reason, we don't implement `perform_later` as our call semantics
|
42
|
+
# are very different.
|
43
|
+
#
|
23
44
|
module Worker
|
24
45
|
##
|
25
46
|
# The Options module is extracted so we can include it in ActiveJob::Base
|
@@ -61,7 +82,7 @@ module Sidekiq
|
|
61
82
|
end
|
62
83
|
|
63
84
|
def get_sidekiq_options # :nodoc:
|
64
|
-
self.sidekiq_options_hash ||= Sidekiq.
|
85
|
+
self.sidekiq_options_hash ||= Sidekiq.default_job_options
|
65
86
|
end
|
66
87
|
|
67
88
|
def sidekiq_class_attribute(*attrs)
|
@@ -150,33 +171,97 @@ module Sidekiq
|
|
150
171
|
# SomeWorker.set(queue: 'foo').perform_async(....)
|
151
172
|
#
|
152
173
|
class Setter
|
174
|
+
include Sidekiq::JobUtil
|
175
|
+
|
153
176
|
def initialize(klass, opts)
|
154
177
|
@klass = klass
|
155
|
-
|
178
|
+
# NB: the internal hash always has stringified keys
|
179
|
+
@opts = opts.transform_keys(&:to_s)
|
180
|
+
|
181
|
+
# ActiveJob compatibility
|
182
|
+
interval = @opts.delete("wait_until") || @opts.delete("wait")
|
183
|
+
at(interval) if interval
|
156
184
|
end
|
157
185
|
|
158
186
|
def set(options)
|
159
|
-
|
187
|
+
hash = options.transform_keys(&:to_s)
|
188
|
+
interval = hash.delete("wait_until") || @opts.delete("wait")
|
189
|
+
@opts.merge!(hash)
|
190
|
+
at(interval) if interval
|
160
191
|
self
|
161
192
|
end
|
162
193
|
|
163
194
|
def perform_async(*args)
|
164
|
-
@
|
195
|
+
if @opts["sync"] == true
|
196
|
+
perform_inline(*args)
|
197
|
+
else
|
198
|
+
@klass.client_push(@opts.merge("args" => args, "class" => @klass))
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Explicit inline execution of a job. Returns nil if the job did not
|
203
|
+
# execute, true otherwise.
|
204
|
+
def perform_inline(*args)
|
205
|
+
raw = @opts.merge("args" => args, "class" => @klass)
|
206
|
+
|
207
|
+
# validate and normalize payload
|
208
|
+
item = normalize_item(raw)
|
209
|
+
queue = item["queue"]
|
210
|
+
|
211
|
+
# run client-side middleware
|
212
|
+
result = Sidekiq.client_middleware.invoke(item["class"], item, queue, Sidekiq.redis_pool) do
|
213
|
+
item
|
214
|
+
end
|
215
|
+
return nil unless result
|
216
|
+
|
217
|
+
# round-trip the payload via JSON
|
218
|
+
msg = Sidekiq.load_json(Sidekiq.dump_json(item))
|
219
|
+
|
220
|
+
# prepare the job instance
|
221
|
+
klass = msg["class"].constantize
|
222
|
+
job = klass.new
|
223
|
+
job.jid = msg["jid"]
|
224
|
+
job.bid = msg["bid"] if job.respond_to?(:bid)
|
225
|
+
|
226
|
+
# run the job through server-side middleware
|
227
|
+
result = Sidekiq.server_middleware.invoke(job, msg, msg["queue"]) do
|
228
|
+
# perform it
|
229
|
+
job.perform(*msg["args"])
|
230
|
+
true
|
231
|
+
end
|
232
|
+
return nil unless result
|
233
|
+
# jobs do not return a result. they should store any
|
234
|
+
# modified state.
|
235
|
+
true
|
236
|
+
end
|
237
|
+
alias_method :perform_sync, :perform_inline
|
238
|
+
|
239
|
+
def perform_bulk(args, batch_size: 1_000)
|
240
|
+
client = @klass.build_client
|
241
|
+
result = args.each_slice(batch_size).flat_map do |slice|
|
242
|
+
client.push_bulk(@opts.merge("class" => @klass, "args" => slice))
|
243
|
+
end
|
244
|
+
|
245
|
+
result.is_a?(Enumerator::Lazy) ? result.force : result
|
165
246
|
end
|
166
247
|
|
167
248
|
# +interval+ must be a timestamp, numeric or something that acts
|
168
249
|
# numeric (like an activesupport time interval).
|
169
250
|
def perform_in(interval, *args)
|
251
|
+
at(interval).perform_async(*args)
|
252
|
+
end
|
253
|
+
alias_method :perform_at, :perform_in
|
254
|
+
|
255
|
+
private
|
256
|
+
|
257
|
+
def at(interval)
|
170
258
|
int = interval.to_f
|
171
259
|
now = Time.now.to_f
|
172
260
|
ts = (int < 1_000_000_000 ? now + int : int)
|
173
|
-
|
174
|
-
payload = @opts.merge("class" => @klass, "args" => args)
|
175
261
|
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
176
|
-
|
177
|
-
|
262
|
+
@opts["at"] = ts if ts > now
|
263
|
+
self
|
178
264
|
end
|
179
|
-
alias_method :perform_at, :perform_in
|
180
265
|
end
|
181
266
|
|
182
267
|
module ClassMethods
|
@@ -192,12 +277,46 @@ module Sidekiq
|
|
192
277
|
raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at"
|
193
278
|
end
|
194
279
|
|
280
|
+
def queue_as(q)
|
281
|
+
sidekiq_options("queue" => q.to_s)
|
282
|
+
end
|
283
|
+
|
195
284
|
def set(options)
|
196
285
|
Setter.new(self, options)
|
197
286
|
end
|
198
287
|
|
199
288
|
def perform_async(*args)
|
200
|
-
|
289
|
+
Setter.new(self, {}).perform_async(*args)
|
290
|
+
end
|
291
|
+
|
292
|
+
# Inline execution of job's perform method after passing through Sidekiq.client_middleware and Sidekiq.server_middleware
|
293
|
+
def perform_inline(*args)
|
294
|
+
Setter.new(self, {}).perform_inline(*args)
|
295
|
+
end
|
296
|
+
alias_method :perform_sync, :perform_inline
|
297
|
+
|
298
|
+
##
|
299
|
+
# Push a large number of jobs to Redis, while limiting the batch of
|
300
|
+
# each job payload to 1,000. This method helps cut down on the number
|
301
|
+
# of round trips to Redis, which can increase the performance of enqueueing
|
302
|
+
# large numbers of jobs.
|
303
|
+
#
|
304
|
+
# +items+ must be an Array of Arrays.
|
305
|
+
#
|
306
|
+
# For finer-grained control, use `Sidekiq::Client.push_bulk` directly.
|
307
|
+
#
|
308
|
+
# Example (3 Redis round trips):
|
309
|
+
#
|
310
|
+
# SomeWorker.perform_async(1)
|
311
|
+
# SomeWorker.perform_async(2)
|
312
|
+
# SomeWorker.perform_async(3)
|
313
|
+
#
|
314
|
+
# Would instead become (1 Redis round trip):
|
315
|
+
#
|
316
|
+
# SomeWorker.perform_bulk([[1], [2], [3]])
|
317
|
+
#
|
318
|
+
def perform_bulk(*args, **kwargs)
|
319
|
+
Setter.new(self, {}).perform_bulk(*args, **kwargs)
|
201
320
|
end
|
202
321
|
|
203
322
|
# +interval+ must be a timestamp, numeric or something that acts
|
@@ -234,10 +353,14 @@ module Sidekiq
|
|
234
353
|
end
|
235
354
|
|
236
355
|
def client_push(item) # :nodoc:
|
237
|
-
|
238
|
-
|
356
|
+
raise ArgumentError, "Job payloads should contain no Symbols: #{item}" if item.any? { |k, v| k.is_a?(::Symbol) }
|
357
|
+
build_client.push(item)
|
358
|
+
end
|
239
359
|
|
240
|
-
|
360
|
+
def build_client # :nodoc:
|
361
|
+
pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
|
362
|
+
client_class = get_sidekiq_options["client_class"] || Sidekiq::Client
|
363
|
+
client_class.new(pool)
|
241
364
|
end
|
242
365
|
end
|
243
366
|
end
|