sidekiq 5.2.4 → 7.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +672 -8
  3. data/LICENSE.txt +9 -0
  4. data/README.md +48 -51
  5. data/bin/multi_queue_bench +271 -0
  6. data/bin/sidekiq +22 -3
  7. data/bin/sidekiqload +213 -115
  8. data/bin/sidekiqmon +11 -0
  9. data/lib/generators/sidekiq/job_generator.rb +57 -0
  10. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  11. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  12. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  13. data/lib/sidekiq/api.rb +623 -352
  14. data/lib/sidekiq/capsule.rb +127 -0
  15. data/lib/sidekiq/cli.rb +214 -229
  16. data/lib/sidekiq/client.rb +127 -102
  17. data/lib/sidekiq/component.rb +68 -0
  18. data/lib/sidekiq/config.rb +287 -0
  19. data/lib/sidekiq/deploy.rb +62 -0
  20. data/lib/sidekiq/embedded.rb +61 -0
  21. data/lib/sidekiq/fetch.rb +49 -42
  22. data/lib/sidekiq/job.rb +374 -0
  23. data/lib/sidekiq/job_logger.rb +33 -7
  24. data/lib/sidekiq/job_retry.rb +157 -108
  25. data/lib/sidekiq/job_util.rb +107 -0
  26. data/lib/sidekiq/launcher.rb +206 -106
  27. data/lib/sidekiq/logger.rb +131 -0
  28. data/lib/sidekiq/manager.rb +43 -46
  29. data/lib/sidekiq/metrics/query.rb +156 -0
  30. data/lib/sidekiq/metrics/shared.rb +95 -0
  31. data/lib/sidekiq/metrics/tracking.rb +140 -0
  32. data/lib/sidekiq/middleware/chain.rb +113 -56
  33. data/lib/sidekiq/middleware/current_attributes.rb +95 -0
  34. data/lib/sidekiq/middleware/i18n.rb +7 -7
  35. data/lib/sidekiq/middleware/modules.rb +21 -0
  36. data/lib/sidekiq/monitor.rb +146 -0
  37. data/lib/sidekiq/paginator.rb +28 -16
  38. data/lib/sidekiq/processor.rb +126 -117
  39. data/lib/sidekiq/rails.rb +52 -38
  40. data/lib/sidekiq/redis_client_adapter.rb +111 -0
  41. data/lib/sidekiq/redis_connection.rb +41 -112
  42. data/lib/sidekiq/ring_buffer.rb +29 -0
  43. data/lib/sidekiq/scheduled.rb +112 -50
  44. data/lib/sidekiq/sd_notify.rb +149 -0
  45. data/lib/sidekiq/systemd.rb +24 -0
  46. data/lib/sidekiq/testing/inline.rb +6 -5
  47. data/lib/sidekiq/testing.rb +91 -90
  48. data/lib/sidekiq/transaction_aware_client.rb +51 -0
  49. data/lib/sidekiq/version.rb +3 -1
  50. data/lib/sidekiq/web/action.rb +20 -11
  51. data/lib/sidekiq/web/application.rb +202 -80
  52. data/lib/sidekiq/web/csrf_protection.rb +183 -0
  53. data/lib/sidekiq/web/helpers.rb +165 -114
  54. data/lib/sidekiq/web/router.rb +23 -19
  55. data/lib/sidekiq/web.rb +68 -107
  56. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  57. data/lib/sidekiq.rb +92 -182
  58. data/sidekiq.gemspec +25 -16
  59. data/web/assets/images/apple-touch-icon.png +0 -0
  60. data/web/assets/javascripts/application.js +152 -61
  61. data/web/assets/javascripts/base-charts.js +106 -0
  62. data/web/assets/javascripts/chart.min.js +13 -0
  63. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  64. data/web/assets/javascripts/dashboard-charts.js +182 -0
  65. data/web/assets/javascripts/dashboard.js +35 -293
  66. data/web/assets/javascripts/metrics.js +298 -0
  67. data/web/assets/stylesheets/application-dark.css +147 -0
  68. data/web/assets/stylesheets/application-rtl.css +10 -93
  69. data/web/assets/stylesheets/application.css +124 -522
  70. data/web/assets/stylesheets/bootstrap.css +1 -1
  71. data/web/locales/ar.yml +71 -65
  72. data/web/locales/cs.yml +62 -62
  73. data/web/locales/da.yml +60 -53
  74. data/web/locales/de.yml +65 -53
  75. data/web/locales/el.yml +43 -24
  76. data/web/locales/en.yml +86 -66
  77. data/web/locales/es.yml +70 -54
  78. data/web/locales/fa.yml +65 -65
  79. data/web/locales/fr.yml +83 -62
  80. data/web/locales/gd.yml +99 -0
  81. data/web/locales/he.yml +65 -64
  82. data/web/locales/hi.yml +59 -59
  83. data/web/locales/it.yml +53 -53
  84. data/web/locales/ja.yml +75 -64
  85. data/web/locales/ko.yml +52 -52
  86. data/web/locales/lt.yml +83 -0
  87. data/web/locales/nb.yml +61 -61
  88. data/web/locales/nl.yml +52 -52
  89. data/web/locales/pl.yml +45 -45
  90. data/web/locales/pt-br.yml +83 -55
  91. data/web/locales/pt.yml +51 -51
  92. data/web/locales/ru.yml +68 -63
  93. data/web/locales/sv.yml +53 -53
  94. data/web/locales/ta.yml +60 -60
  95. data/web/locales/uk.yml +62 -61
  96. data/web/locales/ur.yml +64 -64
  97. data/web/locales/vi.yml +83 -0
  98. data/web/locales/zh-cn.yml +43 -16
  99. data/web/locales/zh-tw.yml +42 -8
  100. data/web/views/_footer.erb +18 -3
  101. data/web/views/_job_info.erb +21 -4
  102. data/web/views/_metrics_period_select.erb +12 -0
  103. data/web/views/_nav.erb +1 -1
  104. data/web/views/_paging.erb +2 -0
  105. data/web/views/_poll_link.erb +3 -6
  106. data/web/views/_summary.erb +7 -7
  107. data/web/views/busy.erb +79 -29
  108. data/web/views/dashboard.erb +48 -18
  109. data/web/views/dead.erb +3 -3
  110. data/web/views/filtering.erb +7 -0
  111. data/web/views/layout.erb +3 -1
  112. data/web/views/metrics.erb +91 -0
  113. data/web/views/metrics_for_job.erb +59 -0
  114. data/web/views/morgue.erb +14 -15
  115. data/web/views/queue.erb +33 -24
  116. data/web/views/queues.erb +19 -5
  117. data/web/views/retries.erb +16 -17
  118. data/web/views/retry.erb +3 -3
  119. data/web/views/scheduled.erb +17 -15
  120. metadata +71 -72
  121. data/.github/contributing.md +0 -32
  122. data/.github/issue_template.md +0 -11
  123. data/.gitignore +0 -15
  124. data/.travis.yml +0 -17
  125. data/3.0-Upgrade.md +0 -70
  126. data/4.0-Upgrade.md +0 -53
  127. data/5.0-Upgrade.md +0 -56
  128. data/Appraisals +0 -9
  129. data/COMM-LICENSE +0 -95
  130. data/Ent-Changes.md +0 -225
  131. data/Gemfile +0 -29
  132. data/LICENSE +0 -9
  133. data/Pro-2.0-Upgrade.md +0 -138
  134. data/Pro-3.0-Upgrade.md +0 -44
  135. data/Pro-4.0-Upgrade.md +0 -35
  136. data/Pro-Changes.md +0 -752
  137. data/Rakefile +0 -9
  138. data/bin/sidekiqctl +0 -237
  139. data/code_of_conduct.md +0 -50
  140. data/gemfiles/rails_4.gemfile +0 -31
  141. data/gemfiles/rails_5.gemfile +0 -31
  142. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  143. data/lib/sidekiq/core_ext.rb +0 -1
  144. data/lib/sidekiq/delay.rb +0 -42
  145. data/lib/sidekiq/exception_handler.rb +0 -29
  146. data/lib/sidekiq/extensions/action_mailer.rb +0 -57
  147. data/lib/sidekiq/extensions/active_record.rb +0 -40
  148. data/lib/sidekiq/extensions/class_methods.rb +0 -40
  149. data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
  150. data/lib/sidekiq/logging.rb +0 -122
  151. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
  152. data/lib/sidekiq/util.rb +0 -66
  153. data/lib/sidekiq/worker.rb +0 -215
data/lib/sidekiq/cli.rb CHANGED
@@ -1,33 +1,31 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  $stdout.sync = true
3
4
 
4
- require 'yaml'
5
- require 'singleton'
6
- require 'optparse'
7
- require 'erb'
8
- require 'fileutils'
5
+ require "yaml"
6
+ require "singleton"
7
+ require "optparse"
8
+ require "erb"
9
+ require "fileutils"
9
10
 
10
- require 'sidekiq'
11
- require 'sidekiq/util'
12
- require 'sidekiq/launcher'
11
+ require "sidekiq"
12
+ require "sidekiq/config"
13
+ require "sidekiq/component"
14
+ require "sidekiq/capsule"
15
+ require "sidekiq/launcher"
13
16
 
14
- module Sidekiq
17
+ module Sidekiq # :nodoc:
15
18
  class CLI
16
- include Util
19
+ include Sidekiq::Component
17
20
  include Singleton unless $TESTING
18
21
 
19
- PROCTITLES = [
20
- proc { 'sidekiq' },
21
- proc { Sidekiq::VERSION },
22
- proc { |me, data| data['tag'] },
23
- proc { |me, data| "[#{Processor::WORKER_STATE.size} of #{data['concurrency']} busy]" },
24
- proc { |me, data| "stopping" if me.stopping? },
25
- ]
26
-
27
22
  attr_accessor :launcher
28
23
  attr_accessor :environment
24
+ attr_accessor :config
25
+
26
+ def parse(args = ARGV.dup)
27
+ @config ||= Sidekiq.default_configuration
29
28
 
30
- def parse(args = ARGV)
31
29
  setup_options(args)
32
30
  initialize_logger
33
31
  validate!
@@ -40,187 +38,208 @@ module Sidekiq
40
38
  # Code within this method is not tested because it alters
41
39
  # global process state irreversibly. PRs which improve the
42
40
  # test coverage of Sidekiq::CLI are welcomed.
43
- def run
44
- daemonize if options[:daemon]
45
- write_pid
46
- boot_system
47
- print_banner if environment == 'development' && $stdout.tty?
41
+ def run(boot_app: true, warmup: true)
42
+ boot_application if boot_app
48
43
 
49
- self_read, self_write = IO.pipe
50
- sigs = %w(INT TERM TTIN TSTP)
51
- # USR1 and USR2 don't work on the JVM
52
- if !jruby?
53
- sigs << 'USR1'
54
- sigs << 'USR2'
44
+ if environment == "development" && $stdout.tty? && @config.logger.formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
45
+ print_banner
55
46
  end
47
+ logger.info "Booted Rails #{::Rails.version} application in #{environment} environment" if rails_app?
56
48
 
49
+ self_read, self_write = IO.pipe
50
+ sigs = %w[INT TERM TTIN TSTP]
51
+ # USR1 and USR2 don't work on the JVM
52
+ sigs << "USR2" if Sidekiq.pro? && !jruby?
57
53
  sigs.each do |sig|
58
- begin
59
- trap sig do
60
- self_write.write("#{sig}\n")
54
+ old_handler = Signal.trap(sig) do
55
+ if old_handler.respond_to?(:call)
56
+ begin
57
+ old_handler.call
58
+ rescue Exception => exc
59
+ # signal handlers can't use Logger so puts only
60
+ puts ["Error in #{sig} handler", exc].inspect
61
+ end
61
62
  end
62
- rescue ArgumentError
63
- puts "Signal #{sig} not supported"
63
+ self_write.puts(sig)
64
64
  end
65
+ rescue ArgumentError
66
+ puts "Signal #{sig} not supported"
65
67
  end
66
68
 
67
69
  logger.info "Running in #{RUBY_DESCRIPTION}"
68
70
  logger.info Sidekiq::LICENSE
69
- logger.info "Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org" unless defined?(::Sidekiq::Pro)
71
+ logger.info "Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org" unless defined?(::Sidekiq::Pro)
70
72
 
71
73
  # touch the connection pool so it is created before we
72
74
  # fire startup and start multithreading.
73
- ver = Sidekiq.redis_info['redis_version']
74
- raise "You are using Redis v#{ver}, Sidekiq requires Redis v2.8.0 or greater" if ver < '2.8'
75
- logger.warn "Sidekiq 6.0 will require Redis 4.0+, you are using Redis v#{ver}" if ver < '4'
75
+ info = @config.redis_info
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")
78
+
79
+ maxmemory_policy = info["maxmemory_policy"]
80
+ if maxmemory_policy != "noeviction" && maxmemory_policy != ""
81
+ # Redis Enterprise Cloud returns "" for their policy 😳
82
+ logger.warn <<~EOM
83
+
84
+
85
+ WARNING: Your Redis instance will evict Sidekiq data under heavy load.
86
+ The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
87
+ See: https://github.com/sidekiq/sidekiq/wiki/Using-Redis#memory
88
+
89
+ EOM
90
+ end
76
91
 
77
92
  # Since the user can pass us a connection pool explicitly in the initializer, we
78
93
  # need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
79
- cursize = Sidekiq.redis_pool.size
80
- needed = Sidekiq.options[:concurrency] + 2
81
- raise "Your pool of #{cursize} Redis connections is too small, please increase the size to at least #{needed}" if cursize < needed
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
82
97
 
83
98
  # cache process identity
84
- Sidekiq.options[:identity] = identity
99
+ @config[:identity] = identity
85
100
 
86
101
  # Touch middleware so it isn't lazy loaded by multiple threads, #3043
87
- Sidekiq.server_middleware
102
+ @config.server_middleware
103
+
104
+ ::Process.warmup if warmup && ::Process.respond_to?(:warmup)
88
105
 
89
106
  # Before this point, the process is initializing with just the main thread.
90
107
  # Starting here the process will now have multiple threads running.
91
108
  fire_event(:startup, reverse: false, reraise: true)
92
109
 
93
- logger.debug { "Client Middleware: #{Sidekiq.client_middleware.map(&:klass).join(', ')}" }
94
- logger.debug { "Server Middleware: #{Sidekiq.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(", ")}" }
95
112
 
96
113
  launch(self_read)
97
114
  end
98
115
 
99
116
  def launch(self_read)
100
- if !options[:daemon]
101
- logger.info 'Starting processing, hit Ctrl-C to stop'
117
+ if environment == "development" && $stdout.tty?
118
+ logger.info "Starting processing, hit Ctrl-C to stop"
102
119
  end
103
120
 
104
- @launcher = Sidekiq::Launcher.new(options)
121
+ @launcher = Sidekiq::Launcher.new(@config)
105
122
 
106
123
  begin
107
124
  launcher.run
108
125
 
109
- while readable_io = IO.select([self_read])
110
- signal = readable_io.first[0].gets.strip
126
+ while self_read.wait_readable
127
+ signal = self_read.gets.strip
111
128
  handle_signal(signal)
112
129
  end
113
130
  rescue Interrupt
114
- logger.info 'Shutting down'
131
+ logger.info "Shutting down"
115
132
  launcher.stop
116
- # Explicitly exit so busy Processor threads can't block
117
- # process shutdown.
118
133
  logger.info "Bye!"
134
+
135
+ # Explicitly exit so busy Processor threads won't block process shutdown.
136
+ #
137
+ # NB: slow at_exit handlers will prevent a timely exit if they take
138
+ # a while to run. If Sidekiq is getting here but the process isn't exiting,
139
+ # use the TTIN signal to determine where things are stuck.
119
140
  exit(0)
120
141
  end
121
142
  end
122
143
 
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
156
+ end
157
+
158
+ def self.r
159
+ @@r ||= HOLIDAY_COLORS[day] || "\e[1;31m"
160
+ end
161
+
162
+ def self.b
163
+ @@b ||= HOLIDAY_COLORS[day] || "\e[30m"
164
+ end
165
+
166
+ def self.w
167
+ "\e[1;37m"
168
+ end
169
+
170
+ def self.reset
171
+ @@b = @@r = @@day = nil
172
+ "\e[0m"
173
+ end
174
+
123
175
  def self.banner
124
- %q{
125
- m,
126
- `$b
127
- .ss, $$: .,d$
128
- `$$P,d$P' .,md$P"'
129
- ,$$$$$bmmd$$$P^'
130
- .d$$$$$$$$$$P'
131
- $$^' `"^$$$' ____ _ _ _ _
132
- $: ,$$: / ___|(_) __| | ___| | _(_) __ _
133
- `b :$$ \___ \| |/ _` |/ _ \ |/ / |/ _` |
134
- $$: ___) | | (_| | __/ <| | (_| |
135
- $$ |____/|_|\__,_|\___|_|\_\_|\__, |
136
- .d$$ |_|
137
- }
176
+ %{
177
+ #{w} m,
178
+ #{w} `$b
179
+ #{w} .ss, $$: .,d$
180
+ #{w} `$$P,d$P' .,md$P"'
181
+ #{w} ,$$$$$b#{b}/#{w}md$$$P^'
182
+ #{w} .d$$$$$$#{b}/#{w}$$$P'
183
+ #{w} $$^' `"#{b}/#{w}$$$' #{r}____ _ _ _ _
184
+ #{w} $: #{b}'#{w},$$: #{r} / ___|(_) __| | ___| | _(_) __ _
185
+ #{w} `b :$$ #{r} \\___ \\| |/ _` |/ _ \\ |/ / |/ _` |
186
+ #{w} $$: #{r} ___) | | (_| | __/ <| | (_| |
187
+ #{w} $$ #{r}|____/|_|\\__,_|\\___|_|\\_\\_|\\__, |
188
+ #{w} .d$$ #{r} |_|
189
+ #{reset}}
138
190
  end
139
191
 
140
192
  SIGNAL_HANDLERS = {
141
193
  # Ctrl-C in terminal
142
- 'INT' => ->(cli) { raise Interrupt },
194
+ "INT" => ->(cli) { raise Interrupt },
143
195
  # TERM is the signal that Sidekiq must exit.
144
196
  # Heroku sends TERM and then waits 30 seconds for process to exit.
145
- 'TERM' => ->(cli) { raise Interrupt },
146
- 'USR1' => ->(cli) {
147
- Sidekiq.logger.info "Received USR1, no longer accepting new work"
148
- cli.launcher.quiet
149
- },
150
- 'TSTP' => ->(cli) {
151
- Sidekiq.logger.info "Received TSTP, no longer accepting new work"
197
+ "TERM" => ->(cli) { raise Interrupt },
198
+ "TSTP" => ->(cli) {
199
+ cli.logger.info "Received TSTP, no longer accepting new work"
152
200
  cli.launcher.quiet
153
201
  },
154
- 'USR2' => ->(cli) {
155
- if Sidekiq.options[:logfile]
156
- Sidekiq.logger.info "Received USR2, reopening log file"
157
- Sidekiq::Logging.reopen_logs
158
- end
159
- },
160
- 'TTIN' => ->(cli) {
202
+ "TTIN" => ->(cli) {
161
203
  Thread.list.each do |thread|
162
- Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread['sidekiq_label']}"
204
+ cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
163
205
  if thread.backtrace
164
- Sidekiq.logger.warn thread.backtrace.join("\n")
206
+ cli.logger.warn thread.backtrace.join("\n")
165
207
  else
166
- Sidekiq.logger.warn "<no backtrace available>"
208
+ cli.logger.warn "<no backtrace available>"
167
209
  end
168
210
  end
169
- },
211
+ }
170
212
  }
213
+ UNHANDLED_SIGNAL_HANDLER = ->(cli) { cli.logger.info "No signal handler registered, ignoring" }
214
+ SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER
171
215
 
172
216
  def handle_signal(sig)
173
- Sidekiq.logger.debug "Got #{sig} signal"
174
- handy = SIGNAL_HANDLERS[sig]
175
- if handy
176
- handy.call(self)
177
- else
178
- Sidekiq.logger.info { "No signal handler for #{sig}" }
179
- end
217
+ logger.debug "Got #{sig} signal"
218
+ SIGNAL_HANDLERS[sig].call(self)
180
219
  end
181
220
 
182
221
  private
183
222
 
184
223
  def print_banner
185
- puts "\e[#{31}m"
224
+ puts "\e[31m"
186
225
  puts Sidekiq::CLI.banner
187
226
  puts "\e[0m"
188
227
  end
189
228
 
190
- def daemonize
191
- raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
192
-
193
- files_to_reopen = ObjectSpace.each_object(File).reject { |f| f.closed? }
194
- ::Process.daemon(true, true)
195
-
196
- files_to_reopen.each do |file|
197
- begin
198
- file.reopen file.path, "a+"
199
- file.sync = true
200
- rescue ::Exception
201
- end
202
- end
203
-
204
- [$stdout, $stderr].each do |io|
205
- File.open(options[:logfile], 'ab') do |f|
206
- io.reopen(f)
207
- end
208
- io.sync = true
209
- end
210
- $stdin.reopen('/dev/null')
211
-
212
- initialize_logger
213
- end
214
-
215
229
  def set_environment(cli_env)
216
- @environment = cli_env || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
230
+ # See #984 for discussion.
231
+ # APP_ENV is now the preferred ENV term since it is not tech-specific.
232
+ # Both Sinatra 2.0+ and Sidekiq support this term.
233
+ # RAILS_ENV and RACK_ENV are there for legacy support.
234
+ @environment = cli_env || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
235
+ config[:environment] = @environment
217
236
  end
218
237
 
219
238
  def symbolize_keys_deep!(hash)
220
239
  hash.keys.each do |k|
221
240
  symkey = k.respond_to?(:to_sym) ? k.to_sym : k
222
241
  hash[symkey] = hash.delete k
223
- symbolize_keys_deep! hash[symkey] if hash[symkey].kind_of? Hash
242
+ symbolize_keys_deep! hash[symkey] if hash[symkey].is_a? Hash
224
243
  end
225
244
  end
226
245
 
@@ -235,64 +254,67 @@ module Sidekiq
235
254
 
236
255
  # check config file presence
237
256
  if opts[:config_file]
238
- if opts[:config_file] && !File.exist?(opts[:config_file])
257
+ unless File.exist?(opts[:config_file])
239
258
  raise ArgumentError, "No such file #{opts[:config_file]}"
240
259
  end
241
260
  else
242
- if opts[:require] && File.directory?(opts[:require])
243
- %w[config/sidekiq.yml config/sidekiq.yml.erb].each do |filename|
244
- path = File.expand_path(filename, opts[:require])
245
- opts[:config_file] ||= path if File.exist?(path)
246
- end
261
+ config_dir = if File.directory?(opts[:require].to_s)
262
+ File.join(opts[:require], "config")
263
+ else
264
+ File.join(@config[:require], "config")
265
+ end
266
+
267
+ %w[sidekiq.yml sidekiq.yml.erb].each do |config_file|
268
+ path = File.join(config_dir, config_file)
269
+ opts[:config_file] ||= path if File.exist?(path)
247
270
  end
248
271
  end
249
272
 
250
273
  # parse config file options
251
274
  opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
252
275
 
253
- opts[:queues] = Array(opts[:queues]) << 'default' if opts[:queues].nil? || opts[:queues].empty?
254
- opts[:strict] = true if opts[:strict].nil?
276
+ # set defaults
277
+ opts[:queues] = ["default"] if opts[:queues].nil?
255
278
  opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
256
279
 
257
280
  # merge with defaults
258
- options.merge!(opts)
259
- end
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
260
287
 
261
- def options
262
- Sidekiq.options
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
263
294
  end
264
295
 
265
- def boot_system
266
- ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment
267
-
268
- if File.directory?(options[:require])
269
- require 'rails'
270
- if ::Rails::VERSION::MAJOR < 4
271
- raise "Sidekiq no longer supports this version of Rails"
272
- elsif ::Rails::VERSION::MAJOR == 4
273
- # Painful contortions, see 1791 for discussion
274
- # No autoloading, we want to force eager load for everything.
275
- require File.expand_path("#{options[:require]}/config/application.rb")
276
- ::Rails::Application.initializer "sidekiq.eager_load" do
277
- ::Rails.application.config.eager_load = true
278
- end
279
- require 'sidekiq/rails'
280
- require File.expand_path("#{options[:require]}/config/environment.rb")
281
- else
282
- require 'sidekiq/rails'
283
- require File.expand_path("#{options[:require]}/config/environment.rb")
296
+ def boot_application
297
+ ENV["RACK_ENV"] = ENV["RAILS_ENV"] = environment
298
+
299
+ if File.directory?(@config[:require])
300
+ require "rails"
301
+ if ::Rails::VERSION::MAJOR < 6
302
+ warn "Sidekiq #{Sidekiq::VERSION} only supports Rails 6+"
284
303
  end
285
- options[:tag] ||= default_tag
304
+ require "sidekiq/rails"
305
+ require File.expand_path("#{@config[:require]}/config/environment.rb")
306
+ @config[:tag] ||= default_tag
286
307
  else
287
- require options[:require]
308
+ require @config[:require]
288
309
  end
289
310
  end
290
311
 
291
312
  def default_tag
292
313
  dir = ::Rails.root
293
314
  name = File.basename(dir)
294
- if name.to_i != 0 && prevdir = File.dirname(dir) # Capistrano release directory?
295
- if File.basename(prevdir) == 'releases'
315
+ prevdir = File.dirname(dir) # Capistrano release directory?
316
+ if name.to_i != 0 && prevdir
317
+ if File.basename(prevdir) == "releases"
296
318
  return File.basename(File.dirname(prevdir))
297
319
  end
298
320
  end
@@ -300,58 +322,52 @@ module Sidekiq
300
322
  end
301
323
 
302
324
  def validate!
303
- if !File.exist?(options[:require]) ||
304
- (File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
325
+ if !File.exist?(@config[:require]) ||
326
+ (File.directory?(@config[:require]) && !File.exist?("#{@config[:require]}/config/application.rb"))
305
327
  logger.info "=================================================================="
306
- logger.info " Please point sidekiq to a Rails 4/5 application or a Ruby file "
307
- logger.info " to load your worker classes with -r [DIR|FILE]."
328
+ logger.info " Please point Sidekiq to a Rails application or a Ruby file "
329
+ logger.info " to load your job classes with -r [DIR|FILE]."
308
330
  logger.info "=================================================================="
309
331
  logger.info @parser
310
332
  die(1)
311
333
  end
312
334
 
313
335
  [:concurrency, :timeout].each do |opt|
314
- raise ArgumentError, "#{opt}: #{options[opt]} is not a valid value" if options.has_key?(opt) && options[opt].to_i <= 0
336
+ raise ArgumentError, "#{opt}: #{@config[opt]} is not a valid value" if @config[opt].to_i <= 0
315
337
  end
316
338
  end
317
339
 
318
340
  def parse_options(argv)
319
341
  opts = {}
342
+ @parser = option_parser(opts)
343
+ @parser.parse!(argv)
344
+ opts
345
+ end
320
346
 
321
- @parser = OptionParser.new do |o|
322
- o.on '-c', '--concurrency INT', "processor threads to use" do |arg|
347
+ def option_parser(opts)
348
+ parser = OptionParser.new { |o|
349
+ o.on "-c", "--concurrency INT", "processor threads to use" do |arg|
323
350
  opts[:concurrency] = Integer(arg)
324
351
  end
325
352
 
326
- o.on '-d', '--daemon', "Daemonize process" do |arg|
327
- opts[:daemon] = arg
328
- puts "WARNING: Daemonization mode will be removed in Sidekiq 6.0, see #4045. Please use a proper process supervisor to start and manage your services"
329
- end
330
-
331
- o.on '-e', '--environment ENV', "Application environment" do |arg|
353
+ o.on "-e", "--environment ENV", "Application environment" do |arg|
332
354
  opts[:environment] = arg
333
355
  end
334
356
 
335
- o.on '-g', '--tag TAG', "Process tag for procline" do |arg|
357
+ o.on "-g", "--tag TAG", "Process tag for procline" do |arg|
336
358
  opts[:tag] = arg
337
359
  end
338
360
 
339
- # this index remains here for backwards compatibility but none of the Sidekiq
340
- # family use this value anymore. it was used by Pro's original reliable_fetch.
341
- o.on '-i', '--index INT', "unique process index on this machine" do |arg|
342
- opts[:index] = Integer(arg.match(/\d+/)[0])
343
- end
344
-
345
361
  o.on "-q", "--queue QUEUE[,WEIGHT]", "Queues to process with optional weights" do |arg|
346
- queue, weight = arg.split(",")
347
- parse_queue opts, queue, weight
362
+ opts[:queues] ||= []
363
+ opts[:queues] << arg
348
364
  end
349
365
 
350
- o.on '-r', '--require [PATH|DIR]', "Location of Rails application with workers or file to require" do |arg|
366
+ o.on "-r", "--require [PATH|DIR]", "Location of Rails application with jobs or file to require" do |arg|
351
367
  opts[:require] = arg
352
368
  end
353
369
 
354
- o.on '-t', '--timeout NUM', "Shutdown timeout" do |arg|
370
+ o.on "-t", "--timeout NUM", "Shutdown timeout" do |arg|
355
371
  opts[:timeout] = Integer(arg)
356
372
  end
357
373
 
@@ -359,54 +375,33 @@ module Sidekiq
359
375
  opts[:verbose] = arg
360
376
  end
361
377
 
362
- o.on '-C', '--config PATH', "path to YAML config file" do |arg|
378
+ o.on "-C", "--config PATH", "path to YAML config file" do |arg|
363
379
  opts[:config_file] = arg
364
380
  end
365
381
 
366
- o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
367
- opts[:logfile] = arg
368
- puts "WARNING: Logfile redirection will be removed in Sidekiq 6.0, see #4045. Sidekiq will only log to STDOUT"
369
- end
370
-
371
- o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
372
- opts[:pidfile] = arg
373
- puts "WARNING: PID file creation will be removed in Sidekiq 6.0, see #4045. 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
380
- end
386
+ }
381
387
 
382
- @parser.banner = "sidekiq [options]"
383
- @parser.on_tail "-h", "--help", "Show help" do
384
- logger.info @parser
388
+ parser.banner = "sidekiq [options]"
389
+ parser.on_tail "-h", "--help", "Show help" do
390
+ logger.info parser
385
391
  die 1
386
392
  end
387
393
 
388
- @parser.parse!(argv)
389
-
390
- opts
394
+ parser
391
395
  end
392
396
 
393
397
  def initialize_logger
394
- Sidekiq::Logging.initialize_logger(options[:logfile]) if options[:logfile]
395
-
396
- Sidekiq.logger.level = ::Logger::DEBUG if options[:verbose]
397
- end
398
-
399
- def write_pid
400
- if path = options[:pidfile]
401
- pidfile = File.expand_path(path)
402
- File.open(pidfile, 'w') do |f|
403
- f.puts ::Process.pid
404
- end
405
- end
398
+ @config.logger.level = ::Logger::DEBUG if @config[:verbose]
406
399
  end
407
400
 
408
401
  def parse_config(path)
409
- opts = YAML.load(ERB.new(File.read(path)).result) || {}
402
+ erb = ERB.new(File.read(path), trim_mode: "-")
403
+ erb.filename = File.expand_path(path)
404
+ opts = YAML.safe_load(erb.result, permitted_classes: [Symbol], aliases: true) || {}
410
405
 
411
406
  if opts.respond_to? :deep_symbolize_keys!
412
407
  opts.deep_symbolize_keys!
@@ -415,26 +410,16 @@ module Sidekiq
415
410
  end
416
411
 
417
412
  opts = opts.merge(opts.delete(environment.to_sym) || {})
418
- parse_queues(opts, opts.delete(:queues) || [])
413
+ opts.delete(:strict)
419
414
 
420
- ns = opts.delete(:namespace)
421
- if ns
422
- # logger hasn't been initialized yet, puts is all we have.
423
- puts("namespace should be set in your ruby initializer, is ignored in config file")
424
- puts("config.redis = { :url => ..., :namespace => '#{ns}' }")
425
- end
426
415
  opts
427
416
  end
428
417
 
429
- def parse_queues(opts, queues_and_weights)
430
- queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
431
- end
432
-
433
- def parse_queue(opts, queue, weight = nil)
434
- opts[:queues] ||= []
435
- raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
436
- [weight.to_i, 1].max.times { opts[:queues] << queue }
437
- opts[:strict] = false if weight.to_i > 0
418
+ def rails_app?
419
+ defined?(::Rails) && ::Rails.respond_to?(:application)
438
420
  end
439
421
  end
440
422
  end
423
+
424
+ require "sidekiq/systemd"
425
+ require "sidekiq/metrics/tracking"