sidekiq 6.2.0 → 6.3.1
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 +63 -1
- data/LICENSE +1 -1
- data/README.md +2 -2
- data/lib/sidekiq/api.rb +80 -56
- data/lib/sidekiq/cli.rb +10 -2
- data/lib/sidekiq/client.rb +2 -6
- data/lib/sidekiq/extensions/generic_proxy.rb +3 -1
- data/lib/sidekiq/fetch.rb +5 -4
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +1 -1
- data/lib/sidekiq/job_retry.rb +3 -7
- data/lib/sidekiq/launcher.rb +27 -21
- data/lib/sidekiq/middleware/chain.rb +5 -3
- data/lib/sidekiq/middleware/current_attributes.rb +52 -0
- data/lib/sidekiq/rails.rb +11 -0
- data/lib/sidekiq/redis_connection.rb +4 -6
- data/lib/sidekiq/scheduled.rb +40 -15
- data/lib/sidekiq/testing.rb +1 -3
- data/lib/sidekiq/util.rb +28 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +2 -2
- data/lib/sidekiq/web/application.rb +7 -5
- data/lib/sidekiq/web/csrf_protection.rb +9 -6
- data/lib/sidekiq/web/helpers.rb +9 -21
- data/lib/sidekiq/web.rb +7 -6
- data/lib/sidekiq/worker.rb +72 -5
- data/lib/sidekiq.rb +3 -1
- data/sidekiq.gemspec +1 -1
- data/web/assets/javascripts/application.js +82 -61
- data/web/assets/javascripts/dashboard.js +51 -51
- data/web/assets/stylesheets/application-dark.css +18 -31
- data/web/assets/stylesheets/application-rtl.css +0 -4
- data/web/assets/stylesheets/application.css +21 -233
- data/web/locales/ar.yml +8 -2
- data/web/locales/en.yml +4 -1
- data/web/locales/es.yml +18 -2
- data/web/locales/fr.yml +7 -0
- data/web/locales/ja.yml +3 -0
- data/web/locales/lt.yml +1 -1
- data/web/views/_footer.erb +1 -1
- data/web/views/_job_info.erb +1 -1
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +8 -7
- data/web/views/dashboard.erb +22 -14
- data/web/views/dead.erb +1 -1
- data/web/views/layout.erb +1 -1
- data/web/views/morgue.erb +6 -6
- data/web/views/queue.erb +11 -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 +5 -3
@@ -0,0 +1,52 @@
|
|
1
|
+
require "active_support/current_attributes"
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
##
|
5
|
+
# Automatically save and load any current attributes in the execution context
|
6
|
+
# so context attributes "flow" from Rails actions into any associated jobs.
|
7
|
+
# This can be useful for multi-tenancy, i18n locale, timezone, any implicit
|
8
|
+
# per-request attribute. See +ActiveSupport::CurrentAttributes+.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
#
|
12
|
+
# # in your initializer
|
13
|
+
# require "sidekiq/middleware/current_attributes"
|
14
|
+
# Sidekiq::CurrentAttributes.persist(Myapp::Current)
|
15
|
+
#
|
16
|
+
module CurrentAttributes
|
17
|
+
class Save
|
18
|
+
def initialize(cattr)
|
19
|
+
@klass = cattr
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(_, job, _, _)
|
23
|
+
job["cattr"] = @klass.attributes
|
24
|
+
yield
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Load
|
29
|
+
def initialize(cattr)
|
30
|
+
@klass = cattr
|
31
|
+
end
|
32
|
+
|
33
|
+
def call(_, job, _, &block)
|
34
|
+
if job.has_key?("cattr")
|
35
|
+
@klass.set(job["cattr"], &block)
|
36
|
+
else
|
37
|
+
yield
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.persist(klass)
|
43
|
+
Sidekiq.configure_client do |config|
|
44
|
+
config.client_middleware.add Save, klass
|
45
|
+
end
|
46
|
+
Sidekiq.configure_server do |config|
|
47
|
+
config.client_middleware.add Save, klass
|
48
|
+
config.server_middleware.add Load, klass
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -37,6 +37,17 @@ module Sidekiq
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
initializer "sidekiq.rails_logger" do
|
41
|
+
Sidekiq.configure_server do |_|
|
42
|
+
# This is the integration code necessary so that if code uses `Rails.logger.info "Hello"`,
|
43
|
+
# it will appear in the Sidekiq console with all of the job context. See #5021 and
|
44
|
+
# https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84
|
45
|
+
unless ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
46
|
+
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(::Sidekiq.logger))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
40
51
|
# This hook happens after all initializers are run, just before returning
|
41
52
|
# from config/environment.rb back to sidekiq/cli.rb.
|
42
53
|
#
|
@@ -94,12 +94,10 @@ module Sidekiq
|
|
94
94
|
def log_info(options)
|
95
95
|
redacted = "REDACTED"
|
96
96
|
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
|
101
|
-
keys = options.keys
|
102
|
-
keys.delete(:ssl_params)
|
97
|
+
# Deep clone so we can muck with these options all we want and exclude
|
98
|
+
# params from dump-and-load that may contain objects that Marshal is
|
99
|
+
# unable to safely dump.
|
100
|
+
keys = options.keys - [:logger, :ssl_params]
|
103
101
|
scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
|
104
102
|
if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
|
105
103
|
uri.password = redacted
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -9,29 +9,48 @@ module Sidekiq
|
|
9
9
|
SETS = %w[retry schedule]
|
10
10
|
|
11
11
|
class Enq
|
12
|
+
LUA_ZPOPBYSCORE = <<~LUA
|
13
|
+
local key, now = KEYS[1], ARGV[1]
|
14
|
+
local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1)
|
15
|
+
if jobs[1] then
|
16
|
+
redis.call("zrem", key, jobs[1])
|
17
|
+
return jobs[1]
|
18
|
+
end
|
19
|
+
LUA
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@lua_zpopbyscore_sha = nil
|
23
|
+
end
|
24
|
+
|
12
25
|
def enqueue_jobs(now = Time.now.to_f.to_s, sorted_sets = SETS)
|
13
26
|
# A job's "score" in Redis is the time at which it should be processed.
|
14
27
|
# Just check Redis for the set of jobs with a timestamp before now.
|
15
28
|
Sidekiq.redis do |conn|
|
16
29
|
sorted_sets.each do |sorted_set|
|
17
|
-
# Get next
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
# the queue, it's because another process already popped it so we can move on to the
|
25
|
-
# next one.
|
26
|
-
if conn.zrem(sorted_set, job)
|
27
|
-
Sidekiq::Client.push(Sidekiq.load_json(job))
|
28
|
-
Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
|
29
|
-
end
|
30
|
-
end
|
30
|
+
# Get next item in the queue with score (time to execute) <= now.
|
31
|
+
# We need to go through the list one at a time to reduce the risk of something
|
32
|
+
# going wrong between the time jobs are popped from the scheduled queue and when
|
33
|
+
# they are pushed onto a work queue and losing the jobs.
|
34
|
+
while (job = zpopbyscore(conn, keys: [sorted_set], argv: [now]))
|
35
|
+
Sidekiq::Client.push(Sidekiq.load_json(job))
|
36
|
+
Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
|
31
37
|
end
|
32
38
|
end
|
33
39
|
end
|
34
40
|
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def zpopbyscore(conn, keys: nil, argv: nil)
|
45
|
+
@lua_zpopbyscore_sha = conn.script(:load, LUA_ZPOPBYSCORE) if @lua_zpopbyscore_sha.nil?
|
46
|
+
|
47
|
+
conn.evalsha(@lua_zpopbyscore_sha, keys: keys, argv: argv)
|
48
|
+
rescue Redis::CommandError => e
|
49
|
+
raise unless e.message.start_with?("NOSCRIPT")
|
50
|
+
|
51
|
+
@lua_zpopbyscore_sha = nil
|
52
|
+
retry
|
53
|
+
end
|
35
54
|
end
|
36
55
|
|
37
56
|
##
|
@@ -49,6 +68,7 @@ module Sidekiq
|
|
49
68
|
@sleeper = ConnectionPool::TimedStack.new
|
50
69
|
@done = false
|
51
70
|
@thread = nil
|
71
|
+
@count_calls = 0
|
52
72
|
end
|
53
73
|
|
54
74
|
# Shut down this instance, will pause until the thread is dead.
|
@@ -152,8 +172,13 @@ module Sidekiq
|
|
152
172
|
end
|
153
173
|
|
154
174
|
def process_count
|
155
|
-
|
175
|
+
# The work buried within Sidekiq::ProcessSet#cleanup can be
|
176
|
+
# expensive at scale. Cut it down by 90% with this counter.
|
177
|
+
# NB: This method is only called by the scheduler thread so we
|
178
|
+
# don't need to worry about the thread safety of +=.
|
179
|
+
pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
|
156
180
|
pcount = 1 if pcount == 0
|
181
|
+
@count_calls += 1
|
157
182
|
pcount
|
158
183
|
end
|
159
184
|
|
data/lib/sidekiq/testing.rb
CHANGED
@@ -338,7 +338,5 @@ module Sidekiq
|
|
338
338
|
end
|
339
339
|
|
340
340
|
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
|
341
|
-
|
342
|
-
puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
|
343
|
-
puts("**************************************************")
|
341
|
+
warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
|
344
342
|
end
|
data/lib/sidekiq/util.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "forwardable"
|
3
4
|
require "socket"
|
4
5
|
require "securerandom"
|
5
6
|
require "sidekiq/exception_handler"
|
@@ -8,6 +9,33 @@ module Sidekiq
|
|
8
9
|
##
|
9
10
|
# This module is part of Sidekiq core and not intended for extensions.
|
10
11
|
#
|
12
|
+
|
13
|
+
class RingBuffer
|
14
|
+
include Enumerable
|
15
|
+
extend Forwardable
|
16
|
+
def_delegators :@buf, :[], :each, :size
|
17
|
+
|
18
|
+
def initialize(size, default = 0)
|
19
|
+
@size = size
|
20
|
+
@buf = Array.new(size, default)
|
21
|
+
@index = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def <<(element)
|
25
|
+
@buf[@index % @size] = element
|
26
|
+
@index += 1
|
27
|
+
element
|
28
|
+
end
|
29
|
+
|
30
|
+
def buffer
|
31
|
+
@buf
|
32
|
+
end
|
33
|
+
|
34
|
+
def reset(default = 0)
|
35
|
+
@buf.fill(default)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
11
39
|
module Util
|
12
40
|
include ExceptionHandler
|
13
41
|
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
@@ -15,7 +15,7 @@ module Sidekiq
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def halt(res)
|
18
|
-
throw :halt, res
|
18
|
+
throw :halt, [res, {"Content-Type" => "text/plain"}, [res.to_s]]
|
19
19
|
end
|
20
20
|
|
21
21
|
def redirect(location)
|
@@ -68,7 +68,7 @@ module Sidekiq
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def json(payload)
|
71
|
-
[200, {"Content-Type" => "application/json", "Cache-Control" => "no-
|
71
|
+
[200, {"Content-Type" => "application/json", "Cache-Control" => "private, no-store"}, [Sidekiq.dump_json(payload)]]
|
72
72
|
end
|
73
73
|
|
74
74
|
def initialize(env, block)
|
@@ -82,15 +82,17 @@ module Sidekiq
|
|
82
82
|
erb(:queues)
|
83
83
|
end
|
84
84
|
|
85
|
+
QUEUE_NAME = /\A[a-z_:.\-0-9]+\z/i
|
86
|
+
|
85
87
|
get "/queues/:name" do
|
86
88
|
@name = route_params[:name]
|
87
89
|
|
88
|
-
halt(404)
|
90
|
+
halt(404) if !@name || @name !~ QUEUE_NAME
|
89
91
|
|
90
92
|
@count = (params["count"] || 25).to_i
|
91
93
|
@queue = Sidekiq::Queue.new(@name)
|
92
|
-
(@current_page, @total_size, @
|
93
|
-
@
|
94
|
+
(@current_page, @total_size, @jobs) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
|
95
|
+
@jobs = @jobs.map { |msg| Sidekiq::JobRecord.new(msg, @name) }
|
94
96
|
|
95
97
|
erb(:queue)
|
96
98
|
end
|
@@ -111,7 +113,7 @@ module Sidekiq
|
|
111
113
|
|
112
114
|
post "/queues/:name/delete" do
|
113
115
|
name = route_params[:name]
|
114
|
-
Sidekiq::
|
116
|
+
Sidekiq::JobRecord.new(params["key_val"], name).delete
|
115
117
|
|
116
118
|
redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
|
117
119
|
end
|
@@ -312,7 +314,7 @@ module Sidekiq
|
|
312
314
|
# rendered content goes here
|
313
315
|
headers = {
|
314
316
|
"Content-Type" => "text/html",
|
315
|
-
"Cache-Control" => "no-
|
317
|
+
"Cache-Control" => "private, no-store",
|
316
318
|
"Content-Language" => action.locale,
|
317
319
|
"Content-Security-Policy" => CSP_HEADER
|
318
320
|
}
|
@@ -77,16 +77,19 @@ module Sidekiq
|
|
77
77
|
end
|
78
78
|
|
79
79
|
|
80
|
-
If this is a
|
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
|
81
83
|
|
84
|
+
If this is a bare Rack app, use a session middleware before Sidekiq::Web:
|
82
85
|
|
83
|
-
|
84
|
-
|
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)) }
|
85
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
|
86
92
|
|
87
|
-
# now use the secret with a session cookie middleware
|
88
|
-
use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
|
89
|
-
run Sidekiq::Web
|
90
93
|
EOM
|
91
94
|
end
|
92
95
|
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -10,14 +10,13 @@ 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.load(File.open(file))
|
19
|
+
global.merge!(strs[lang])
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
@@ -71,17 +70,6 @@ module Sidekiq
|
|
71
70
|
@head_html.join if defined?(@head_html)
|
72
71
|
end
|
73
72
|
|
74
|
-
def poll_path
|
75
|
-
if current_path != "" && params["poll"]
|
76
|
-
path = root_path + current_path
|
77
|
-
query_string = to_query_string(params.slice(*params.keys - %w[page poll]))
|
78
|
-
path += "?#{query_string}" unless query_string.empty?
|
79
|
-
path
|
80
|
-
else
|
81
|
-
""
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
73
|
def text_direction
|
86
74
|
get_locale["TextDirection"] || "ltr"
|
87
75
|
end
|
@@ -126,7 +114,7 @@ module Sidekiq
|
|
126
114
|
# within is used by Sidekiq Pro
|
127
115
|
def display_tags(job, within = nil)
|
128
116
|
job.tags.map { |tag|
|
129
|
-
"<span class='
|
117
|
+
"<span class='label label-info jobtag'>#{::Rack::Utils.escape_html(tag)}</span>"
|
130
118
|
}.join(" ")
|
131
119
|
end
|
132
120
|
|
@@ -204,7 +192,7 @@ module Sidekiq
|
|
204
192
|
[score.to_f, jid]
|
205
193
|
end
|
206
194
|
|
207
|
-
SAFE_QPARAMS = %w[page
|
195
|
+
SAFE_QPARAMS = %w[page direction]
|
208
196
|
|
209
197
|
# Merge options with current params, filter safe params, and stringify to query string
|
210
198
|
def qparams(options)
|
data/lib/sidekiq/web.rb
CHANGED
@@ -143,13 +143,14 @@ module Sidekiq
|
|
143
143
|
klass = self.class
|
144
144
|
m = middlewares
|
145
145
|
|
146
|
+
rules = []
|
147
|
+
rules = [[:all, {"Cache-Control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
|
148
|
+
|
146
149
|
::Rack::Builder.new do
|
147
|
-
use Rack::Static, :
|
148
|
-
:
|
149
|
-
:
|
150
|
-
:
|
151
|
-
[:all, {'Cache-Control' => 'public, max-age=86400'}],
|
152
|
-
]
|
150
|
+
use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
|
151
|
+
root: ASSETS,
|
152
|
+
cascade: true,
|
153
|
+
header_rules: rules
|
153
154
|
m.each { |middleware, block| use(*middleware, &block) }
|
154
155
|
use Sidekiq::Web::CsrfProtection unless $TESTING
|
155
156
|
run WebApplication.new(klass)
|
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
|
@@ -153,10 +174,16 @@ module Sidekiq
|
|
153
174
|
def initialize(klass, opts)
|
154
175
|
@klass = klass
|
155
176
|
@opts = opts
|
177
|
+
|
178
|
+
# ActiveJob compatibility
|
179
|
+
interval = @opts.delete(:wait_until) || @opts.delete(:wait)
|
180
|
+
at(interval) if interval
|
156
181
|
end
|
157
182
|
|
158
183
|
def set(options)
|
184
|
+
interval = options.delete(:wait_until) || options.delete(:wait)
|
159
185
|
@opts.merge!(options)
|
186
|
+
at(interval) if interval
|
160
187
|
self
|
161
188
|
end
|
162
189
|
|
@@ -164,19 +191,29 @@ module Sidekiq
|
|
164
191
|
@klass.client_push(@opts.merge("args" => args, "class" => @klass))
|
165
192
|
end
|
166
193
|
|
194
|
+
def perform_bulk(args, batch_size: 1_000)
|
195
|
+
args.each_slice(batch_size).flat_map do |slice|
|
196
|
+
Sidekiq::Client.push_bulk(@opts.merge("class" => @klass, "args" => slice))
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
167
200
|
# +interval+ must be a timestamp, numeric or something that acts
|
168
201
|
# numeric (like an activesupport time interval).
|
169
202
|
def perform_in(interval, *args)
|
203
|
+
at(interval).perform_async(*args)
|
204
|
+
end
|
205
|
+
alias_method :perform_at, :perform_in
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def at(interval)
|
170
210
|
int = interval.to_f
|
171
211
|
now = Time.now.to_f
|
172
212
|
ts = (int < 1_000_000_000 ? now + int : int)
|
173
|
-
|
174
|
-
payload = @opts.merge("class" => @klass, "args" => args)
|
175
213
|
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
176
|
-
|
177
|
-
|
214
|
+
@opts["at"] = ts if ts > now
|
215
|
+
self
|
178
216
|
end
|
179
|
-
alias_method :perform_at, :perform_in
|
180
217
|
end
|
181
218
|
|
182
219
|
module ClassMethods
|
@@ -192,6 +229,10 @@ module Sidekiq
|
|
192
229
|
raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at"
|
193
230
|
end
|
194
231
|
|
232
|
+
def queue_as(q)
|
233
|
+
sidekiq_options("queue" => q.to_s)
|
234
|
+
end
|
235
|
+
|
195
236
|
def set(options)
|
196
237
|
Setter.new(self, options)
|
197
238
|
end
|
@@ -200,6 +241,32 @@ module Sidekiq
|
|
200
241
|
client_push("class" => self, "args" => args)
|
201
242
|
end
|
202
243
|
|
244
|
+
##
|
245
|
+
# Push a large number of jobs to Redis, while limiting the batch of
|
246
|
+
# each job payload to 1,000. This method helps cut down on the number
|
247
|
+
# of round trips to Redis, which can increase the performance of enqueueing
|
248
|
+
# large numbers of jobs.
|
249
|
+
#
|
250
|
+
# +items+ must be an Array of Arrays.
|
251
|
+
#
|
252
|
+
# For finer-grained control, use `Sidekiq::Client.push_bulk` directly.
|
253
|
+
#
|
254
|
+
# Example (3 Redis round trips):
|
255
|
+
#
|
256
|
+
# SomeWorker.perform_async(1)
|
257
|
+
# SomeWorker.perform_async(2)
|
258
|
+
# SomeWorker.perform_async(3)
|
259
|
+
#
|
260
|
+
# Would instead become (1 Redis round trip):
|
261
|
+
#
|
262
|
+
# SomeWorker.perform_bulk([[1], [2], [3]])
|
263
|
+
#
|
264
|
+
def perform_bulk(items, batch_size: 1_000)
|
265
|
+
items.each_slice(batch_size).flat_map do |slice|
|
266
|
+
Sidekiq::Client.push_bulk("class" => self, "args" => slice)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
203
270
|
# +interval+ must be a timestamp, numeric or something that acts
|
204
271
|
# numeric (like an activesupport time interval).
|
205
272
|
def perform_in(interval, *args)
|
data/lib/sidekiq.rb
CHANGED
@@ -6,6 +6,7 @@ fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.5.0." i
|
|
6
6
|
require "sidekiq/logger"
|
7
7
|
require "sidekiq/client"
|
8
8
|
require "sidekiq/worker"
|
9
|
+
require "sidekiq/job"
|
9
10
|
require "sidekiq/redis_connection"
|
10
11
|
require "sidekiq/delay"
|
11
12
|
|
@@ -100,7 +101,8 @@ module Sidekiq
|
|
100
101
|
# 2550 Failover can cause the server to become a replica, need
|
101
102
|
# to disconnect and reopen the socket to get back to the primary.
|
102
103
|
# 4495 Use the same logic if we have a "Not enough replicas" error from the primary
|
103
|
-
|
104
|
+
# 4985 Use the same logic when a blocking command is force-unblocked
|
105
|
+
if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
|
104
106
|
conn.disconnect!
|
105
107
|
retryable = false
|
106
108
|
retry
|
data/sidekiq.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
|
|
18
18
|
"homepage_uri" => "https://sidekiq.org",
|
19
19
|
"bug_tracker_uri" => "https://github.com/mperham/sidekiq/issues",
|
20
20
|
"documentation_uri" => "https://github.com/mperham/sidekiq/wiki",
|
21
|
-
"changelog_uri" => "https://github.com/mperham/sidekiq/blob/
|
21
|
+
"changelog_uri" => "https://github.com/mperham/sidekiq/blob/main/Changes.md",
|
22
22
|
"source_code_uri" => "https://github.com/mperham/sidekiq"
|
23
23
|
}
|
24
24
|
|