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.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +726 -11
  3. data/LICENSE.txt +9 -0
  4. data/README.md +70 -39
  5. data/bin/kiq +17 -0
  6. data/bin/lint-herb +13 -0
  7. data/bin/multi_queue_bench +271 -0
  8. data/bin/sidekiq +4 -9
  9. data/bin/sidekiqload +214 -115
  10. data/bin/sidekiqmon +4 -1
  11. data/bin/webload +69 -0
  12. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
  13. data/lib/generators/sidekiq/job_generator.rb +71 -0
  14. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +3 -3
  15. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  16. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  17. data/lib/sidekiq/api.rb +729 -264
  18. data/lib/sidekiq/capsule.rb +135 -0
  19. data/lib/sidekiq/cli.rb +124 -100
  20. data/lib/sidekiq/client.rb +153 -106
  21. data/lib/sidekiq/component.rb +132 -0
  22. data/lib/sidekiq/config.rb +320 -0
  23. data/lib/sidekiq/deploy.rb +64 -0
  24. data/lib/sidekiq/embedded.rb +64 -0
  25. data/lib/sidekiq/fetch.rb +27 -26
  26. data/lib/sidekiq/iterable_job.rb +56 -0
  27. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  28. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  29. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  30. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  31. data/lib/sidekiq/job/iterable.rb +322 -0
  32. data/lib/sidekiq/job.rb +397 -5
  33. data/lib/sidekiq/job_logger.rb +23 -32
  34. data/lib/sidekiq/job_retry.rb +141 -68
  35. data/lib/sidekiq/job_util.rb +113 -0
  36. data/lib/sidekiq/launcher.rb +122 -98
  37. data/lib/sidekiq/loader.rb +57 -0
  38. data/lib/sidekiq/logger.rb +27 -106
  39. data/lib/sidekiq/manager.rb +41 -43
  40. data/lib/sidekiq/metrics/query.rb +184 -0
  41. data/lib/sidekiq/metrics/shared.rb +109 -0
  42. data/lib/sidekiq/metrics/tracking.rb +153 -0
  43. data/lib/sidekiq/middleware/chain.rb +96 -51
  44. data/lib/sidekiq/middleware/current_attributes.rb +120 -0
  45. data/lib/sidekiq/middleware/i18n.rb +8 -4
  46. data/lib/sidekiq/middleware/modules.rb +23 -0
  47. data/lib/sidekiq/monitor.rb +16 -6
  48. data/lib/sidekiq/paginator.rb +37 -10
  49. data/lib/sidekiq/processor.rb +105 -87
  50. data/lib/sidekiq/profiler.rb +73 -0
  51. data/lib/sidekiq/rails.rb +49 -36
  52. data/lib/sidekiq/redis_client_adapter.rb +117 -0
  53. data/lib/sidekiq/redis_connection.rb +55 -86
  54. data/lib/sidekiq/ring_buffer.rb +32 -0
  55. data/lib/sidekiq/scheduled.rb +106 -50
  56. data/lib/sidekiq/systemd.rb +2 -0
  57. data/lib/sidekiq/test_api.rb +331 -0
  58. data/lib/sidekiq/testing/inline.rb +2 -30
  59. data/lib/sidekiq/testing.rb +2 -342
  60. data/lib/sidekiq/transaction_aware_client.rb +59 -0
  61. data/lib/sidekiq/tui/controls.rb +53 -0
  62. data/lib/sidekiq/tui/filtering.rb +53 -0
  63. data/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
  64. data/lib/sidekiq/tui/tabs/busy.rb +118 -0
  65. data/lib/sidekiq/tui/tabs/dead.rb +19 -0
  66. data/lib/sidekiq/tui/tabs/home.rb +144 -0
  67. data/lib/sidekiq/tui/tabs/metrics.rb +131 -0
  68. data/lib/sidekiq/tui/tabs/queues.rb +95 -0
  69. data/lib/sidekiq/tui/tabs/retries.rb +19 -0
  70. data/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
  71. data/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
  72. data/lib/sidekiq/tui/tabs.rb +15 -0
  73. data/lib/sidekiq/tui.rb +382 -0
  74. data/lib/sidekiq/version.rb +6 -1
  75. data/lib/sidekiq/web/action.rb +149 -64
  76. data/lib/sidekiq/web/application.rb +376 -268
  77. data/lib/sidekiq/web/config.rb +117 -0
  78. data/lib/sidekiq/web/helpers.rb +213 -87
  79. data/lib/sidekiq/web/router.rb +61 -74
  80. data/lib/sidekiq/web.rb +71 -100
  81. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  82. data/lib/sidekiq.rb +95 -196
  83. data/sidekiq.gemspec +14 -11
  84. data/web/assets/images/logo.png +0 -0
  85. data/web/assets/images/status.png +0 -0
  86. data/web/assets/javascripts/application.js +171 -57
  87. data/web/assets/javascripts/base-charts.js +120 -0
  88. data/web/assets/javascripts/chart.min.js +13 -0
  89. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  90. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  91. data/web/assets/javascripts/dashboard-charts.js +194 -0
  92. data/web/assets/javascripts/dashboard.js +41 -274
  93. data/web/assets/javascripts/metrics.js +280 -0
  94. data/web/assets/stylesheets/style.css +776 -0
  95. data/web/locales/ar.yml +72 -70
  96. data/web/locales/cs.yml +64 -62
  97. data/web/locales/da.yml +62 -53
  98. data/web/locales/de.yml +67 -65
  99. data/web/locales/el.yml +45 -24
  100. data/web/locales/en.yml +93 -69
  101. data/web/locales/es.yml +91 -68
  102. data/web/locales/fa.yml +67 -65
  103. data/web/locales/fr.yml +82 -67
  104. data/web/locales/gd.yml +110 -0
  105. data/web/locales/he.yml +67 -64
  106. data/web/locales/hi.yml +61 -59
  107. data/web/locales/it.yml +94 -54
  108. data/web/locales/ja.yml +74 -68
  109. data/web/locales/ko.yml +54 -52
  110. data/web/locales/lt.yml +68 -66
  111. data/web/locales/nb.yml +63 -61
  112. data/web/locales/nl.yml +54 -52
  113. data/web/locales/pl.yml +47 -45
  114. data/web/locales/{pt-br.yml → pt-BR.yml} +85 -56
  115. data/web/locales/pt.yml +53 -51
  116. data/web/locales/ru.yml +69 -66
  117. data/web/locales/sv.yml +55 -53
  118. data/web/locales/ta.yml +62 -60
  119. data/web/locales/tr.yml +102 -0
  120. data/web/locales/uk.yml +87 -61
  121. data/web/locales/ur.yml +66 -64
  122. data/web/locales/vi.yml +69 -67
  123. data/web/locales/zh-CN.yml +107 -0
  124. data/web/locales/{zh-tw.yml → zh-TW.yml} +44 -9
  125. data/web/views/_footer.html.erb +32 -0
  126. data/web/views/_job_info.html.erb +115 -0
  127. data/web/views/_metrics_period_select.html.erb +15 -0
  128. data/web/views/_nav.html.erb +45 -0
  129. data/web/views/_paging.html.erb +26 -0
  130. data/web/views/_poll_link.html.erb +4 -0
  131. data/web/views/_summary.html.erb +40 -0
  132. data/web/views/busy.html.erb +151 -0
  133. data/web/views/dashboard.html.erb +104 -0
  134. data/web/views/dead.html.erb +38 -0
  135. data/web/views/filtering.html.erb +6 -0
  136. data/web/views/layout.html.erb +26 -0
  137. data/web/views/metrics.html.erb +85 -0
  138. data/web/views/metrics_for_job.html.erb +58 -0
  139. data/web/views/morgue.html.erb +69 -0
  140. data/web/views/profiles.html.erb +43 -0
  141. data/web/views/queue.html.erb +57 -0
  142. data/web/views/queues.html.erb +46 -0
  143. data/web/views/retries.html.erb +77 -0
  144. data/web/views/retry.html.erb +39 -0
  145. data/web/views/scheduled.html.erb +64 -0
  146. data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
  147. metadata +130 -61
  148. data/LICENSE +0 -9
  149. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  150. data/lib/sidekiq/delay.rb +0 -41
  151. data/lib/sidekiq/exception_handler.rb +0 -27
  152. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  153. data/lib/sidekiq/extensions/active_record.rb +0 -43
  154. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  155. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  156. data/lib/sidekiq/util.rb +0 -95
  157. data/lib/sidekiq/web/csrf_protection.rb +0 -180
  158. data/lib/sidekiq/worker.rb +0 -244
  159. data/web/assets/stylesheets/application-dark.css +0 -147
  160. data/web/assets/stylesheets/application-rtl.css +0 -246
  161. data/web/assets/stylesheets/application.css +0 -1053
  162. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  163. data/web/assets/stylesheets/bootstrap.css +0 -5
  164. data/web/locales/zh-cn.yml +0 -68
  165. data/web/views/_footer.erb +0 -20
  166. data/web/views/_job_info.erb +0 -89
  167. data/web/views/_nav.erb +0 -52
  168. data/web/views/_paging.erb +0 -23
  169. data/web/views/_poll_link.erb +0 -7
  170. data/web/views/_status.erb +0 -4
  171. data/web/views/_summary.erb +0 -40
  172. data/web/views/busy.erb +0 -132
  173. data/web/views/dashboard.erb +0 -83
  174. data/web/views/dead.erb +0 -34
  175. data/web/views/layout.erb +0 -42
  176. data/web/views/morgue.erb +0 -78
  177. data/web/views/queue.erb +0 -55
  178. data/web/views/queues.erb +0 -38
  179. data/web/views/retries.erb +0 -83
  180. data/web/views/retry.erb +0 -34
  181. 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 Util
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? && Sidekiq.log_formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
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 sig do
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 = Sidekiq.redis_info
63
- ver = info["redis_version"]
64
- raise "You are connecting to Redis v#{ver}, Sidekiq requires Redis v4.0.0 or greater" if ver < "4"
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/mperham/sidekiq/wiki/Using-Redis#memory
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
- cursize = Sidekiq.redis_pool.size
81
- needed = Sidekiq.options[:concurrency] + 2
82
- raise "Your pool of #{cursize} Redis connections is too small, please increase the size to at least #{needed}" if cursize < needed
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
- Sidekiq.options[:identity] = identity
100
+ @config[:identity] = identity
86
101
 
87
102
  # Touch middleware so it isn't lazy loaded by multiple threads, #3043
88
- Sidekiq.server_middleware
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: #{Sidekiq.client_middleware.map(&:klass).join(", ")}" }
95
- logger.debug { "Server Middleware: #{Sidekiq.server_middleware.map(&:klass).join(", ")}" }
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(options)
122
+ @launcher = Sidekiq::Launcher.new(@config)
106
123
 
107
124
  begin
108
125
  launcher.run
109
126
 
110
- while (readable_io = IO.select([self_read]))
111
- signal = readable_io.first[0].gets.strip
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
- def self.w
129
- "\e[37m"
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} $: ,$$: #{r} / ___|(_) __| | ___| | _(_) __ _
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
- Sidekiq.logger.info "Received TSTP, no longer accepting new work"
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
- Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
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
- Sidekiq.logger.warn thread.backtrace.join("\n")
218
+ cli.logger.warn thread.backtrace.join("\n")
176
219
  else
177
- Sidekiq.logger.warn "<no backtrace available>"
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
- Sidekiq.logger.debug "Got #{sig} signal"
187
- SIGNAL_HANDLERS[sig].call(self)
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(options[:require], "config")
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
- options.merge!(opts)
250
- end
293
+ @config.merge!(opts)
251
294
 
252
- def options
253
- Sidekiq.options
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?(options[:require])
311
+ if File.directory?(@config[:require])
260
312
  require "rails"
261
- if ::Rails::VERSION::MAJOR < 5
262
- raise "Sidekiq no longer supports this version of Rails"
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
- options[:tag] ||= default_tag
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 options[:require]
270
- end
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?(options[:require]) ||
287
- (File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
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 worker classes with -r [DIR|FILE]."
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}: #{options[opt]} is not a valid value" if options.key?(opt) && options[opt].to_i <= 0
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
- queue, weight = arg.split(",")
328
- parse_queue opts, queue, weight
363
+ opts[:queues] ||= []
364
+ opts[:queues] << arg
329
365
  end
330
366
 
331
- o.on "-r", "--require [PATH|DIR]", "Location of Rails application with workers or file to require" do |arg|
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 "-L", "--logfile PATH", "path to writable logfile" do |arg|
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
- Sidekiq.logger.level = ::Logger::DEBUG if options[:verbose]
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
- opts = YAML.load(ERB.new(File.read(path)).result) || {}
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"