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