sidekiq 6.5.12 → 7.3.9
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.
- checksums.yaml +4 -4
- data/Changes.md +340 -20
- data/README.md +43 -35
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiq +3 -8
- data/bin/sidekiqload +213 -118
- data/bin/sidekiqmon +3 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/sidekiq/api.rb +243 -162
- data/lib/sidekiq/capsule.rb +132 -0
- data/lib/sidekiq/cli.rb +60 -75
- data/lib/sidekiq/client.rb +87 -38
- data/lib/sidekiq/component.rb +26 -1
- data/lib/sidekiq/config.rb +311 -0
- data/lib/sidekiq/deploy.rb +64 -0
- data/lib/sidekiq/embedded.rb +63 -0
- data/lib/sidekiq/fetch.rb +11 -14
- data/lib/sidekiq/iterable_job.rb +55 -0
- data/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +294 -0
- data/lib/sidekiq/job.rb +382 -10
- data/lib/sidekiq/job_logger.rb +8 -7
- data/lib/sidekiq/job_retry.rb +42 -19
- data/lib/sidekiq/job_util.rb +53 -15
- data/lib/sidekiq/launcher.rb +71 -65
- data/lib/sidekiq/logger.rb +2 -27
- data/lib/sidekiq/manager.rb +9 -11
- data/lib/sidekiq/metrics/query.rb +9 -4
- data/lib/sidekiq/metrics/shared.rb +21 -9
- data/lib/sidekiq/metrics/tracking.rb +40 -26
- data/lib/sidekiq/middleware/chain.rb +19 -18
- data/lib/sidekiq/middleware/current_attributes.rb +85 -20
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +18 -4
- data/lib/sidekiq/paginator.rb +8 -2
- data/lib/sidekiq/processor.rb +62 -57
- data/lib/sidekiq/rails.rb +27 -10
- data/lib/sidekiq/redis_client_adapter.rb +31 -71
- data/lib/sidekiq/redis_connection.rb +44 -115
- data/lib/sidekiq/ring_buffer.rb +2 -0
- data/lib/sidekiq/scheduled.rb +22 -23
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/testing.rb +37 -46
- data/lib/sidekiq/transaction_aware_client.rb +11 -5
- data/lib/sidekiq/version.rb +6 -1
- data/lib/sidekiq/web/action.rb +29 -7
- data/lib/sidekiq/web/application.rb +82 -28
- data/lib/sidekiq/web/csrf_protection.rb +10 -7
- data/lib/sidekiq/web/helpers.rb +110 -49
- data/lib/sidekiq/web/router.rb +5 -2
- data/lib/sidekiq/web.rb +70 -17
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +78 -274
- data/sidekiq.gemspec +13 -10
- data/web/assets/javascripts/application.js +44 -0
- data/web/assets/javascripts/base-charts.js +106 -0
- data/web/assets/javascripts/dashboard-charts.js +194 -0
- data/web/assets/javascripts/dashboard.js +17 -233
- data/web/assets/javascripts/metrics.js +151 -115
- data/web/assets/stylesheets/application-dark.css +4 -0
- data/web/assets/stylesheets/application-rtl.css +10 -89
- data/web/assets/stylesheets/application.css +56 -296
- data/web/locales/ar.yml +70 -70
- data/web/locales/cs.yml +62 -62
- data/web/locales/da.yml +60 -53
- data/web/locales/de.yml +65 -65
- data/web/locales/el.yml +2 -7
- data/web/locales/en.yml +81 -71
- data/web/locales/es.yml +68 -68
- data/web/locales/fa.yml +65 -65
- data/web/locales/fr.yml +80 -67
- data/web/locales/gd.yml +98 -0
- data/web/locales/he.yml +65 -64
- data/web/locales/hi.yml +59 -59
- data/web/locales/it.yml +85 -54
- data/web/locales/ja.yml +67 -70
- data/web/locales/ko.yml +52 -52
- data/web/locales/lt.yml +66 -66
- data/web/locales/nb.yml +61 -61
- data/web/locales/nl.yml +52 -52
- data/web/locales/pl.yml +45 -45
- data/web/locales/pt-br.yml +78 -69
- data/web/locales/pt.yml +51 -51
- data/web/locales/ru.yml +67 -66
- data/web/locales/sv.yml +53 -53
- data/web/locales/ta.yml +60 -60
- data/web/locales/tr.yml +100 -0
- data/web/locales/uk.yml +85 -61
- data/web/locales/ur.yml +64 -64
- data/web/locales/vi.yml +67 -67
- data/web/locales/zh-cn.yml +20 -19
- data/web/locales/zh-tw.yml +10 -2
- data/web/views/_footer.erb +16 -2
- data/web/views/_job_info.erb +18 -2
- data/web/views/_metrics_period_select.erb +12 -0
- data/web/views/_paging.erb +2 -0
- data/web/views/_poll_link.erb +1 -1
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +46 -35
- data/web/views/dashboard.erb +32 -8
- data/web/views/filtering.erb +6 -0
- data/web/views/layout.erb +6 -6
- data/web/views/metrics.erb +47 -26
- data/web/views/metrics_for_job.erb +43 -71
- data/web/views/morgue.erb +7 -11
- data/web/views/queue.erb +11 -15
- data/web/views/queues.erb +9 -3
- data/web/views/retries.erb +5 -9
- data/web/views/scheduled.erb +12 -13
- metadata +66 -41
- data/lib/sidekiq/delay.rb +0 -43
- data/lib/sidekiq/extensions/action_mailer.rb +0 -48
- data/lib/sidekiq/extensions/active_record.rb +0 -43
- data/lib/sidekiq/extensions/class_methods.rb +0 -43
- data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
- data/lib/sidekiq/metrics/deploy.rb +0 -47
- data/lib/sidekiq/worker.rb +0 -370
- data/web/assets/javascripts/graph.js +0 -16
- /data/{LICENSE → LICENSE.txt} +0 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sidekiq/component"
|
4
|
+
|
5
|
+
module Sidekiq
|
6
|
+
# A Sidekiq::Capsule is the set of resources necessary to
|
7
|
+
# process one or more queues with a given concurrency.
|
8
|
+
# One "default" Capsule is started but the user may declare additional
|
9
|
+
# Capsules in their initializer.
|
10
|
+
#
|
11
|
+
# This capsule will pull jobs from the "single" queue and process
|
12
|
+
# the jobs with one thread, meaning the jobs will be processed serially.
|
13
|
+
#
|
14
|
+
# Sidekiq.configure_server do |config|
|
15
|
+
# config.capsule("single-threaded") do |cap|
|
16
|
+
# cap.concurrency = 1
|
17
|
+
# cap.queues = %w(single)
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
class Capsule
|
21
|
+
include Sidekiq::Component
|
22
|
+
extend Forwardable
|
23
|
+
|
24
|
+
attr_reader :name
|
25
|
+
attr_reader :queues
|
26
|
+
attr_accessor :concurrency
|
27
|
+
attr_reader :mode
|
28
|
+
attr_reader :weights
|
29
|
+
|
30
|
+
def_delegators :@config, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig
|
31
|
+
|
32
|
+
def initialize(name, config)
|
33
|
+
@name = name
|
34
|
+
@config = config
|
35
|
+
@queues = ["default"]
|
36
|
+
@weights = {"default" => 0}
|
37
|
+
@concurrency = config[:concurrency]
|
38
|
+
@mode = :strict
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetcher
|
42
|
+
@fetcher ||= begin
|
43
|
+
instance = (config[:fetch_class] || Sidekiq::BasicFetch).new(self)
|
44
|
+
instance.setup(config[:fetch_setup]) if instance.respond_to?(:setup)
|
45
|
+
instance
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def stop
|
50
|
+
fetcher&.bulk_requeue([])
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sidekiq checks queues in three modes:
|
54
|
+
# - :strict - all queues have 0 weight and are checked strictly in order
|
55
|
+
# - :weighted - queues have arbitrary weight between 1 and N
|
56
|
+
# - :random - all queues have weight of 1
|
57
|
+
def queues=(val)
|
58
|
+
@weights = {}
|
59
|
+
@queues = Array(val).each_with_object([]) do |qstr, memo|
|
60
|
+
arr = qstr
|
61
|
+
arr = qstr.split(",") if qstr.is_a?(String)
|
62
|
+
name, weight = arr
|
63
|
+
@weights[name] = weight.to_i
|
64
|
+
[weight.to_i, 1].max.times do
|
65
|
+
memo << name
|
66
|
+
end
|
67
|
+
end
|
68
|
+
@mode = if @weights.values.all?(&:zero?)
|
69
|
+
:strict
|
70
|
+
elsif @weights.values.all? { |x| x == 1 }
|
71
|
+
:random
|
72
|
+
else
|
73
|
+
:weighted
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Allow the middleware to be different per-capsule.
|
78
|
+
# Avoid if possible and add middleware globally so all
|
79
|
+
# capsules share the same chains. Easier to debug that way.
|
80
|
+
def client_middleware
|
81
|
+
@client_chain ||= config.client_middleware.copy_for(self)
|
82
|
+
yield @client_chain if block_given?
|
83
|
+
@client_chain
|
84
|
+
end
|
85
|
+
|
86
|
+
def server_middleware
|
87
|
+
@server_chain ||= config.server_middleware.copy_for(self)
|
88
|
+
yield @server_chain if block_given?
|
89
|
+
@server_chain
|
90
|
+
end
|
91
|
+
|
92
|
+
def redis_pool
|
93
|
+
Thread.current[:sidekiq_redis_pool] || local_redis_pool
|
94
|
+
end
|
95
|
+
|
96
|
+
def local_redis_pool
|
97
|
+
# connection pool is lazy, it will not create connections unless you actually need them
|
98
|
+
# so don't be skimpy!
|
99
|
+
@redis ||= config.new_redis_pool(@concurrency, name)
|
100
|
+
end
|
101
|
+
|
102
|
+
def redis
|
103
|
+
raise ArgumentError, "requires a block" unless block_given?
|
104
|
+
redis_pool.with do |conn|
|
105
|
+
retryable = true
|
106
|
+
begin
|
107
|
+
yield conn
|
108
|
+
rescue RedisClientAdapter::BaseError => ex
|
109
|
+
# 2550 Failover can cause the server to become a replica, need
|
110
|
+
# to disconnect and reopen the socket to get back to the primary.
|
111
|
+
# 4495 Use the same logic if we have a "Not enough replicas" error from the primary
|
112
|
+
# 4985 Use the same logic when a blocking command is force-unblocked
|
113
|
+
# The same retry logic is also used in client.rb
|
114
|
+
if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
|
115
|
+
conn.close
|
116
|
+
retryable = false
|
117
|
+
retry
|
118
|
+
end
|
119
|
+
raise
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def lookup(name)
|
125
|
+
config.lookup(name)
|
126
|
+
end
|
127
|
+
|
128
|
+
def logger
|
129
|
+
config.logger
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/sidekiq/cli.rb
CHANGED
@@ -9,20 +9,11 @@ require "erb"
|
|
9
9
|
require "fileutils"
|
10
10
|
|
11
11
|
require "sidekiq"
|
12
|
+
require "sidekiq/config"
|
12
13
|
require "sidekiq/component"
|
14
|
+
require "sidekiq/capsule"
|
13
15
|
require "sidekiq/launcher"
|
14
16
|
|
15
|
-
# module ScoutApm
|
16
|
-
# VERSION = "5.3.1"
|
17
|
-
# end
|
18
|
-
fail <<~EOM if defined?(ScoutApm::VERSION) && ScoutApm::VERSION < "5.2.0"
|
19
|
-
|
20
|
-
|
21
|
-
scout_apm v#{ScoutApm::VERSION} is unsafe with Sidekiq 6.5. Please run `bundle up scout_apm` to upgrade to 5.2.0 or greater.
|
22
|
-
|
23
|
-
|
24
|
-
EOM
|
25
|
-
|
26
17
|
module Sidekiq # :nodoc:
|
27
18
|
class CLI
|
28
19
|
include Sidekiq::Component
|
@@ -33,9 +24,7 @@ module Sidekiq # :nodoc:
|
|
33
24
|
attr_accessor :config
|
34
25
|
|
35
26
|
def parse(args = ARGV.dup)
|
36
|
-
@config
|
37
|
-
@config[:error_handlers].clear
|
38
|
-
@config[:error_handlers] << @config.method(:default_error_handler)
|
27
|
+
@config ||= Sidekiq.default_configuration
|
39
28
|
|
40
29
|
setup_options(args)
|
41
30
|
initialize_logger
|
@@ -49,10 +38,10 @@ module Sidekiq # :nodoc:
|
|
49
38
|
# Code within this method is not tested because it alters
|
50
39
|
# global process state irreversibly. PRs which improve the
|
51
40
|
# test coverage of Sidekiq::CLI are welcomed.
|
52
|
-
def run(boot_app: true)
|
41
|
+
def run(boot_app: true, warmup: true)
|
53
42
|
boot_application if boot_app
|
54
43
|
|
55
|
-
if environment == "development" && $stdout.tty? && @config.
|
44
|
+
if environment == "development" && $stdout.tty? && @config.logger.formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
|
56
45
|
print_banner
|
57
46
|
end
|
58
47
|
logger.info "Booted Rails #{::Rails.version} application in #{environment} environment" if rails_app?
|
@@ -84,26 +73,27 @@ module Sidekiq # :nodoc:
|
|
84
73
|
# touch the connection pool so it is created before we
|
85
74
|
# fire startup and start multithreading.
|
86
75
|
info = @config.redis_info
|
87
|
-
ver = info["redis_version"]
|
88
|
-
raise "You are connecting to Redis
|
76
|
+
ver = Gem::Version.new(info["redis_version"])
|
77
|
+
raise "You are connecting to Redis #{ver}, Sidekiq requires Redis 6.2.0 or greater" if ver < Gem::Version.new("6.2.0")
|
89
78
|
|
90
79
|
maxmemory_policy = info["maxmemory_policy"]
|
91
|
-
if maxmemory_policy != "noeviction"
|
80
|
+
if maxmemory_policy != "noeviction" && maxmemory_policy != ""
|
81
|
+
# Redis Enterprise Cloud returns "" for their policy 😳
|
92
82
|
logger.warn <<~EOM
|
93
83
|
|
94
84
|
|
95
85
|
WARNING: Your Redis instance will evict Sidekiq data under heavy load.
|
96
86
|
The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
|
97
|
-
See: https://github.com/
|
87
|
+
See: https://github.com/sidekiq/sidekiq/wiki/Using-Redis#memory
|
98
88
|
|
99
89
|
EOM
|
100
90
|
end
|
101
91
|
|
102
92
|
# Since the user can pass us a connection pool explicitly in the initializer, we
|
103
93
|
# need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
|
104
|
-
|
105
|
-
|
106
|
-
|
94
|
+
@config.capsules.each_pair do |name, cap|
|
95
|
+
raise ArgumentError, "Pool size too small for #{name}" if cap.redis_pool.size < cap.concurrency
|
96
|
+
end
|
107
97
|
|
108
98
|
# cache process identity
|
109
99
|
@config[:identity] = identity
|
@@ -111,12 +101,14 @@ module Sidekiq # :nodoc:
|
|
111
101
|
# Touch middleware so it isn't lazy loaded by multiple threads, #3043
|
112
102
|
@config.server_middleware
|
113
103
|
|
104
|
+
::Process.warmup if warmup && ::Process.respond_to?(:warmup) && ENV["RUBY_DISABLE_WARMUP"] != "1"
|
105
|
+
|
114
106
|
# Before this point, the process is initializing with just the main thread.
|
115
107
|
# Starting here the process will now have multiple threads running.
|
116
108
|
fire_event(:startup, reverse: false, reraise: true)
|
117
109
|
|
118
|
-
logger.debug { "Client Middleware: #{@config.client_middleware.map(&:klass).join(", ")}" }
|
119
|
-
logger.debug { "Server Middleware: #{@config.server_middleware.map(&:klass).join(", ")}" }
|
110
|
+
logger.debug { "Client Middleware: #{@config.default_capsule.client_middleware.map(&:klass).join(", ")}" }
|
111
|
+
logger.debug { "Server Middleware: #{@config.default_capsule.server_middleware.map(&:klass).join(", ")}" }
|
120
112
|
|
121
113
|
launch(self_read)
|
122
114
|
end
|
@@ -149,19 +141,34 @@ module Sidekiq # :nodoc:
|
|
149
141
|
end
|
150
142
|
end
|
151
143
|
|
152
|
-
|
153
|
-
|
144
|
+
HOLIDAY_COLORS = {
|
145
|
+
# got other color-specific holidays from around the world?
|
146
|
+
# https://developer-book.com/post/definitive-guide-for-colored-text-in-terminal/#256-color-escape-codes
|
147
|
+
"3-17" => "\e[1;32m", # St. Patrick's Day green
|
148
|
+
"10-31" => "\e[38;5;208m" # Halloween orange
|
149
|
+
}
|
150
|
+
|
151
|
+
def self.day
|
152
|
+
@@day ||= begin
|
153
|
+
t = Date.today
|
154
|
+
"#{t.month}-#{t.day}"
|
155
|
+
end
|
154
156
|
end
|
155
157
|
|
156
158
|
def self.r
|
157
|
-
"\e[31m"
|
159
|
+
@@r ||= HOLIDAY_COLORS[day] || "\e[1;31m"
|
158
160
|
end
|
159
161
|
|
160
162
|
def self.b
|
161
|
-
"\e[30m"
|
163
|
+
@@b ||= HOLIDAY_COLORS[day] || "\e[30m"
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.w
|
167
|
+
"\e[1;37m"
|
162
168
|
end
|
163
169
|
|
164
170
|
def self.reset
|
171
|
+
@@b = @@r = @@day = nil
|
165
172
|
"\e[0m"
|
166
173
|
end
|
167
174
|
|
@@ -174,7 +181,7 @@ module Sidekiq # :nodoc:
|
|
174
181
|
#{w} ,$$$$$b#{b}/#{w}md$$$P^'
|
175
182
|
#{w} .d$$$$$$#{b}/#{w}$$$P'
|
176
183
|
#{w} $$^' `"#{b}/#{w}$$$' #{r}____ _ _ _ _
|
177
|
-
#{w} $:
|
184
|
+
#{w} $: #{b}'#{w},$$: #{r} / ___|(_) __| | ___| | _(_) __ _
|
178
185
|
#{w} `b :$$ #{r} \\___ \\| |/ _` |/ _ \\ |/ / |/ _` |
|
179
186
|
#{w} $$: #{r} ___) | | (_| | __/ <| | (_| |
|
180
187
|
#{w} $$ #{r}|____/|_|\\__,_|\\___|_|\\_\\_|\\__, |
|
@@ -272,6 +279,18 @@ module Sidekiq # :nodoc:
|
|
272
279
|
|
273
280
|
# merge with defaults
|
274
281
|
@config.merge!(opts)
|
282
|
+
|
283
|
+
@config.default_capsule.tap do |cap|
|
284
|
+
cap.queues = opts[:queues]
|
285
|
+
cap.concurrency = opts[:concurrency] || @config[:concurrency]
|
286
|
+
end
|
287
|
+
|
288
|
+
opts[:capsules]&.each do |name, cap_config|
|
289
|
+
@config.capsule(name.to_s) do |cap|
|
290
|
+
cap.queues = cap_config[:queues]
|
291
|
+
cap.concurrency = cap_config[:concurrency]
|
292
|
+
end
|
293
|
+
end
|
275
294
|
end
|
276
295
|
|
277
296
|
def boot_application
|
@@ -279,12 +298,11 @@ module Sidekiq # :nodoc:
|
|
279
298
|
|
280
299
|
if File.directory?(@config[:require])
|
281
300
|
require "rails"
|
282
|
-
if ::Rails::VERSION::MAJOR <
|
283
|
-
|
284
|
-
else
|
285
|
-
require "sidekiq/rails"
|
286
|
-
require File.expand_path("#{@config[:require]}/config/environment.rb")
|
301
|
+
if ::Rails::VERSION::MAJOR < 6
|
302
|
+
warn "Sidekiq #{Sidekiq::VERSION} only supports Rails 6+"
|
287
303
|
end
|
304
|
+
require "sidekiq/rails"
|
305
|
+
require File.expand_path("#{@config[:require]}/config/environment.rb")
|
288
306
|
@config[:tag] ||= default_tag
|
289
307
|
else
|
290
308
|
require @config[:require]
|
@@ -332,10 +350,6 @@ module Sidekiq # :nodoc:
|
|
332
350
|
opts[:concurrency] = Integer(arg)
|
333
351
|
end
|
334
352
|
|
335
|
-
o.on "-d", "--daemon", "Daemonize process" do |arg|
|
336
|
-
puts "ERROR: Daemonization mode was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services"
|
337
|
-
end
|
338
|
-
|
339
353
|
o.on "-e", "--environment ENV", "Application environment" do |arg|
|
340
354
|
opts[:environment] = arg
|
341
355
|
end
|
@@ -345,8 +359,8 @@ module Sidekiq # :nodoc:
|
|
345
359
|
end
|
346
360
|
|
347
361
|
o.on "-q", "--queue QUEUE[,WEIGHT]", "Queues to process with optional weights" do |arg|
|
348
|
-
|
349
|
-
|
362
|
+
opts[:queues] ||= []
|
363
|
+
opts[:queues] << arg
|
350
364
|
end
|
351
365
|
|
352
366
|
o.on "-r", "--require [PATH|DIR]", "Location of Rails application with jobs or file to require" do |arg|
|
@@ -365,15 +379,7 @@ module Sidekiq # :nodoc:
|
|
365
379
|
opts[:config_file] = arg
|
366
380
|
end
|
367
381
|
|
368
|
-
o.on "-
|
369
|
-
puts "ERROR: Logfile redirection was removed in Sidekiq 6.0, Sidekiq will only log to STDOUT"
|
370
|
-
end
|
371
|
-
|
372
|
-
o.on "-P", "--pidfile PATH", "path to pidfile" do |arg|
|
373
|
-
puts "ERROR: PID file creation was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services"
|
374
|
-
end
|
375
|
-
|
376
|
-
o.on "-V", "--version", "Print version and exit" do |arg|
|
382
|
+
o.on "-V", "--version", "Print version and exit" do
|
377
383
|
puts "Sidekiq #{Sidekiq::VERSION}"
|
378
384
|
die(0)
|
379
385
|
end
|
@@ -393,9 +399,9 @@ module Sidekiq # :nodoc:
|
|
393
399
|
end
|
394
400
|
|
395
401
|
def parse_config(path)
|
396
|
-
erb = ERB.new(File.read(path))
|
402
|
+
erb = ERB.new(File.read(path), trim_mode: "-")
|
397
403
|
erb.filename = File.expand_path(path)
|
398
|
-
opts =
|
404
|
+
opts = YAML.safe_load(erb.result, permitted_classes: [Symbol], aliases: true) || {}
|
399
405
|
|
400
406
|
if opts.respond_to? :deep_symbolize_keys!
|
401
407
|
opts.deep_symbolize_keys!
|
@@ -406,31 +412,9 @@ module Sidekiq # :nodoc:
|
|
406
412
|
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
407
413
|
opts.delete(:strict)
|
408
414
|
|
409
|
-
parse_queues(opts, opts.delete(:queues) || [])
|
410
|
-
|
411
415
|
opts
|
412
416
|
end
|
413
417
|
|
414
|
-
def load_yaml(src)
|
415
|
-
if Psych::VERSION > "4.0"
|
416
|
-
YAML.safe_load(src, permitted_classes: [Symbol], aliases: true)
|
417
|
-
else
|
418
|
-
YAML.load(src)
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
|
-
def parse_queues(opts, queues_and_weights)
|
423
|
-
queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
|
424
|
-
end
|
425
|
-
|
426
|
-
def parse_queue(opts, queue, weight = nil)
|
427
|
-
opts[:queues] ||= []
|
428
|
-
opts[:strict] = true if opts[:strict].nil?
|
429
|
-
raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
|
430
|
-
[weight.to_i, 1].max.times { opts[:queues] << queue.to_s }
|
431
|
-
opts[:strict] = false if weight.to_i > 0
|
432
|
-
end
|
433
|
-
|
434
418
|
def rails_app?
|
435
419
|
defined?(::Rails) && ::Rails.respond_to?(:application)
|
436
420
|
end
|
@@ -438,4 +422,5 @@ module Sidekiq # :nodoc:
|
|
438
422
|
end
|
439
423
|
|
440
424
|
require "sidekiq/systemd"
|
441
|
-
require "sidekiq/metrics/tracking"
|
425
|
+
require "sidekiq/metrics/tracking"
|
426
|
+
require "sidekiq/job/interrupt_handler"
|
data/lib/sidekiq/client.rb
CHANGED
@@ -21,7 +21,6 @@ module Sidekiq
|
|
21
21
|
# Sidekiq.client_middleware but you can change as necessary.
|
22
22
|
#
|
23
23
|
def middleware(&block)
|
24
|
-
@chain ||= Sidekiq.client_middleware
|
25
24
|
if block
|
26
25
|
@chain = @chain.dup
|
27
26
|
yield @chain
|
@@ -31,18 +30,49 @@ module Sidekiq
|
|
31
30
|
|
32
31
|
attr_accessor :redis_pool
|
33
32
|
|
34
|
-
# Sidekiq::Client
|
35
|
-
#
|
36
|
-
# Sidekiq jobs across several Redis instances (for scalability
|
37
|
-
# reasons, e.g.)
|
33
|
+
# Sidekiq::Client is responsible for pushing job payloads to Redis.
|
34
|
+
# Requires the :pool or :config keyword argument.
|
38
35
|
#
|
39
|
-
# Sidekiq::Client.new(
|
36
|
+
# Sidekiq::Client.new(pool: Sidekiq::RedisConnection.create)
|
40
37
|
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
|
45
|
-
|
38
|
+
# Inside the Sidekiq process, you can reuse the configured resources:
|
39
|
+
#
|
40
|
+
# Sidekiq::Client.new(config: config)
|
41
|
+
#
|
42
|
+
# @param pool [ConnectionPool] explicit Redis pool to use
|
43
|
+
# @param config [Sidekiq::Config] use the pool and middleware from the given Sidekiq container
|
44
|
+
# @param chain [Sidekiq::Middleware::Chain] use the given middleware chain
|
45
|
+
def initialize(*args, **kwargs)
|
46
|
+
if args.size == 1 && kwargs.size == 0
|
47
|
+
warn "Sidekiq::Client.new(pool) is deprecated, please use Sidekiq::Client.new(pool: pool), #{caller(0..3)}"
|
48
|
+
# old calling method, accept 1 pool argument
|
49
|
+
@redis_pool = args[0]
|
50
|
+
@chain = Sidekiq.default_configuration.client_middleware
|
51
|
+
@config = Sidekiq.default_configuration
|
52
|
+
else
|
53
|
+
# new calling method: keyword arguments
|
54
|
+
@config = kwargs[:config] || Sidekiq.default_configuration
|
55
|
+
@redis_pool = kwargs[:pool] || Thread.current[:sidekiq_redis_pool] || @config&.redis_pool
|
56
|
+
@chain = kwargs[:chain] || @config&.client_middleware
|
57
|
+
raise ArgumentError, "No Redis pool available for Sidekiq::Client" unless @redis_pool
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Cancel the IterableJob with the given JID.
|
62
|
+
# **NB: Cancellation is asynchronous.** Iteration checks every
|
63
|
+
# five seconds so this will not immediately stop the given job.
|
64
|
+
def cancel!(jid)
|
65
|
+
key = "it-#{jid}"
|
66
|
+
_, result, _ = Sidekiq.redis do |c|
|
67
|
+
c.pipelined do |p|
|
68
|
+
p.hsetnx(key, "cancelled", Time.now.to_i)
|
69
|
+
p.hget(key, "cancelled")
|
70
|
+
p.expire(key, Sidekiq::Job::Iterable::STATE_TTL)
|
71
|
+
# TODO When Redis 7.2 is required
|
72
|
+
# p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
result.to_i
|
46
76
|
end
|
47
77
|
|
48
78
|
##
|
@@ -53,6 +83,7 @@ module Sidekiq
|
|
53
83
|
# args - an array of simple arguments to the perform method, must be JSON-serializable
|
54
84
|
# at - timestamp to schedule the job (optional), must be Numeric (e.g. Time.now.to_f)
|
55
85
|
# retry - whether to retry this job if it fails, default true or an integer number of retries
|
86
|
+
# retry_for - relative amount of time to retry this job if it fails, default nil
|
56
87
|
# backtrace - whether to save any error backtrace, default false
|
57
88
|
#
|
58
89
|
# If class is set to the class name, the jobs' options will be based on Sidekiq's default
|
@@ -60,7 +91,7 @@ module Sidekiq
|
|
60
91
|
#
|
61
92
|
# Any options valid for a job class's sidekiq_options are also available here.
|
62
93
|
#
|
63
|
-
# All
|
94
|
+
# All keys must be strings, not symbols. NB: because we are serializing to JSON, all
|
64
95
|
# symbols in 'args' will be converted to strings. Note that +backtrace: true+ can take quite a bit of
|
65
96
|
# space in Redis; a large volume of failing jobs can start Redis swapping if you aren't careful.
|
66
97
|
#
|
@@ -83,8 +114,9 @@ module Sidekiq
|
|
83
114
|
|
84
115
|
##
|
85
116
|
# Push a large number of jobs to Redis. This method cuts out the redis
|
86
|
-
# network round trip latency.
|
87
|
-
# 1000
|
117
|
+
# network round trip latency. It pushes jobs in batches if more than
|
118
|
+
# `:batch_size` (1000 by default) of jobs are passed. I wouldn't recommend making `:batch_size`
|
119
|
+
# larger than 1000 but YMMV based on network quality, size of job args, etc.
|
88
120
|
# A large number of jobs can cause a bit of Redis command processing latency.
|
89
121
|
#
|
90
122
|
# Takes the same arguments as #push except that args is expected to be
|
@@ -92,13 +124,15 @@ module Sidekiq
|
|
92
124
|
# is run through the client middleware pipeline and each job gets its own Job ID
|
93
125
|
# as normal.
|
94
126
|
#
|
95
|
-
# Returns an array of the of pushed jobs' jids
|
96
|
-
#
|
127
|
+
# Returns an array of the of pushed jobs' jids, may contain nils if any client middleware
|
128
|
+
# prevented a job push.
|
129
|
+
#
|
130
|
+
# Example (pushing jobs in batches):
|
131
|
+
# push_bulk('class' => MyJob, 'args' => (1..100_000).to_a, batch_size: 1_000)
|
132
|
+
#
|
97
133
|
def push_bulk(items)
|
134
|
+
batch_size = items.delete(:batch_size) || items.delete("batch_size") || 1_000
|
98
135
|
args = items["args"]
|
99
|
-
raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless args.is_a?(Array) && args.all?(Array)
|
100
|
-
return [] if args.empty? # no jobs to push
|
101
|
-
|
102
136
|
at = items.delete("at")
|
103
137
|
raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all? { |entry| entry.is_a?(Numeric) })
|
104
138
|
raise ArgumentError, "Job 'at' Array must have same size as 'args' Array" if at.is_a?(Array) && at.size != args.size
|
@@ -107,18 +141,28 @@ module Sidekiq
|
|
107
141
|
raise ArgumentError, "Explicitly passing 'jid' when pushing more than one job is not supported" if jid && args.size > 1
|
108
142
|
|
109
143
|
normed = normalize_item(items)
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
144
|
+
slice_index = 0
|
145
|
+
result = args.each_slice(batch_size).flat_map do |slice|
|
146
|
+
raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless slice.is_a?(Array) && slice.all?(Array)
|
147
|
+
break [] if slice.empty? # no jobs to push
|
148
|
+
|
149
|
+
payloads = slice.map.with_index { |job_args, index|
|
150
|
+
copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12))
|
151
|
+
copy["at"] = (at.is_a?(Array) ? at[slice_index + index] : at) if at
|
152
|
+
result = middleware.invoke(items["class"], copy, copy["queue"], @redis_pool) do
|
153
|
+
verify_json(copy)
|
154
|
+
copy
|
155
|
+
end
|
156
|
+
result || nil
|
157
|
+
}
|
158
|
+
slice_index += batch_size
|
159
|
+
|
160
|
+
to_push = payloads.compact
|
161
|
+
raw_push(to_push) unless to_push.empty?
|
162
|
+
payloads.map { |payload| payload&.[]("jid") }
|
163
|
+
end
|
119
164
|
|
120
|
-
|
121
|
-
payloads.collect { |payload| payload["jid"] }
|
165
|
+
result.is_a?(Enumerator::Lazy) ? result.force : result
|
122
166
|
end
|
123
167
|
|
124
168
|
# Allows sharding of jobs across any number of Redis instances. All jobs
|
@@ -135,11 +179,11 @@ module Sidekiq
|
|
135
179
|
# you cannot scale any other way (e.g. splitting your app into smaller apps).
|
136
180
|
def self.via(pool)
|
137
181
|
raise ArgumentError, "No pool given" if pool.nil?
|
138
|
-
current_sidekiq_pool = Thread.current[:
|
139
|
-
Thread.current[:
|
182
|
+
current_sidekiq_pool = Thread.current[:sidekiq_redis_pool]
|
183
|
+
Thread.current[:sidekiq_redis_pool] = pool
|
140
184
|
yield
|
141
185
|
ensure
|
142
|
-
Thread.current[:
|
186
|
+
Thread.current[:sidekiq_redis_pool] = current_sidekiq_pool
|
143
187
|
end
|
144
188
|
|
145
189
|
class << self
|
@@ -147,8 +191,8 @@ module Sidekiq
|
|
147
191
|
new.push(item)
|
148
192
|
end
|
149
193
|
|
150
|
-
def push_bulk(
|
151
|
-
new.push_bulk(
|
194
|
+
def push_bulk(...)
|
195
|
+
new.push_bulk(...)
|
152
196
|
end
|
153
197
|
|
154
198
|
# Resque compatibility helpers. Note all helpers
|
@@ -201,14 +245,14 @@ module Sidekiq
|
|
201
245
|
conn.pipelined do |pipeline|
|
202
246
|
atomic_push(pipeline, payloads)
|
203
247
|
end
|
204
|
-
rescue
|
248
|
+
rescue RedisClient::Error => ex
|
205
249
|
# 2550 Failover can cause the server to become a replica, need
|
206
250
|
# to disconnect and reopen the socket to get back to the primary.
|
207
251
|
# 4495 Use the same logic if we have a "Not enough replicas" error from the primary
|
208
252
|
# 4985 Use the same logic when a blocking command is force-unblocked
|
209
253
|
# The retry logic is copied from sidekiq.rb
|
210
254
|
if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
|
211
|
-
conn.
|
255
|
+
conn.close
|
212
256
|
retryable = false
|
213
257
|
retry
|
214
258
|
end
|
@@ -221,7 +265,12 @@ module Sidekiq
|
|
221
265
|
def atomic_push(conn, payloads)
|
222
266
|
if payloads.first.key?("at")
|
223
267
|
conn.zadd("schedule", payloads.flat_map { |hash|
|
224
|
-
at = hash
|
268
|
+
at = hash["at"].to_s
|
269
|
+
# ActiveJob sets this but the job has not been enqueued yet
|
270
|
+
hash.delete("enqueued_at")
|
271
|
+
# TODO: Use hash.except("at") when support for Ruby 2.7 is dropped
|
272
|
+
hash = hash.dup
|
273
|
+
hash.delete("at")
|
225
274
|
[at, Sidekiq.dump_json(hash)]
|
226
275
|
})
|
227
276
|
else
|
data/lib/sidekiq/component.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Sidekiq
|
2
4
|
##
|
3
5
|
# Sidekiq::Component assumes a config instance is available at @config
|
@@ -13,7 +15,7 @@ module Sidekiq
|
|
13
15
|
|
14
16
|
def safe_thread(name, &block)
|
15
17
|
Thread.new do
|
16
|
-
Thread.current.name = name
|
18
|
+
Thread.current.name = "sidekiq.#{name}"
|
17
19
|
watchdog(name, &block)
|
18
20
|
end
|
19
21
|
end
|
@@ -50,6 +52,7 @@ module Sidekiq
|
|
50
52
|
oneshot = options.fetch(:oneshot, true)
|
51
53
|
reverse = options[:reverse]
|
52
54
|
reraise = options[:reraise]
|
55
|
+
logger.debug("Firing #{event} event") if oneshot
|
53
56
|
|
54
57
|
arr = config[:lifecycle_events][event]
|
55
58
|
arr.reverse! if reverse
|
@@ -61,5 +64,27 @@ module Sidekiq
|
|
61
64
|
end
|
62
65
|
arr.clear if oneshot # once we've fired an event, we never fire it again
|
63
66
|
end
|
67
|
+
|
68
|
+
# When you have a large tree of components, the `inspect` output
|
69
|
+
# can get out of hand, especially with lots of Sidekiq::Config
|
70
|
+
# references everywhere. We avoid calling `inspect` on more complex
|
71
|
+
# state and use `to_s` instead to keep output manageable, #6553
|
72
|
+
def inspect
|
73
|
+
"#<#{self.class.name} #{
|
74
|
+
instance_variables.map do |name|
|
75
|
+
value = instance_variable_get(name)
|
76
|
+
case value
|
77
|
+
when Proc
|
78
|
+
"#{name}=#{value}"
|
79
|
+
when Sidekiq::Config
|
80
|
+
"#{name}=#{value}"
|
81
|
+
when Sidekiq::Component
|
82
|
+
"#{name}=#{value}"
|
83
|
+
else
|
84
|
+
"#{name}=#{value.inspect}"
|
85
|
+
end
|
86
|
+
end.join(", ")
|
87
|
+
}>"
|
88
|
+
end
|
64
89
|
end
|
65
90
|
end
|