sidekiq 4.2.10 → 7.3.10

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