sidekiq 6.2.2 → 8.1.5
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 +726 -11
- data/LICENSE.txt +9 -0
- data/README.md +70 -39
- data/bin/kiq +17 -0
- data/bin/lint-herb +13 -0
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiq +4 -9
- data/bin/sidekiqload +214 -115
- data/bin/sidekiqmon +4 -1
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
- data/lib/generators/sidekiq/job_generator.rb +71 -0
- data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +3 -3
- 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 +729 -264
- data/lib/sidekiq/capsule.rb +135 -0
- data/lib/sidekiq/cli.rb +124 -100
- data/lib/sidekiq/client.rb +153 -106
- data/lib/sidekiq/component.rb +132 -0
- data/lib/sidekiq/config.rb +320 -0
- data/lib/sidekiq/deploy.rb +64 -0
- data/lib/sidekiq/embedded.rb +64 -0
- data/lib/sidekiq/fetch.rb +27 -26
- data/lib/sidekiq/iterable_job.rb +56 -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 +322 -0
- data/lib/sidekiq/job.rb +397 -5
- data/lib/sidekiq/job_logger.rb +23 -32
- data/lib/sidekiq/job_retry.rb +141 -68
- data/lib/sidekiq/job_util.rb +113 -0
- data/lib/sidekiq/launcher.rb +122 -98
- data/lib/sidekiq/loader.rb +57 -0
- data/lib/sidekiq/logger.rb +27 -106
- data/lib/sidekiq/manager.rb +41 -43
- data/lib/sidekiq/metrics/query.rb +184 -0
- data/lib/sidekiq/metrics/shared.rb +109 -0
- data/lib/sidekiq/metrics/tracking.rb +153 -0
- data/lib/sidekiq/middleware/chain.rb +96 -51
- data/lib/sidekiq/middleware/current_attributes.rb +120 -0
- data/lib/sidekiq/middleware/i18n.rb +8 -4
- data/lib/sidekiq/middleware/modules.rb +23 -0
- data/lib/sidekiq/monitor.rb +16 -6
- data/lib/sidekiq/paginator.rb +37 -10
- data/lib/sidekiq/processor.rb +105 -87
- data/lib/sidekiq/profiler.rb +73 -0
- data/lib/sidekiq/rails.rb +49 -36
- data/lib/sidekiq/redis_client_adapter.rb +117 -0
- data/lib/sidekiq/redis_connection.rb +55 -86
- data/lib/sidekiq/ring_buffer.rb +32 -0
- data/lib/sidekiq/scheduled.rb +106 -50
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/test_api.rb +331 -0
- data/lib/sidekiq/testing/inline.rb +2 -30
- data/lib/sidekiq/testing.rb +2 -342
- data/lib/sidekiq/transaction_aware_client.rb +59 -0
- data/lib/sidekiq/tui/controls.rb +53 -0
- data/lib/sidekiq/tui/filtering.rb +53 -0
- data/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
- data/lib/sidekiq/tui/tabs/busy.rb +118 -0
- data/lib/sidekiq/tui/tabs/dead.rb +19 -0
- data/lib/sidekiq/tui/tabs/home.rb +144 -0
- data/lib/sidekiq/tui/tabs/metrics.rb +131 -0
- data/lib/sidekiq/tui/tabs/queues.rb +95 -0
- data/lib/sidekiq/tui/tabs/retries.rb +19 -0
- data/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
- data/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
- data/lib/sidekiq/tui/tabs.rb +15 -0
- data/lib/sidekiq/tui.rb +382 -0
- data/lib/sidekiq/version.rb +6 -1
- data/lib/sidekiq/web/action.rb +149 -64
- data/lib/sidekiq/web/application.rb +376 -268
- data/lib/sidekiq/web/config.rb +117 -0
- data/lib/sidekiq/web/helpers.rb +213 -87
- data/lib/sidekiq/web/router.rb +61 -74
- data/lib/sidekiq/web.rb +71 -100
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +95 -196
- data/sidekiq.gemspec +14 -11
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status.png +0 -0
- data/web/assets/javascripts/application.js +171 -57
- data/web/assets/javascripts/base-charts.js +120 -0
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard-charts.js +194 -0
- data/web/assets/javascripts/dashboard.js +41 -274
- data/web/assets/javascripts/metrics.js +280 -0
- data/web/assets/stylesheets/style.css +776 -0
- data/web/locales/ar.yml +72 -70
- data/web/locales/cs.yml +64 -62
- data/web/locales/da.yml +62 -53
- data/web/locales/de.yml +67 -65
- data/web/locales/el.yml +45 -24
- data/web/locales/en.yml +93 -69
- data/web/locales/es.yml +91 -68
- data/web/locales/fa.yml +67 -65
- data/web/locales/fr.yml +82 -67
- data/web/locales/gd.yml +110 -0
- data/web/locales/he.yml +67 -64
- data/web/locales/hi.yml +61 -59
- data/web/locales/it.yml +94 -54
- data/web/locales/ja.yml +74 -68
- data/web/locales/ko.yml +54 -52
- data/web/locales/lt.yml +68 -66
- data/web/locales/nb.yml +63 -61
- data/web/locales/nl.yml +54 -52
- data/web/locales/pl.yml +47 -45
- data/web/locales/{pt-br.yml → pt-BR.yml} +85 -56
- data/web/locales/pt.yml +53 -51
- data/web/locales/ru.yml +69 -66
- data/web/locales/sv.yml +55 -53
- data/web/locales/ta.yml +62 -60
- data/web/locales/tr.yml +102 -0
- data/web/locales/uk.yml +87 -61
- data/web/locales/ur.yml +66 -64
- data/web/locales/vi.yml +69 -67
- data/web/locales/zh-CN.yml +107 -0
- data/web/locales/{zh-tw.yml → zh-TW.yml} +44 -9
- data/web/views/_footer.html.erb +32 -0
- data/web/views/_job_info.html.erb +115 -0
- data/web/views/_metrics_period_select.html.erb +15 -0
- data/web/views/_nav.html.erb +45 -0
- data/web/views/_paging.html.erb +26 -0
- data/web/views/_poll_link.html.erb +4 -0
- data/web/views/_summary.html.erb +40 -0
- data/web/views/busy.html.erb +151 -0
- data/web/views/dashboard.html.erb +104 -0
- data/web/views/dead.html.erb +38 -0
- data/web/views/filtering.html.erb +6 -0
- data/web/views/layout.html.erb +26 -0
- data/web/views/metrics.html.erb +85 -0
- data/web/views/metrics_for_job.html.erb +58 -0
- data/web/views/morgue.html.erb +69 -0
- data/web/views/profiles.html.erb +43 -0
- data/web/views/queue.html.erb +57 -0
- data/web/views/queues.html.erb +46 -0
- data/web/views/retries.html.erb +77 -0
- data/web/views/retry.html.erb +39 -0
- data/web/views/scheduled.html.erb +64 -0
- data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
- metadata +130 -61
- data/LICENSE +0 -9
- data/lib/generators/sidekiq/worker_generator.rb +0 -57
- data/lib/sidekiq/delay.rb +0 -41
- data/lib/sidekiq/exception_handler.rb +0 -27
- 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/util.rb +0 -95
- data/lib/sidekiq/web/csrf_protection.rb +0 -180
- data/lib/sidekiq/worker.rb +0 -244
- data/web/assets/stylesheets/application-dark.css +0 -147
- data/web/assets/stylesheets/application-rtl.css +0 -246
- data/web/assets/stylesheets/application.css +0 -1053
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/locales/zh-cn.yml +0 -68
- data/web/views/_footer.erb +0 -20
- data/web/views/_job_info.erb +0 -89
- data/web/views/_nav.erb +0 -52
- data/web/views/_paging.erb +0 -23
- data/web/views/_poll_link.erb +0 -7
- data/web/views/_status.erb +0 -4
- data/web/views/_summary.erb +0 -40
- data/web/views/busy.erb +0 -132
- data/web/views/dashboard.erb +0 -83
- data/web/views/dead.erb +0 -34
- data/web/views/layout.erb +0 -42
- data/web/views/morgue.erb +0 -78
- data/web/views/queue.erb +0 -55
- data/web/views/queues.erb +0 -38
- data/web/views/retries.erb +0 -83
- data/web/views/retry.erb +0 -34
- data/web/views/scheduled.erb +0 -57
|
@@ -0,0 +1,135 @@
|
|
|
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, :thread_priority
|
|
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 to_h
|
|
42
|
+
{concurrency: concurrency, mode: mode, weights: weights}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def fetcher
|
|
46
|
+
@fetcher ||= begin
|
|
47
|
+
instance = (config[:fetch_class] || Sidekiq::BasicFetch).new(self)
|
|
48
|
+
instance.setup(config[:fetch_setup]) if instance.respond_to?(:setup)
|
|
49
|
+
instance
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def stop
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Sidekiq checks queues in three modes:
|
|
57
|
+
# - :strict - all queues have 0 weight and are checked strictly in order
|
|
58
|
+
# - :weighted - queues have arbitrary weight between 1 and N
|
|
59
|
+
# - :random - all queues have weight of 1
|
|
60
|
+
def queues=(val)
|
|
61
|
+
@weights = {}
|
|
62
|
+
@queues = Array(val).each_with_object([]) do |qstr, memo|
|
|
63
|
+
arr = qstr
|
|
64
|
+
arr = qstr.split(",") if qstr.is_a?(String)
|
|
65
|
+
name, weight = arr
|
|
66
|
+
@weights[name] = weight.to_i
|
|
67
|
+
[weight.to_i, 1].max.times do
|
|
68
|
+
memo << name
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
@mode = if @weights.values.all?(&:zero?)
|
|
72
|
+
:strict
|
|
73
|
+
elsif @weights.values.all? { |x| x == 1 }
|
|
74
|
+
:random
|
|
75
|
+
else
|
|
76
|
+
:weighted
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Allow the middleware to be different per-capsule.
|
|
81
|
+
# Avoid if possible and add middleware globally so all
|
|
82
|
+
# capsules share the same chains. Easier to debug that way.
|
|
83
|
+
def client_middleware
|
|
84
|
+
@client_chain ||= config.client_middleware.copy_for(self)
|
|
85
|
+
yield @client_chain if block_given?
|
|
86
|
+
@client_chain
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def server_middleware
|
|
90
|
+
@server_chain ||= config.server_middleware.copy_for(self)
|
|
91
|
+
yield @server_chain if block_given?
|
|
92
|
+
@server_chain
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def redis_pool
|
|
96
|
+
Thread.current[:sidekiq_redis_pool] || local_redis_pool
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def local_redis_pool
|
|
100
|
+
# connection pool is lazy, it will not create connections unless you actually need them
|
|
101
|
+
# so don't be skimpy!
|
|
102
|
+
@redis ||= config.new_redis_pool(@concurrency, name)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def redis
|
|
106
|
+
raise ArgumentError, "requires a block" unless block_given?
|
|
107
|
+
redis_pool.with do |conn|
|
|
108
|
+
retryable = true
|
|
109
|
+
begin
|
|
110
|
+
yield conn
|
|
111
|
+
rescue RedisClientAdapter::BaseError => ex
|
|
112
|
+
# 2550 Failover can cause the server to become a replica, need
|
|
113
|
+
# to disconnect and reopen the socket to get back to the primary.
|
|
114
|
+
# 4495 Use the same logic if we have a "Not enough replicas" error from the primary
|
|
115
|
+
# 4985 Use the same logic when a blocking command is force-unblocked
|
|
116
|
+
# The same retry logic is also used in client.rb
|
|
117
|
+
if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
|
|
118
|
+
conn.close
|
|
119
|
+
retryable = false
|
|
120
|
+
retry
|
|
121
|
+
end
|
|
122
|
+
raise
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def lookup(name)
|
|
128
|
+
config.lookup(name)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def logger
|
|
132
|
+
config.logger
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
data/lib/sidekiq/cli.rb
CHANGED
|
@@ -2,30 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
$stdout.sync = true
|
|
4
4
|
|
|
5
|
-
require "yaml"
|
|
6
|
-
require "singleton"
|
|
7
5
|
require "optparse"
|
|
8
6
|
require "erb"
|
|
9
7
|
require "fileutils"
|
|
10
8
|
|
|
11
9
|
require "sidekiq"
|
|
10
|
+
require "sidekiq/config"
|
|
11
|
+
require "sidekiq/component"
|
|
12
|
+
require "sidekiq/capsule"
|
|
12
13
|
require "sidekiq/launcher"
|
|
13
|
-
require "sidekiq/util"
|
|
14
14
|
|
|
15
|
-
module Sidekiq
|
|
15
|
+
module Sidekiq # :nodoc:
|
|
16
16
|
class CLI
|
|
17
|
-
include
|
|
18
|
-
include Singleton unless $TESTING
|
|
17
|
+
include Sidekiq::Component
|
|
19
18
|
|
|
20
19
|
attr_accessor :launcher
|
|
21
20
|
attr_accessor :environment
|
|
21
|
+
attr_accessor :config
|
|
22
|
+
|
|
23
|
+
def parse(args = ARGV.dup)
|
|
24
|
+
@config ||= Sidekiq.default_configuration
|
|
22
25
|
|
|
23
|
-
def parse(args = ARGV)
|
|
24
26
|
setup_options(args)
|
|
25
27
|
initialize_logger
|
|
26
28
|
validate!
|
|
27
29
|
end
|
|
28
30
|
|
|
31
|
+
def self.instance
|
|
32
|
+
@instance ||= new
|
|
33
|
+
end
|
|
34
|
+
|
|
29
35
|
def jruby?
|
|
30
36
|
defined?(::JRUBY_VERSION)
|
|
31
37
|
end
|
|
@@ -33,20 +39,28 @@ module Sidekiq
|
|
|
33
39
|
# Code within this method is not tested because it alters
|
|
34
40
|
# global process state irreversibly. PRs which improve the
|
|
35
41
|
# test coverage of Sidekiq::CLI are welcomed.
|
|
36
|
-
def run(boot_app: true)
|
|
42
|
+
def run(boot_app: true, warmup: true)
|
|
37
43
|
boot_application if boot_app
|
|
38
44
|
|
|
39
|
-
if environment == "development" && $stdout.tty? &&
|
|
45
|
+
if environment == "development" && $stdout.tty? && @config.logger.formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
|
|
40
46
|
print_banner
|
|
41
47
|
end
|
|
42
48
|
logger.info "Booted Rails #{::Rails.version} application in #{environment} environment" if rails_app?
|
|
43
49
|
|
|
44
50
|
self_read, self_write = IO.pipe
|
|
45
|
-
sigs = %w[INT TERM TTIN TSTP]
|
|
51
|
+
sigs = %w[INT TERM INFO TTIN TSTP]
|
|
46
52
|
# USR1 and USR2 don't work on the JVM
|
|
47
53
|
sigs << "USR2" if Sidekiq.pro? && !jruby?
|
|
48
54
|
sigs.each do |sig|
|
|
49
|
-
trap
|
|
55
|
+
old_handler = Signal.trap(sig) do
|
|
56
|
+
if old_handler.respond_to?(:call)
|
|
57
|
+
begin
|
|
58
|
+
old_handler.call
|
|
59
|
+
rescue Exception => exc
|
|
60
|
+
# signal handlers can't use Logger so puts only
|
|
61
|
+
puts ["Error in #{sig} handler", exc].inspect
|
|
62
|
+
end
|
|
63
|
+
end
|
|
50
64
|
self_write.puts(sig)
|
|
51
65
|
end
|
|
52
66
|
rescue ArgumentError
|
|
@@ -59,40 +73,43 @@ module Sidekiq
|
|
|
59
73
|
|
|
60
74
|
# touch the connection pool so it is created before we
|
|
61
75
|
# fire startup and start multithreading.
|
|
62
|
-
info =
|
|
63
|
-
ver = info["redis_version"]
|
|
64
|
-
raise "You are
|
|
76
|
+
info = @config.redis_info
|
|
77
|
+
ver = Gem::Version.new(info["redis_version"])
|
|
78
|
+
raise "You are connected to Redis #{ver}, Sidekiq requires Redis 7.0.0 or greater" if ver < Gem::Version.new("7.0.0")
|
|
65
79
|
|
|
66
80
|
maxmemory_policy = info["maxmemory_policy"]
|
|
67
|
-
if maxmemory_policy != "noeviction"
|
|
81
|
+
if maxmemory_policy != "noeviction" && maxmemory_policy != ""
|
|
82
|
+
# Redis Enterprise Cloud returns "" for their policy 😳
|
|
68
83
|
logger.warn <<~EOM
|
|
69
84
|
|
|
70
85
|
|
|
71
86
|
WARNING: Your Redis instance will evict Sidekiq data under heavy load.
|
|
72
87
|
The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
|
|
73
|
-
See: https://github.com/
|
|
88
|
+
See: https://github.com/sidekiq/sidekiq/wiki/Using-Redis#memory
|
|
74
89
|
|
|
75
90
|
EOM
|
|
76
91
|
end
|
|
77
92
|
|
|
78
93
|
# Since the user can pass us a connection pool explicitly in the initializer, we
|
|
79
94
|
# need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
@config.capsules.each_pair do |name, cap|
|
|
96
|
+
raise ArgumentError, "Pool size too small for #{name}" if cap.redis_pool.size < cap.concurrency
|
|
97
|
+
end
|
|
83
98
|
|
|
84
99
|
# cache process identity
|
|
85
|
-
|
|
100
|
+
@config[:identity] = identity
|
|
86
101
|
|
|
87
102
|
# Touch middleware so it isn't lazy loaded by multiple threads, #3043
|
|
88
|
-
|
|
103
|
+
@config.server_middleware
|
|
104
|
+
|
|
105
|
+
::Process.warmup if warmup && ::Process.respond_to?(:warmup) && ENV["RUBY_DISABLE_WARMUP"] != "1"
|
|
89
106
|
|
|
90
107
|
# Before this point, the process is initializing with just the main thread.
|
|
91
108
|
# Starting here the process will now have multiple threads running.
|
|
92
109
|
fire_event(:startup, reverse: false, reraise: true)
|
|
93
110
|
|
|
94
|
-
logger.debug { "Client Middleware: #{
|
|
95
|
-
logger.debug { "Server Middleware: #{
|
|
111
|
+
logger.debug { "Client Middleware: #{@config.default_capsule.client_middleware.map(&:klass).join(", ")}" }
|
|
112
|
+
logger.debug { "Server Middleware: #{@config.default_capsule.server_middleware.map(&:klass).join(", ")}" }
|
|
96
113
|
|
|
97
114
|
launch(self_read)
|
|
98
115
|
end
|
|
@@ -102,13 +119,13 @@ module Sidekiq
|
|
|
102
119
|
logger.info "Starting processing, hit Ctrl-C to stop"
|
|
103
120
|
end
|
|
104
121
|
|
|
105
|
-
@launcher = Sidekiq::Launcher.new(
|
|
122
|
+
@launcher = Sidekiq::Launcher.new(@config)
|
|
106
123
|
|
|
107
124
|
begin
|
|
108
125
|
launcher.run
|
|
109
126
|
|
|
110
|
-
while
|
|
111
|
-
signal =
|
|
127
|
+
while self_read.wait_readable
|
|
128
|
+
signal = self_read.gets.strip
|
|
112
129
|
handle_signal(signal)
|
|
113
130
|
end
|
|
114
131
|
rescue Interrupt
|
|
@@ -125,19 +142,34 @@ module Sidekiq
|
|
|
125
142
|
end
|
|
126
143
|
end
|
|
127
144
|
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
HOLIDAY_COLORS = {
|
|
146
|
+
# got other color-specific holidays from around the world?
|
|
147
|
+
# https://developer-book.com/post/definitive-guide-for-colored-text-in-terminal/#256-color-escape-codes
|
|
148
|
+
"3-17" => "\e[1;32m", # St. Patrick's Day green
|
|
149
|
+
"10-31" => "\e[38;5;208m" # Halloween orange
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
def self.day
|
|
153
|
+
@@day ||= begin
|
|
154
|
+
t = Date.today
|
|
155
|
+
"#{t.month}-#{t.day}"
|
|
156
|
+
end
|
|
130
157
|
end
|
|
131
158
|
|
|
132
159
|
def self.r
|
|
133
|
-
"\e[31m"
|
|
160
|
+
@@r ||= HOLIDAY_COLORS[day] || "\e[1;31m"
|
|
134
161
|
end
|
|
135
162
|
|
|
136
163
|
def self.b
|
|
137
|
-
"\e[30m"
|
|
164
|
+
@@b ||= HOLIDAY_COLORS[day] || "\e[30m"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def self.w
|
|
168
|
+
"\e[1;37m"
|
|
138
169
|
end
|
|
139
170
|
|
|
140
171
|
def self.reset
|
|
172
|
+
@@b = @@r = @@day = nil
|
|
141
173
|
"\e[0m"
|
|
142
174
|
end
|
|
143
175
|
|
|
@@ -150,7 +182,7 @@ module Sidekiq
|
|
|
150
182
|
#{w} ,$$$$$b#{b}/#{w}md$$$P^'
|
|
151
183
|
#{w} .d$$$$$$#{b}/#{w}$$$P'
|
|
152
184
|
#{w} $$^' `"#{b}/#{w}$$$' #{r}____ _ _ _ _
|
|
153
|
-
#{w} $:
|
|
185
|
+
#{w} $: #{b}'#{w},$$: #{r} / ___|(_) __| | ___| | _(_) __ _
|
|
154
186
|
#{w} `b :$$ #{r} \\___ \\| |/ _` |/ _ \\ |/ / |/ _` |
|
|
155
187
|
#{w} $$: #{r} ___) | | (_| | __/ <| | (_| |
|
|
156
188
|
#{w} $$ #{r}|____/|_|\\__,_|\\___|_|\\_\\_|\\__, |
|
|
@@ -165,26 +197,37 @@ module Sidekiq
|
|
|
165
197
|
# Heroku sends TERM and then waits 30 seconds for process to exit.
|
|
166
198
|
"TERM" => ->(cli) { raise Interrupt },
|
|
167
199
|
"TSTP" => ->(cli) {
|
|
168
|
-
|
|
200
|
+
cli.logger.info "Received TSTP, no longer accepting new work"
|
|
169
201
|
cli.launcher.quiet
|
|
170
202
|
},
|
|
171
203
|
"TTIN" => ->(cli) {
|
|
172
204
|
Thread.list.each do |thread|
|
|
173
|
-
|
|
205
|
+
cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
|
|
206
|
+
if thread.backtrace
|
|
207
|
+
cli.logger.warn thread.backtrace.join("\n")
|
|
208
|
+
else
|
|
209
|
+
cli.logger.warn "<no backtrace available>"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
},
|
|
213
|
+
"INFO" => ->(cli) {
|
|
214
|
+
cli.logger.error { "DEPRECATED: the INFO signal does not work on Linux, use TTIN instead." }
|
|
215
|
+
Thread.list.each do |thread|
|
|
216
|
+
cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
|
|
174
217
|
if thread.backtrace
|
|
175
|
-
|
|
218
|
+
cli.logger.warn thread.backtrace.join("\n")
|
|
176
219
|
else
|
|
177
|
-
|
|
220
|
+
cli.logger.warn "<no backtrace available>"
|
|
178
221
|
end
|
|
179
222
|
end
|
|
180
223
|
}
|
|
181
224
|
}
|
|
182
|
-
UNHANDLED_SIGNAL_HANDLER = ->(cli) { Sidekiq.logger.info "No signal handler registered, ignoring" }
|
|
183
|
-
SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER
|
|
184
225
|
|
|
185
226
|
def handle_signal(sig)
|
|
186
|
-
|
|
187
|
-
SIGNAL_HANDLERS[sig]
|
|
227
|
+
logger.debug "Got #{sig} signal"
|
|
228
|
+
hndlr = SIGNAL_HANDLERS[sig]
|
|
229
|
+
hndlr ? hndlr.call(self) :
|
|
230
|
+
logger.warn("No #{sig} signal handler registered, ignoring")
|
|
188
231
|
end
|
|
189
232
|
|
|
190
233
|
private
|
|
@@ -201,6 +244,7 @@ module Sidekiq
|
|
|
201
244
|
# Both Sinatra 2.0+ and Sidekiq support this term.
|
|
202
245
|
# RAILS_ENV and RACK_ENV are there for legacy support.
|
|
203
246
|
@environment = cli_env || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
247
|
+
config[:environment] = @environment
|
|
204
248
|
end
|
|
205
249
|
|
|
206
250
|
def symbolize_keys_deep!(hash)
|
|
@@ -229,7 +273,7 @@ module Sidekiq
|
|
|
229
273
|
config_dir = if File.directory?(opts[:require].to_s)
|
|
230
274
|
File.join(opts[:require], "config")
|
|
231
275
|
else
|
|
232
|
-
File.join(
|
|
276
|
+
File.join(@config[:require], "config")
|
|
233
277
|
end
|
|
234
278
|
|
|
235
279
|
%w[sidekiq.yml sidekiq.yml.erb].each do |config_file|
|
|
@@ -246,55 +290,51 @@ module Sidekiq
|
|
|
246
290
|
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
|
|
247
291
|
|
|
248
292
|
# merge with defaults
|
|
249
|
-
|
|
250
|
-
end
|
|
293
|
+
@config.merge!(opts)
|
|
251
294
|
|
|
252
|
-
|
|
253
|
-
|
|
295
|
+
@config.default_capsule.tap do |cap|
|
|
296
|
+
cap.queues = opts[:queues]
|
|
297
|
+
cap.concurrency = opts[:concurrency] || @config[:concurrency]
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
opts[:capsules]&.each do |name, cap_config|
|
|
301
|
+
@config.capsule(name.to_s) do |cap|
|
|
302
|
+
cap.queues = cap_config[:queues]
|
|
303
|
+
cap.concurrency = cap_config[:concurrency]
|
|
304
|
+
end
|
|
305
|
+
end
|
|
254
306
|
end
|
|
255
307
|
|
|
256
308
|
def boot_application
|
|
257
309
|
ENV["RACK_ENV"] = ENV["RAILS_ENV"] = environment
|
|
258
310
|
|
|
259
|
-
if File.directory?(
|
|
311
|
+
if File.directory?(@config[:require])
|
|
260
312
|
require "rails"
|
|
261
|
-
if ::Rails::VERSION::MAJOR <
|
|
262
|
-
|
|
263
|
-
else
|
|
264
|
-
require "sidekiq/rails"
|
|
265
|
-
require File.expand_path("#{options[:require]}/config/environment.rb")
|
|
313
|
+
if ::Rails::VERSION::MAJOR < 7
|
|
314
|
+
warn "Sidekiq #{Sidekiq::VERSION} only supports Rails 7+"
|
|
266
315
|
end
|
|
267
|
-
|
|
316
|
+
require "sidekiq/rails"
|
|
317
|
+
require File.expand_path("#{@config[:require]}/config/environment.rb")
|
|
318
|
+
@config[:tag] ||= default_tag(::Rails.root)
|
|
268
319
|
else
|
|
269
|
-
require
|
|
270
|
-
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
def default_tag
|
|
274
|
-
dir = ::Rails.root
|
|
275
|
-
name = File.basename(dir)
|
|
276
|
-
prevdir = File.dirname(dir) # Capistrano release directory?
|
|
277
|
-
if name.to_i != 0 && prevdir
|
|
278
|
-
if File.basename(prevdir) == "releases"
|
|
279
|
-
return File.basename(File.dirname(prevdir))
|
|
280
|
-
end
|
|
320
|
+
require @config[:require]
|
|
321
|
+
@config[:tag] ||= default_tag
|
|
281
322
|
end
|
|
282
|
-
name
|
|
283
323
|
end
|
|
284
324
|
|
|
285
325
|
def validate!
|
|
286
|
-
if !File.exist?(
|
|
287
|
-
(File.directory?(
|
|
326
|
+
if !File.exist?(@config[:require]) ||
|
|
327
|
+
(File.directory?(@config[:require]) && !File.exist?("#{@config[:require]}/config/application.rb"))
|
|
288
328
|
logger.info "=================================================================="
|
|
289
329
|
logger.info " Please point Sidekiq to a Rails application or a Ruby file "
|
|
290
|
-
logger.info " to load your
|
|
330
|
+
logger.info " to load your job classes with -r [DIR|FILE]."
|
|
291
331
|
logger.info "=================================================================="
|
|
292
332
|
logger.info @parser
|
|
293
333
|
die(1)
|
|
294
334
|
end
|
|
295
335
|
|
|
296
336
|
[:concurrency, :timeout].each do |opt|
|
|
297
|
-
raise ArgumentError, "#{opt}: #{
|
|
337
|
+
raise ArgumentError, "#{opt}: #{@config[opt]} is not a valid value" if @config[opt].to_i <= 0
|
|
298
338
|
end
|
|
299
339
|
end
|
|
300
340
|
|
|
@@ -311,10 +351,6 @@ module Sidekiq
|
|
|
311
351
|
opts[:concurrency] = Integer(arg)
|
|
312
352
|
end
|
|
313
353
|
|
|
314
|
-
o.on "-d", "--daemon", "Daemonize process" do |arg|
|
|
315
|
-
puts "ERROR: Daemonization mode was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services"
|
|
316
|
-
end
|
|
317
|
-
|
|
318
354
|
o.on "-e", "--environment ENV", "Application environment" do |arg|
|
|
319
355
|
opts[:environment] = arg
|
|
320
356
|
end
|
|
@@ -324,11 +360,11 @@ module Sidekiq
|
|
|
324
360
|
end
|
|
325
361
|
|
|
326
362
|
o.on "-q", "--queue QUEUE[,WEIGHT]", "Queues to process with optional weights" do |arg|
|
|
327
|
-
|
|
328
|
-
|
|
363
|
+
opts[:queues] ||= []
|
|
364
|
+
opts[:queues] << arg
|
|
329
365
|
end
|
|
330
366
|
|
|
331
|
-
o.on "-r", "--require [PATH|DIR]", "Location of Rails application with
|
|
367
|
+
o.on "-r", "--require [PATH|DIR]", "Location of Rails application with jobs or file to require" do |arg|
|
|
332
368
|
opts[:require] = arg
|
|
333
369
|
end
|
|
334
370
|
|
|
@@ -344,15 +380,7 @@ module Sidekiq
|
|
|
344
380
|
opts[:config_file] = arg
|
|
345
381
|
end
|
|
346
382
|
|
|
347
|
-
o.on "-
|
|
348
|
-
puts "ERROR: Logfile redirection was removed in Sidekiq 6.0, Sidekiq will only log to STDOUT"
|
|
349
|
-
end
|
|
350
|
-
|
|
351
|
-
o.on "-P", "--pidfile PATH", "path to pidfile" do |arg|
|
|
352
|
-
puts "ERROR: PID file creation was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services"
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
o.on "-V", "--version", "Print version and exit" do |arg|
|
|
383
|
+
o.on "-V", "--version", "Print version and exit" do
|
|
356
384
|
puts "Sidekiq #{Sidekiq::VERSION}"
|
|
357
385
|
die(0)
|
|
358
386
|
end
|
|
@@ -368,11 +396,19 @@ module Sidekiq
|
|
|
368
396
|
end
|
|
369
397
|
|
|
370
398
|
def initialize_logger
|
|
371
|
-
|
|
399
|
+
if @config[:verbose] || ENV["DEBUG_INVOCATION"] == "1"
|
|
400
|
+
# DEBUG_INVOCATION is a systemd-ism triggered by
|
|
401
|
+
# RestartMode=debug. We turn on debugging when the
|
|
402
|
+
# sidekiq process crashes and is restarted with this flag.
|
|
403
|
+
@config.logger.level = ::Logger::DEBUG
|
|
404
|
+
end
|
|
372
405
|
end
|
|
373
406
|
|
|
374
407
|
def parse_config(path)
|
|
375
|
-
|
|
408
|
+
erb = ERB.new(File.read(path), trim_mode: "-")
|
|
409
|
+
erb.filename = File.expand_path(path)
|
|
410
|
+
require "yaml"
|
|
411
|
+
opts = YAML.safe_load(erb.result, permitted_classes: [Symbol], aliases: true) || {}
|
|
376
412
|
|
|
377
413
|
if opts.respond_to? :deep_symbolize_keys!
|
|
378
414
|
opts.deep_symbolize_keys!
|
|
@@ -383,23 +419,9 @@ module Sidekiq
|
|
|
383
419
|
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
|
384
420
|
opts.delete(:strict)
|
|
385
421
|
|
|
386
|
-
parse_queues(opts, opts.delete(:queues) || [])
|
|
387
|
-
|
|
388
422
|
opts
|
|
389
423
|
end
|
|
390
424
|
|
|
391
|
-
def parse_queues(opts, queues_and_weights)
|
|
392
|
-
queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
|
|
393
|
-
end
|
|
394
|
-
|
|
395
|
-
def parse_queue(opts, queue, weight = nil)
|
|
396
|
-
opts[:queues] ||= []
|
|
397
|
-
opts[:strict] = true if opts[:strict].nil?
|
|
398
|
-
raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
|
|
399
|
-
[weight.to_i, 1].max.times { opts[:queues] << queue }
|
|
400
|
-
opts[:strict] = false if weight.to_i > 0
|
|
401
|
-
end
|
|
402
|
-
|
|
403
425
|
def rails_app?
|
|
404
426
|
defined?(::Rails) && ::Rails.respond_to?(:application)
|
|
405
427
|
end
|
|
@@ -407,3 +429,5 @@ module Sidekiq
|
|
|
407
429
|
end
|
|
408
430
|
|
|
409
431
|
require "sidekiq/systemd"
|
|
432
|
+
require "sidekiq/metrics/tracking"
|
|
433
|
+
require "sidekiq/job/interrupt_handler"
|