sidekiq 3.5.4 → 7.2.0

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 (228) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +992 -6
  3. data/LICENSE.txt +9 -0
  4. data/README.md +52 -43
  5. data/bin/sidekiq +22 -4
  6. data/bin/sidekiqload +209 -115
  7. data/bin/sidekiqmon +11 -0
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
  11. data/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
  12. data/lib/sidekiq/api.rb +633 -295
  13. data/lib/sidekiq/capsule.rb +127 -0
  14. data/lib/sidekiq/cli.rb +270 -248
  15. data/lib/sidekiq/client.rb +139 -108
  16. data/lib/sidekiq/component.rb +68 -0
  17. data/lib/sidekiq/config.rb +287 -0
  18. data/lib/sidekiq/deploy.rb +62 -0
  19. data/lib/sidekiq/embedded.rb +61 -0
  20. data/lib/sidekiq/fetch.rb +53 -121
  21. data/lib/sidekiq/job.rb +374 -0
  22. data/lib/sidekiq/job_logger.rb +51 -0
  23. data/lib/sidekiq/job_retry.rb +301 -0
  24. data/lib/sidekiq/job_util.rb +107 -0
  25. data/lib/sidekiq/launcher.rb +241 -69
  26. data/lib/sidekiq/logger.rb +131 -0
  27. data/lib/sidekiq/manager.rb +88 -190
  28. data/lib/sidekiq/metrics/query.rb +155 -0
  29. data/lib/sidekiq/metrics/shared.rb +95 -0
  30. data/lib/sidekiq/metrics/tracking.rb +136 -0
  31. data/lib/sidekiq/middleware/chain.rb +114 -56
  32. data/lib/sidekiq/middleware/current_attributes.rb +95 -0
  33. data/lib/sidekiq/middleware/i18n.rb +8 -7
  34. data/lib/sidekiq/middleware/modules.rb +21 -0
  35. data/lib/sidekiq/monitor.rb +146 -0
  36. data/lib/sidekiq/paginator.rb +29 -16
  37. data/lib/sidekiq/processor.rb +238 -118
  38. data/lib/sidekiq/rails.rb +57 -27
  39. data/lib/sidekiq/redis_client_adapter.rb +111 -0
  40. data/lib/sidekiq/redis_connection.rb +49 -50
  41. data/lib/sidekiq/ring_buffer.rb +29 -0
  42. data/lib/sidekiq/scheduled.rb +173 -52
  43. data/lib/sidekiq/sd_notify.rb +149 -0
  44. data/lib/sidekiq/systemd.rb +24 -0
  45. data/lib/sidekiq/testing/inline.rb +7 -5
  46. data/lib/sidekiq/testing.rb +197 -65
  47. data/lib/sidekiq/transaction_aware_client.rb +44 -0
  48. data/lib/sidekiq/version.rb +4 -1
  49. data/lib/sidekiq/web/action.rb +93 -0
  50. data/lib/sidekiq/web/application.rb +463 -0
  51. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  52. data/lib/sidekiq/web/helpers.rb +364 -0
  53. data/lib/sidekiq/web/router.rb +104 -0
  54. data/lib/sidekiq/web.rb +113 -216
  55. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  56. data/lib/sidekiq.rb +99 -142
  57. data/sidekiq.gemspec +26 -23
  58. data/web/assets/images/apple-touch-icon.png +0 -0
  59. data/web/assets/javascripts/application.js +163 -74
  60. data/web/assets/javascripts/base-charts.js +106 -0
  61. data/web/assets/javascripts/chart.min.js +13 -0
  62. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  63. data/web/assets/javascripts/dashboard-charts.js +182 -0
  64. data/web/assets/javascripts/dashboard.js +37 -280
  65. data/web/assets/javascripts/metrics.js +298 -0
  66. data/web/assets/stylesheets/application-dark.css +147 -0
  67. data/web/assets/stylesheets/application-rtl.css +153 -0
  68. data/web/assets/stylesheets/application.css +181 -198
  69. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  70. data/web/assets/stylesheets/bootstrap.css +4 -8
  71. data/web/locales/ar.yml +87 -0
  72. data/web/locales/cs.yml +62 -52
  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 -62
  77. data/web/locales/es.yml +70 -53
  78. data/web/locales/fa.yml +80 -0
  79. data/web/locales/fr.yml +86 -56
  80. data/web/locales/gd.yml +99 -0
  81. data/web/locales/he.yml +80 -0
  82. data/web/locales/hi.yml +59 -59
  83. data/web/locales/it.yml +53 -53
  84. data/web/locales/ja.yml +78 -56
  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 -60
  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 +80 -0
  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 +10 -9
  101. data/web/views/_job_info.erb +26 -5
  102. data/web/views/_metrics_period_select.erb +12 -0
  103. data/web/views/_nav.erb +6 -20
  104. data/web/views/_paging.erb +3 -1
  105. data/web/views/_poll_link.erb +3 -6
  106. data/web/views/_summary.erb +7 -7
  107. data/web/views/busy.erb +87 -28
  108. data/web/views/dashboard.erb +51 -21
  109. data/web/views/dead.erb +4 -4
  110. data/web/views/filtering.erb +7 -0
  111. data/web/views/layout.erb +15 -5
  112. data/web/views/metrics.erb +91 -0
  113. data/web/views/metrics_for_job.erb +59 -0
  114. data/web/views/morgue.erb +25 -22
  115. data/web/views/queue.erb +35 -25
  116. data/web/views/queues.erb +23 -7
  117. data/web/views/retries.erb +28 -23
  118. data/web/views/retry.erb +5 -5
  119. data/web/views/scheduled.erb +19 -17
  120. data/web/views/scheduled_job_info.erb +1 -1
  121. metadata +86 -268
  122. data/.gitignore +0 -12
  123. data/.travis.yml +0 -16
  124. data/3.0-Upgrade.md +0 -70
  125. data/COMM-LICENSE +0 -95
  126. data/Contributing.md +0 -32
  127. data/Ent-Changes.md +0 -39
  128. data/Gemfile +0 -27
  129. data/LICENSE +0 -9
  130. data/Pro-2.0-Upgrade.md +0 -138
  131. data/Pro-Changes.md +0 -454
  132. data/Rakefile +0 -9
  133. data/bin/sidekiqctl +0 -93
  134. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +0 -6
  135. data/lib/generators/sidekiq/templates/worker_test.rb.erb +0 -8
  136. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  137. data/lib/sidekiq/actor.rb +0 -39
  138. data/lib/sidekiq/core_ext.rb +0 -105
  139. data/lib/sidekiq/exception_handler.rb +0 -30
  140. data/lib/sidekiq/extensions/action_mailer.rb +0 -56
  141. data/lib/sidekiq/extensions/active_record.rb +0 -39
  142. data/lib/sidekiq/extensions/class_methods.rb +0 -39
  143. data/lib/sidekiq/extensions/generic_proxy.rb +0 -24
  144. data/lib/sidekiq/logging.rb +0 -104
  145. data/lib/sidekiq/middleware/server/active_record.rb +0 -13
  146. data/lib/sidekiq/middleware/server/logging.rb +0 -40
  147. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -206
  148. data/lib/sidekiq/util.rb +0 -68
  149. data/lib/sidekiq/web_helpers.rb +0 -249
  150. data/lib/sidekiq/worker.rb +0 -103
  151. data/test/config.yml +0 -9
  152. data/test/env_based_config.yml +0 -11
  153. data/test/fake_env.rb +0 -0
  154. data/test/fixtures/en.yml +0 -2
  155. data/test/helper.rb +0 -49
  156. data/test/test_api.rb +0 -493
  157. data/test/test_cli.rb +0 -335
  158. data/test/test_client.rb +0 -194
  159. data/test/test_exception_handler.rb +0 -55
  160. data/test/test_extensions.rb +0 -126
  161. data/test/test_fetch.rb +0 -104
  162. data/test/test_logging.rb +0 -34
  163. data/test/test_manager.rb +0 -168
  164. data/test/test_middleware.rb +0 -159
  165. data/test/test_processor.rb +0 -237
  166. data/test/test_rails.rb +0 -21
  167. data/test/test_redis_connection.rb +0 -126
  168. data/test/test_retry.rb +0 -325
  169. data/test/test_scheduled.rb +0 -114
  170. data/test/test_scheduling.rb +0 -49
  171. data/test/test_sidekiq.rb +0 -99
  172. data/test/test_testing.rb +0 -142
  173. data/test/test_testing_fake.rb +0 -268
  174. data/test/test_testing_inline.rb +0 -93
  175. data/test/test_util.rb +0 -16
  176. data/test/test_web.rb +0 -608
  177. data/test/test_web_helpers.rb +0 -53
  178. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  179. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  180. data/web/assets/images/status/active.png +0 -0
  181. data/web/assets/images/status/idle.png +0 -0
  182. data/web/assets/javascripts/locales/README.md +0 -27
  183. data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
  184. data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
  185. data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
  186. data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
  187. data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
  188. data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
  189. data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
  190. data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
  191. data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
  192. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
  193. data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
  194. data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
  195. data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
  196. data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
  197. data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
  198. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
  199. data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
  200. data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
  201. data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
  202. data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
  203. data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
  204. data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
  205. data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
  206. data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
  207. data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
  208. data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
  209. data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
  210. data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
  211. data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
  212. data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
  213. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
  214. data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
  215. data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
  216. data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
  217. data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
  218. data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
  219. data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
  220. data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
  221. data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
  222. data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
  223. data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
  224. data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
  225. data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
  226. data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
  227. data/web/views/_poll_js.erb +0 -5
  228. /data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
data/lib/sidekiq/cli.rb CHANGED
@@ -1,261 +1,318 @@
1
- # encoding: utf-8
2
- $stdout.sync = true
1
+ # frozen_string_literal: true
3
2
 
4
- require 'yaml'
5
- require 'singleton'
6
- require 'optparse'
7
- require 'erb'
8
- require 'fileutils'
3
+ $stdout.sync = true
9
4
 
10
- require 'sidekiq'
11
- require 'sidekiq/util'
5
+ require "yaml"
6
+ require "singleton"
7
+ require "optparse"
8
+ require "erb"
9
+ require "fileutils"
12
10
 
13
- module Sidekiq
14
- # We are shutting down Sidekiq but what about workers that
15
- # are working on some long job? This error is
16
- # raised in workers that have not finished within the hard
17
- # timeout limit. This is needed to rollback db transactions,
18
- # otherwise Ruby's Thread#kill will commit. See #377.
19
- # DO NOT RESCUE THIS ERROR.
20
- class Shutdown < Interrupt; end
11
+ require "sidekiq"
12
+ require "sidekiq/config"
13
+ require "sidekiq/component"
14
+ require "sidekiq/capsule"
15
+ require "sidekiq/launcher"
21
16
 
17
+ module Sidekiq # :nodoc:
22
18
  class CLI
23
- include Util
19
+ include Sidekiq::Component
24
20
  include Singleton unless $TESTING
25
21
 
26
- # Used for CLI testing
27
- attr_accessor :code
28
22
  attr_accessor :launcher
29
23
  attr_accessor :environment
24
+ attr_accessor :config
30
25
 
31
- def initialize
32
- @code = nil
33
- end
34
-
35
- def parse(args=ARGV)
36
- @code = nil
26
+ def parse(args = ARGV.dup)
27
+ @config ||= Sidekiq.default_configuration
37
28
 
38
29
  setup_options(args)
39
30
  initialize_logger
40
31
  validate!
41
- daemonize
42
- write_pid
43
- load_celluloid
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)
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).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
- fire_event(:startup)
73
+ # touch the connection pool so it is created before we
74
+ # fire startup and start multithreading.
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")
70
78
 
71
- logger.debug {
72
- "Middleware: #{Sidekiq.server_middleware.map(&:klass).join(', ')}"
73
- }
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
74
91
 
75
- Sidekiq.redis do |conn|
76
- # touch the connection pool so it is created before we
77
- # launch the actors.
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
78
96
  end
79
97
 
80
- if !options[:daemon]
81
- logger.info 'Starting processing, hit Ctrl-C to stop'
98
+ # cache process identity
99
+ @config[:identity] = identity
100
+
101
+ # Touch middleware so it isn't lazy loaded by multiple threads, #3043
102
+ @config.server_middleware
103
+
104
+ # Before this point, the process is initializing with just the main thread.
105
+ # Starting here the process will now have multiple threads running.
106
+ fire_event(:startup, reverse: false, reraise: true)
107
+
108
+ logger.debug { "Client Middleware: #{@config.default_capsule.client_middleware.map(&:klass).join(", ")}" }
109
+ logger.debug { "Server Middleware: #{@config.default_capsule.server_middleware.map(&:klass).join(", ")}" }
110
+
111
+ launch(self_read)
112
+ end
113
+
114
+ def launch(self_read)
115
+ if environment == "development" && $stdout.tty?
116
+ logger.info "Starting processing, hit Ctrl-C to stop"
82
117
  end
83
118
 
84
- require 'sidekiq/launcher'
85
- @launcher = Sidekiq::Launcher.new(options)
119
+ @launcher = Sidekiq::Launcher.new(@config)
86
120
 
87
121
  begin
88
122
  launcher.run
89
123
 
90
- while readable_io = IO.select([self_read])
91
- signal = readable_io.first[0].gets.strip
124
+ while self_read.wait_readable
125
+ signal = self_read.gets.strip
92
126
  handle_signal(signal)
93
127
  end
94
128
  rescue Interrupt
95
- logger.info 'Shutting down'
129
+ logger.info "Shutting down"
96
130
  launcher.stop
97
- # Explicitly exit so busy Processor threads can't block
98
- # process shutdown.
131
+ logger.info "Bye!"
132
+
133
+ # Explicitly exit so busy Processor threads won't block process shutdown.
134
+ #
135
+ # NB: slow at_exit handlers will prevent a timely exit if they take
136
+ # a while to run. If Sidekiq is getting here but the process isn't exiting,
137
+ # use the TTIN signal to determine where things are stuck.
99
138
  exit(0)
100
139
  end
101
140
  end
102
141
 
142
+ HOLIDAY_COLORS = {
143
+ # got other color-specific holidays from around the world?
144
+ # https://developer-book.com/post/definitive-guide-for-colored-text-in-terminal/#256-color-escape-codes
145
+ "3-17" => "\e[1;32m", # St. Patrick's Day green
146
+ "10-31" => "\e[38;5;208m" # Halloween orange
147
+ }
148
+
149
+ def self.day
150
+ @@day ||= begin
151
+ t = Date.today
152
+ "#{t.month}-#{t.day}"
153
+ end
154
+ end
155
+
156
+ def self.r
157
+ @@r ||= HOLIDAY_COLORS[day] || "\e[1;31m"
158
+ end
159
+
160
+ def self.b
161
+ @@b ||= HOLIDAY_COLORS[day] || "\e[30m"
162
+ end
163
+
164
+ def self.w
165
+ "\e[1;37m"
166
+ end
167
+
168
+ def self.reset
169
+ @@b = @@r = @@day = nil
170
+ "\e[0m"
171
+ end
172
+
103
173
  def self.banner
104
- %q{
105
- m,
106
- `$b
107
- .ss, $$: .,d$
108
- `$$P,d$P' .,md$P"'
109
- ,$$$$$bmmd$$$P^'
110
- .d$$$$$$$$$$P'
111
- $$^' `"^$$$' ____ _ _ _ _
112
- $: ,$$: / ___|(_) __| | ___| | _(_) __ _
113
- `b :$$ \___ \| |/ _` |/ _ \ |/ / |/ _` |
114
- $$: ___) | | (_| | __/ <| | (_| |
115
- $$ |____/|_|\__,_|\___|_|\_\_|\__, |
116
- .d$$ |_|
117
- }
174
+ %{
175
+ #{w} m,
176
+ #{w} `$b
177
+ #{w} .ss, $$: .,d$
178
+ #{w} `$$P,d$P' .,md$P"'
179
+ #{w} ,$$$$$b#{b}/#{w}md$$$P^'
180
+ #{w} .d$$$$$$#{b}/#{w}$$$P'
181
+ #{w} $$^' `"#{b}/#{w}$$$' #{r}____ _ _ _ _
182
+ #{w} $: #{b}'#{w},$$: #{r} / ___|(_) __| | ___| | _(_) __ _
183
+ #{w} `b :$$ #{r} \\___ \\| |/ _` |/ _ \\ |/ / |/ _` |
184
+ #{w} $$: #{r} ___) | | (_| | __/ <| | (_| |
185
+ #{w} $$ #{r}|____/|_|\\__,_|\\___|_|\\_\\_|\\__, |
186
+ #{w} .d$$ #{r} |_|
187
+ #{reset}}
118
188
  end
119
189
 
120
- def handle_signal(sig)
121
- Sidekiq.logger.debug "Got #{sig} signal"
122
- case sig
123
- when 'INT'
124
- # Handle Ctrl-C in JRuby like MRI
125
- # http://jira.codehaus.org/browse/JRUBY-4637
126
- raise Interrupt
127
- when 'TERM'
128
- # Heroku sends TERM and then waits 10 seconds for process to exit.
129
- raise Interrupt
130
- when 'USR1'
131
- Sidekiq.logger.info "Received USR1, no longer accepting new work"
132
- launcher.manager.async.stop
133
- fire_event(:quiet, true)
134
- when 'USR2'
135
- if Sidekiq.options[:logfile]
136
- Sidekiq.logger.info "Received USR2, reopening log file"
137
- Sidekiq::Logging.reopen_logs
138
- end
139
- when 'TTIN'
190
+ SIGNAL_HANDLERS = {
191
+ # Ctrl-C in terminal
192
+ "INT" => ->(cli) { raise Interrupt },
193
+ # TERM is the signal that Sidekiq must exit.
194
+ # Heroku sends TERM and then waits 30 seconds for process to exit.
195
+ "TERM" => ->(cli) { raise Interrupt },
196
+ "TSTP" => ->(cli) {
197
+ cli.logger.info "Received TSTP, no longer accepting new work"
198
+ cli.launcher.quiet
199
+ },
200
+ "TTIN" => ->(cli) {
140
201
  Thread.list.each do |thread|
141
- Sidekiq.logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
202
+ cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
142
203
  if thread.backtrace
143
- Sidekiq.logger.warn thread.backtrace.join("\n")
204
+ cli.logger.warn thread.backtrace.join("\n")
144
205
  else
145
- Sidekiq.logger.warn "<no backtrace available>"
206
+ cli.logger.warn "<no backtrace available>"
146
207
  end
147
208
  end
148
- end
209
+ }
210
+ }
211
+ UNHANDLED_SIGNAL_HANDLER = ->(cli) { cli.logger.info "No signal handler registered, ignoring" }
212
+ SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER
213
+
214
+ def handle_signal(sig)
215
+ logger.debug "Got #{sig} signal"
216
+ SIGNAL_HANDLERS[sig].call(self)
149
217
  end
150
218
 
151
219
  private
152
220
 
153
221
  def print_banner
154
- # Print logo and banner for development
155
- if environment == 'development' && $stdout.tty?
156
- puts "\e[#{31}m"
157
- puts Sidekiq::CLI.banner
158
- puts "\e[0m"
159
- end
222
+ puts "\e[31m"
223
+ puts Sidekiq::CLI.banner
224
+ puts "\e[0m"
160
225
  end
161
226
 
162
- def load_celluloid
163
- raise "Celluloid cannot be required until here, or it will break Sidekiq's daemonization" if defined?(::Celluloid) && options[:daemon]
164
-
165
- # Celluloid can't be loaded until after we've daemonized
166
- # because it spins up threads and creates locks which get
167
- # into a very bad state if forked.
168
- require 'celluloid/current'
169
- Celluloid.logger = (options[:verbose] ? Sidekiq.logger : nil)
170
-
171
- require 'sidekiq/manager'
172
- require 'sidekiq/scheduled'
227
+ def set_environment(cli_env)
228
+ # See #984 for discussion.
229
+ # APP_ENV is now the preferred ENV term since it is not tech-specific.
230
+ # Both Sinatra 2.0+ and Sidekiq support this term.
231
+ # RAILS_ENV and RACK_ENV are there for legacy support.
232
+ @environment = cli_env || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
233
+ config[:environment] = @environment
173
234
  end
174
235
 
175
- def daemonize
176
- return unless options[:daemon]
177
-
178
- raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
179
- files_to_reopen = []
180
- ObjectSpace.each_object(File) do |file|
181
- files_to_reopen << file unless file.closed?
236
+ def symbolize_keys_deep!(hash)
237
+ hash.keys.each do |k|
238
+ symkey = k.respond_to?(:to_sym) ? k.to_sym : k
239
+ hash[symkey] = hash.delete k
240
+ symbolize_keys_deep! hash[symkey] if hash[symkey].is_a? Hash
182
241
  end
183
-
184
- ::Process.daemon(true, true)
185
-
186
- files_to_reopen.each do |file|
187
- begin
188
- file.reopen file.path, "a+"
189
- file.sync = true
190
- rescue ::Exception
191
- end
192
- end
193
-
194
- [$stdout, $stderr].each do |io|
195
- File.open(options[:logfile], 'ab') do |f|
196
- io.reopen(f)
197
- end
198
- io.sync = true
199
- end
200
- $stdin.reopen('/dev/null')
201
-
202
- initialize_logger
203
- end
204
-
205
- def set_environment(cli_env)
206
- @environment = cli_env || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
207
242
  end
208
243
 
209
244
  alias_method :die, :exit
210
245
  alias_method :☠, :exit
211
246
 
212
247
  def setup_options(args)
248
+ # parse CLI options
213
249
  opts = parse_options(args)
250
+
214
251
  set_environment opts[:environment]
215
252
 
216
- cfile = opts[:config_file]
217
- opts = parse_config(cfile).merge(opts) if cfile
253
+ # check config file presence
254
+ if opts[:config_file]
255
+ unless File.exist?(opts[:config_file])
256
+ raise ArgumentError, "No such file #{opts[:config_file]}"
257
+ end
258
+ else
259
+ config_dir = if File.directory?(opts[:require].to_s)
260
+ File.join(opts[:require], "config")
261
+ else
262
+ File.join(@config[:require], "config")
263
+ end
264
+
265
+ %w[sidekiq.yml sidekiq.yml.erb].each do |config_file|
266
+ path = File.join(config_dir, config_file)
267
+ opts[:config_file] ||= path if File.exist?(path)
268
+ end
269
+ end
218
270
 
219
- opts[:strict] = true if opts[:strict].nil?
271
+ # parse config file options
272
+ opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
220
273
 
221
- options.merge!(opts)
222
- end
274
+ # set defaults
275
+ opts[:queues] = ["default"] if opts[:queues].nil?
276
+ opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
223
277
 
224
- def options
225
- Sidekiq.options
226
- end
278
+ # merge with defaults
279
+ @config.merge!(opts)
227
280
 
228
- def boot_system
229
- ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment
281
+ @config.default_capsule.tap do |cap|
282
+ cap.queues = opts[:queues]
283
+ cap.concurrency = opts[:concurrency] || @config[:concurrency]
284
+ end
230
285
 
231
- raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
286
+ opts[:capsules]&.each do |name, cap_config|
287
+ @config.capsule(name.to_s) do |cap|
288
+ cap.queues = cap_config[:queues]
289
+ cap.concurrency = cap_config[:concurrency]
290
+ end
291
+ end
292
+ end
232
293
 
233
- if File.directory?(options[:require])
234
- require 'rails'
235
- if ::Rails::VERSION::MAJOR < 4
236
- require 'sidekiq/rails'
237
- require File.expand_path("#{options[:require]}/config/environment.rb")
238
- ::Rails.application.eager_load!
239
- else
240
- # Painful contortions, see 1791 for discussion
241
- require File.expand_path("#{options[:require]}/config/application.rb")
242
- ::Rails::Application.initializer "sidekiq.eager_load" do
243
- ::Rails.application.config.eager_load = true
244
- end
245
- require 'sidekiq/rails'
246
- require File.expand_path("#{options[:require]}/config/environment.rb")
294
+ def boot_application
295
+ ENV["RACK_ENV"] = ENV["RAILS_ENV"] = environment
296
+
297
+ if File.directory?(@config[:require])
298
+ require "rails"
299
+ if ::Rails::VERSION::MAJOR < 6
300
+ warn "Sidekiq #{Sidekiq::VERSION} only supports Rails 6+"
247
301
  end
248
- options[:tag] ||= default_tag
302
+ require "sidekiq/rails"
303
+ require File.expand_path("#{@config[:require]}/config/environment.rb")
304
+ @config[:tag] ||= default_tag
249
305
  else
250
- require options[:require]
306
+ require @config[:require]
251
307
  end
252
308
  end
253
309
 
254
310
  def default_tag
255
311
  dir = ::Rails.root
256
312
  name = File.basename(dir)
257
- if name.to_i != 0 && prevdir = File.dirname(dir) # Capistrano release directory?
258
- if File.basename(prevdir) == 'releases'
313
+ prevdir = File.dirname(dir) # Capistrano release directory?
314
+ if name.to_i != 0 && prevdir
315
+ if File.basename(prevdir) == "releases"
259
316
  return File.basename(File.dirname(prevdir))
260
317
  end
261
318
  end
@@ -263,57 +320,52 @@ module Sidekiq
263
320
  end
264
321
 
265
322
  def validate!
266
- options[:queues] << 'default' if options[:queues].empty?
267
-
268
- if !File.exist?(options[:require]) ||
269
- (File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
323
+ if !File.exist?(@config[:require]) ||
324
+ (File.directory?(@config[:require]) && !File.exist?("#{@config[:require]}/config/application.rb"))
270
325
  logger.info "=================================================================="
271
- logger.info " Please point sidekiq to a Rails 3/4 application or a Ruby file "
272
- logger.info " to load your worker classes with -r [DIR|FILE]."
326
+ logger.info " Please point Sidekiq to a Rails application or a Ruby file "
327
+ logger.info " to load your job classes with -r [DIR|FILE]."
273
328
  logger.info "=================================================================="
274
329
  logger.info @parser
275
330
  die(1)
276
331
  end
277
332
 
278
333
  [:concurrency, :timeout].each do |opt|
279
- raise ArgumentError, "#{opt}: #{options[opt]} is not a valid value" if options.has_key?(opt) && options[opt].to_i <= 0
334
+ raise ArgumentError, "#{opt}: #{@config[opt]} is not a valid value" if @config[opt].to_i <= 0
280
335
  end
281
336
  end
282
337
 
283
338
  def parse_options(argv)
284
339
  opts = {}
340
+ @parser = option_parser(opts)
341
+ @parser.parse!(argv)
342
+ opts
343
+ end
285
344
 
286
- @parser = OptionParser.new do |o|
287
- o.on '-c', '--concurrency INT', "processor threads to use" do |arg|
345
+ def option_parser(opts)
346
+ parser = OptionParser.new { |o|
347
+ o.on "-c", "--concurrency INT", "processor threads to use" do |arg|
288
348
  opts[:concurrency] = Integer(arg)
289
349
  end
290
350
 
291
- o.on '-d', '--daemon', "Daemonize process" do |arg|
292
- opts[:daemon] = arg
293
- end
294
-
295
- o.on '-e', '--environment ENV', "Application environment" do |arg|
351
+ o.on "-e", "--environment ENV", "Application environment" do |arg|
296
352
  opts[:environment] = arg
297
353
  end
298
354
 
299
- o.on '-g', '--tag TAG', "Process tag for procline" do |arg|
355
+ o.on "-g", "--tag TAG", "Process tag for procline" do |arg|
300
356
  opts[:tag] = arg
301
357
  end
302
358
 
303
- o.on '-i', '--index INT', "unique process index on this machine" do |arg|
304
- opts[:index] = Integer(arg.match(/\d+/)[0])
305
- end
306
-
307
359
  o.on "-q", "--queue QUEUE[,WEIGHT]", "Queues to process with optional weights" do |arg|
308
- queue, weight = arg.split(",")
309
- parse_queue opts, queue, weight
360
+ opts[:queues] ||= []
361
+ opts[:queues] << arg
310
362
  end
311
363
 
312
- o.on '-r', '--require [PATH|DIR]', "Location of Rails application with workers or file to require" do |arg|
364
+ o.on "-r", "--require [PATH|DIR]", "Location of Rails application with jobs or file to require" do |arg|
313
365
  opts[:require] = arg
314
366
  end
315
367
 
316
- o.on '-t', '--timeout NUM', "Shutdown timeout" do |arg|
368
+ o.on "-t", "--timeout NUM", "Shutdown timeout" do |arg|
317
369
  opts[:timeout] = Integer(arg)
318
370
  end
319
371
 
@@ -321,81 +373,51 @@ module Sidekiq
321
373
  opts[:verbose] = arg
322
374
  end
323
375
 
324
- o.on '-C', '--config PATH', "path to YAML config file" do |arg|
376
+ o.on "-C", "--config PATH", "path to YAML config file" do |arg|
325
377
  opts[:config_file] = arg
326
378
  end
327
379
 
328
- o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
329
- opts[:logfile] = arg
330
- end
331
-
332
- o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
333
- opts[:pidfile] = arg
334
- end
335
-
336
- o.on '-V', '--version', "Print version and exit" do |arg|
380
+ o.on "-V", "--version", "Print version and exit" do
337
381
  puts "Sidekiq #{Sidekiq::VERSION}"
338
382
  die(0)
339
383
  end
340
- end
384
+ }
341
385
 
342
- @parser.banner = "sidekiq [options]"
343
- @parser.on_tail "-h", "--help", "Show help" do
344
- logger.info @parser
386
+ parser.banner = "sidekiq [options]"
387
+ parser.on_tail "-h", "--help", "Show help" do
388
+ logger.info parser
345
389
  die 1
346
390
  end
347
- @parser.parse!(argv)
348
-
349
- %w[config/sidekiq.yml config/sidekiq.yml.erb].each do |filename|
350
- opts[:config_file] ||= filename if File.exist?(filename)
351
- end
352
391
 
353
- opts
392
+ parser
354
393
  end
355
394
 
356
395
  def initialize_logger
357
- Sidekiq::Logging.initialize_logger(options[:logfile]) if options[:logfile]
358
-
359
- Sidekiq.logger.level = ::Logger::DEBUG if options[:verbose]
396
+ @config.logger.level = ::Logger::DEBUG if @config[:verbose]
360
397
  end
361
398
 
362
- def write_pid
363
- if path = options[:pidfile]
364
- pidfile = File.expand_path(path)
365
- File.open(pidfile, 'w') do |f|
366
- f.puts ::Process.pid
367
- end
368
- end
369
- end
399
+ def parse_config(path)
400
+ erb = ERB.new(File.read(path), trim_mode: "-")
401
+ erb.filename = File.expand_path(path)
402
+ opts = YAML.safe_load(erb.result, permitted_classes: [Symbol], aliases: true) || {}
370
403
 
371
- def parse_config(cfile)
372
- opts = {}
373
- if File.exist?(cfile)
374
- opts = YAML.load(ERB.new(IO.read(cfile)).result) || opts
375
- opts = opts.merge(opts.delete(environment) || {})
376
- parse_queues(opts, opts.delete(:queues) || [])
404
+ if opts.respond_to? :deep_symbolize_keys!
405
+ opts.deep_symbolize_keys!
377
406
  else
378
- # allow a non-existent config file so Sidekiq
379
- # can be deployed by cap with just the defaults.
380
- end
381
- ns = opts.delete(:namespace)
382
- if ns
383
- # logger hasn't been initialized yet, puts is all we have.
384
- puts("namespace should be set in your ruby initializer, is ignored in config file")
385
- puts("config.redis = { :url => ..., :namespace => '#{ns}' }")
407
+ symbolize_keys_deep!(opts)
386
408
  end
387
- opts
388
- end
389
409
 
390
- def parse_queues(opts, queues_and_weights)
391
- queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
410
+ opts = opts.merge(opts.delete(environment.to_sym) || {})
411
+ opts.delete(:strict)
412
+
413
+ opts
392
414
  end
393
415
 
394
- def parse_queue(opts, q, weight=nil)
395
- [weight.to_i, 1].max.times do
396
- (opts[:queues] ||= []) << q
397
- end
398
- opts[:strict] = false if weight.to_i > 0
416
+ def rails_app?
417
+ defined?(::Rails) && ::Rails.respond_to?(:application)
399
418
  end
400
419
  end
401
420
  end
421
+
422
+ require "sidekiq/systemd"
423
+ require "sidekiq/metrics/tracking"