sidekiq 3.5.4 → 5.2.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (175) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +61 -0
  3. data/{Contributing.md → .github/contributing.md} +0 -0
  4. data/.github/issue_template.md +11 -0
  5. data/.gitignore +3 -0
  6. data/.travis.yml +5 -10
  7. data/4.0-Upgrade.md +53 -0
  8. data/5.0-Upgrade.md +56 -0
  9. data/COMM-LICENSE +13 -11
  10. data/Changes.md +376 -1
  11. data/Ent-Changes.md +201 -2
  12. data/Gemfile +14 -18
  13. data/LICENSE +1 -1
  14. data/Pro-3.0-Upgrade.md +44 -0
  15. data/Pro-4.0-Upgrade.md +35 -0
  16. data/Pro-Changes.md +307 -2
  17. data/README.md +34 -22
  18. data/Rakefile +3 -3
  19. data/bin/sidekiq +0 -1
  20. data/bin/sidekiqctl +13 -86
  21. data/bin/sidekiqload +23 -27
  22. data/code_of_conduct.md +50 -0
  23. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +3 -3
  24. data/lib/generators/sidekiq/templates/worker_test.rb.erb +6 -6
  25. data/lib/sidekiq.rb +72 -25
  26. data/lib/sidekiq/api.rb +206 -73
  27. data/lib/sidekiq/cli.rb +145 -101
  28. data/lib/sidekiq/client.rb +42 -36
  29. data/lib/sidekiq/core_ext.rb +1 -105
  30. data/lib/sidekiq/ctl.rb +221 -0
  31. data/lib/sidekiq/delay.rb +42 -0
  32. data/lib/sidekiq/exception_handler.rb +4 -5
  33. data/lib/sidekiq/extensions/action_mailer.rb +1 -0
  34. data/lib/sidekiq/extensions/active_record.rb +1 -0
  35. data/lib/sidekiq/extensions/class_methods.rb +1 -0
  36. data/lib/sidekiq/extensions/generic_proxy.rb +8 -1
  37. data/lib/sidekiq/fetch.rb +36 -111
  38. data/lib/sidekiq/job_logger.rb +25 -0
  39. data/lib/sidekiq/job_retry.rb +262 -0
  40. data/lib/sidekiq/launcher.rb +129 -55
  41. data/lib/sidekiq/logging.rb +21 -3
  42. data/lib/sidekiq/manager.rb +83 -182
  43. data/lib/sidekiq/middleware/chain.rb +1 -0
  44. data/lib/sidekiq/middleware/i18n.rb +1 -0
  45. data/lib/sidekiq/middleware/server/active_record.rb +10 -0
  46. data/lib/sidekiq/paginator.rb +1 -0
  47. data/lib/sidekiq/processor.rb +221 -103
  48. data/lib/sidekiq/rails.rb +47 -27
  49. data/lib/sidekiq/redis_connection.rb +74 -7
  50. data/lib/sidekiq/scheduled.rb +87 -28
  51. data/lib/sidekiq/testing.rb +150 -19
  52. data/lib/sidekiq/testing/inline.rb +1 -0
  53. data/lib/sidekiq/util.rb +15 -17
  54. data/lib/sidekiq/version.rb +2 -1
  55. data/lib/sidekiq/web.rb +120 -184
  56. data/lib/sidekiq/web/action.rb +89 -0
  57. data/lib/sidekiq/web/application.rb +353 -0
  58. data/lib/sidekiq/{web_helpers.rb → web/helpers.rb} +123 -47
  59. data/lib/sidekiq/web/router.rb +100 -0
  60. data/lib/sidekiq/worker.rb +135 -18
  61. data/sidekiq.gemspec +8 -14
  62. data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
  63. data/web/assets/javascripts/application.js +24 -20
  64. data/web/assets/javascripts/dashboard.js +33 -18
  65. data/web/assets/stylesheets/application-rtl.css +246 -0
  66. data/web/assets/stylesheets/application.css +401 -7
  67. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  68. data/web/assets/stylesheets/bootstrap.css +4 -8
  69. data/web/locales/ar.yml +81 -0
  70. data/web/locales/cs.yml +11 -1
  71. data/web/locales/de.yml +1 -1
  72. data/web/locales/en.yml +4 -0
  73. data/web/locales/es.yml +4 -3
  74. data/web/locales/fa.yml +80 -0
  75. data/web/locales/fr.yml +21 -12
  76. data/web/locales/he.yml +79 -0
  77. data/web/locales/ja.yml +24 -13
  78. data/web/locales/ru.yml +3 -0
  79. data/web/locales/ur.yml +80 -0
  80. data/web/views/_footer.erb +7 -9
  81. data/web/views/_job_info.erb +5 -1
  82. data/web/views/_nav.erb +5 -19
  83. data/web/views/_paging.erb +1 -1
  84. data/web/views/busy.erb +18 -9
  85. data/web/views/dashboard.erb +5 -5
  86. data/web/views/dead.erb +1 -1
  87. data/web/views/layout.erb +13 -5
  88. data/web/views/morgue.erb +16 -12
  89. data/web/views/queue.erb +12 -11
  90. data/web/views/queues.erb +5 -3
  91. data/web/views/retries.erb +19 -13
  92. data/web/views/retry.erb +2 -2
  93. data/web/views/scheduled.erb +4 -4
  94. data/web/views/scheduled_job_info.erb +1 -1
  95. metadata +45 -227
  96. data/lib/sidekiq/actor.rb +0 -39
  97. data/lib/sidekiq/middleware/server/logging.rb +0 -40
  98. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -206
  99. data/test/config.yml +0 -9
  100. data/test/env_based_config.yml +0 -11
  101. data/test/fake_env.rb +0 -0
  102. data/test/fixtures/en.yml +0 -2
  103. data/test/helper.rb +0 -49
  104. data/test/test_api.rb +0 -493
  105. data/test/test_cli.rb +0 -335
  106. data/test/test_client.rb +0 -194
  107. data/test/test_exception_handler.rb +0 -55
  108. data/test/test_extensions.rb +0 -126
  109. data/test/test_fetch.rb +0 -104
  110. data/test/test_logging.rb +0 -34
  111. data/test/test_manager.rb +0 -168
  112. data/test/test_middleware.rb +0 -159
  113. data/test/test_processor.rb +0 -237
  114. data/test/test_rails.rb +0 -21
  115. data/test/test_redis_connection.rb +0 -126
  116. data/test/test_retry.rb +0 -325
  117. data/test/test_scheduled.rb +0 -114
  118. data/test/test_scheduling.rb +0 -49
  119. data/test/test_sidekiq.rb +0 -99
  120. data/test/test_testing.rb +0 -142
  121. data/test/test_testing_fake.rb +0 -268
  122. data/test/test_testing_inline.rb +0 -93
  123. data/test/test_util.rb +0 -16
  124. data/test/test_web.rb +0 -608
  125. data/test/test_web_helpers.rb +0 -53
  126. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  127. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  128. data/web/assets/images/status/active.png +0 -0
  129. data/web/assets/images/status/idle.png +0 -0
  130. data/web/assets/javascripts/locales/README.md +0 -27
  131. data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
  132. data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
  133. data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
  134. data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
  135. data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
  136. data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
  137. data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
  138. data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
  139. data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
  140. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
  141. data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
  142. data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
  143. data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
  144. data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
  145. data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
  146. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
  147. data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
  148. data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
  149. data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
  150. data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
  151. data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
  152. data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
  153. data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
  154. data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
  155. data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
  156. data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
  157. data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
  158. data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
  159. data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
  160. data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
  161. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
  162. data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
  163. data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
  164. data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
  165. data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
  166. data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
  167. data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
  168. data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
  169. data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
  170. data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
  171. data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
  172. data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
  173. data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
  174. data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
  175. data/web/views/_poll_js.erb +0 -5
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
  $stdout.sync = true
3
3
 
4
4
  require 'yaml'
@@ -9,53 +9,55 @@ require 'fileutils'
9
9
 
10
10
  require 'sidekiq'
11
11
  require 'sidekiq/util'
12
+ require 'sidekiq/launcher'
12
13
 
13
14
  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
21
-
22
15
  class CLI
23
16
  include Util
24
17
  include Singleton unless $TESTING
25
18
 
26
- # Used for CLI testing
27
- attr_accessor :code
19
+ PROCTITLES = [
20
+ proc { 'sidekiq' },
21
+ proc { Sidekiq::VERSION },
22
+ proc { |me, data| data['tag'] },
23
+ proc { |me, data| "[#{Processor::WORKER_STATE.size} of #{data['concurrency']} busy]" },
24
+ proc { |me, data| "stopping" if me.stopping? },
25
+ ]
26
+
28
27
  attr_accessor :launcher
29
28
  attr_accessor :environment
30
29
 
31
- def initialize
32
- @code = nil
33
- end
34
-
35
- def parse(args=ARGV)
36
- @code = nil
37
-
30
+ def parse(args = ARGV)
38
31
  setup_options(args)
39
32
  initialize_logger
40
33
  validate!
41
- daemonize
42
- write_pid
43
- load_celluloid
34
+ end
35
+
36
+ def jruby?
37
+ defined?(::JRUBY_VERSION)
44
38
  end
45
39
 
46
40
  # Code within this method is not tested because it alters
47
41
  # global process state irreversibly. PRs which improve the
48
42
  # test coverage of Sidekiq::CLI are welcomed.
49
43
  def run
44
+ daemonize if options[:daemon]
45
+ write_pid
50
46
  boot_system
51
- print_banner
47
+ print_banner if environment == 'development' && $stdout.tty?
52
48
 
53
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'
55
+ end
54
56
 
55
- %w(INT TERM USR1 USR2 TTIN).each do |sig|
57
+ sigs.each do |sig|
56
58
  begin
57
59
  trap sig do
58
- self_write.puts(sig)
60
+ self_write.write("#{sig}\n")
59
61
  end
60
62
  rescue ArgumentError
61
63
  puts "Signal #{sig} not supported"
@@ -66,22 +68,39 @@ module Sidekiq
66
68
  logger.info Sidekiq::LICENSE
67
69
  logger.info "Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org" unless defined?(::Sidekiq::Pro)
68
70
 
69
- fire_event(:startup)
71
+ # touch the connection pool so it is created before we
72
+ # 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'
70
76
 
71
- logger.debug {
72
- "Middleware: #{Sidekiq.server_middleware.map(&:klass).join(', ')}"
73
- }
77
+ # Since the user can pass us a connection pool explicitly in the initializer, we
78
+ # 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
74
82
 
75
- Sidekiq.redis do |conn|
76
- # touch the connection pool so it is created before we
77
- # launch the actors.
78
- end
83
+ # cache process identity
84
+ Sidekiq.options[:identity] = identity
85
+
86
+ # Touch middleware so it isn't lazy loaded by multiple threads, #3043
87
+ Sidekiq.server_middleware
79
88
 
89
+ # Before this point, the process is initializing with just the main thread.
90
+ # Starting here the process will now have multiple threads running.
91
+ fire_event(:startup, reverse: false, reraise: true)
92
+
93
+ logger.debug { "Client Middleware: #{Sidekiq.client_middleware.map(&:klass).join(', ')}" }
94
+ logger.debug { "Server Middleware: #{Sidekiq.server_middleware.map(&:klass).join(', ')}" }
95
+
96
+ launch(self_read)
97
+ end
98
+
99
+ def launch(self_read)
80
100
  if !options[:daemon]
81
101
  logger.info 'Starting processing, hit Ctrl-C to stop'
82
102
  end
83
103
 
84
- require 'sidekiq/launcher'
85
104
  @launcher = Sidekiq::Launcher.new(options)
86
105
 
87
106
  begin
@@ -96,6 +115,7 @@ module Sidekiq
96
115
  launcher.stop
97
116
  # Explicitly exit so busy Processor threads can't block
98
117
  # process shutdown.
118
+ logger.info "Bye!"
99
119
  exit(0)
100
120
  end
101
121
  end
@@ -117,70 +137,60 @@ module Sidekiq
117
137
  }
118
138
  end
119
139
 
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'
140
+ SIGNAL_HANDLERS = {
141
+ # Ctrl-C in terminal
142
+ 'INT' => ->(cli) { raise Interrupt },
143
+ # TERM is the signal that Sidekiq must exit.
144
+ # Heroku sends TERM and then waits 30 seconds for process to exit.
145
+ 'TERM' => ->(cli) { raise Interrupt },
146
+ 'USR1' => ->(cli) {
131
147
  Sidekiq.logger.info "Received USR1, no longer accepting new work"
132
- launcher.manager.async.stop
133
- fire_event(:quiet, true)
134
- when 'USR2'
148
+ cli.launcher.quiet
149
+ },
150
+ 'TSTP' => ->(cli) {
151
+ Sidekiq.logger.info "Received TSTP, no longer accepting new work"
152
+ cli.launcher.quiet
153
+ },
154
+ 'USR2' => ->(cli) {
135
155
  if Sidekiq.options[:logfile]
136
156
  Sidekiq.logger.info "Received USR2, reopening log file"
137
157
  Sidekiq::Logging.reopen_logs
138
158
  end
139
- when 'TTIN'
159
+ },
160
+ 'TTIN' => ->(cli) {
140
161
  Thread.list.each do |thread|
141
- Sidekiq.logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
162
+ Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread['sidekiq_label']}"
142
163
  if thread.backtrace
143
164
  Sidekiq.logger.warn thread.backtrace.join("\n")
144
165
  else
145
166
  Sidekiq.logger.warn "<no backtrace available>"
146
167
  end
147
168
  end
169
+ },
170
+ }
171
+
172
+ 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}" }
148
179
  end
149
180
  end
150
181
 
151
182
  private
152
183
 
153
184
  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
160
- end
161
-
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'
185
+ puts "\e[#{31}m"
186
+ puts Sidekiq::CLI.banner
187
+ puts "\e[0m"
173
188
  end
174
189
 
175
190
  def daemonize
176
- return unless options[:daemon]
177
-
178
191
  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?
182
- end
183
192
 
193
+ files_to_reopen = ObjectSpace.each_object(File).reject { |f| f.closed? }
184
194
  ::Process.daemon(true, true)
185
195
 
186
196
  files_to_reopen.each do |file|
@@ -206,18 +216,50 @@ module Sidekiq
206
216
  @environment = cli_env || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
207
217
  end
208
218
 
219
+ def symbolize_keys_deep!(hash)
220
+ hash.keys.each do |k|
221
+ symkey = k.respond_to?(:to_sym) ? k.to_sym : k
222
+ hash[symkey] = hash.delete k
223
+ symbolize_keys_deep! hash[symkey] if hash[symkey].kind_of? Hash
224
+ end
225
+ end
226
+
209
227
  alias_method :die, :exit
210
228
  alias_method :☠, :exit
211
229
 
212
230
  def setup_options(args)
231
+ # parse CLI options
213
232
  opts = parse_options(args)
233
+
214
234
  set_environment opts[:environment]
215
235
 
216
- cfile = opts[:config_file]
217
- opts = parse_config(cfile).merge(opts) if cfile
236
+ # check config file presence
237
+ if opts[:config_file]
238
+ if opts[:config_file] && !File.exist?(opts[:config_file])
239
+ raise ArgumentError, "No such file #{opts[:config_file]}"
240
+ end
241
+ else
242
+ config_dir = if File.directory?(opts[:require].to_s)
243
+ File.join(opts[:require], 'config')
244
+ else
245
+ File.join(options[:require], 'config')
246
+ end
247
+
248
+ %w[sidekiq.yml sidekiq.yml.erb].each do |config_file|
249
+ path = File.join(config_dir, config_file)
250
+ opts[:config_file] ||= path if File.exist?(path)
251
+ end
252
+ end
218
253
 
254
+ # parse config file options
255
+ opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
256
+
257
+ # set defaults
258
+ opts[:queues] = Array(opts[:queues]) << 'default' if opts[:queues].nil? || opts[:queues].empty?
219
259
  opts[:strict] = true if opts[:strict].nil?
260
+ opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
220
261
 
262
+ # merge with defaults
221
263
  options.merge!(opts)
222
264
  end
223
265
 
@@ -228,22 +270,22 @@ module Sidekiq
228
270
  def boot_system
229
271
  ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment
230
272
 
231
- raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
232
-
233
273
  if File.directory?(options[:require])
234
274
  require 'rails'
235
275
  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
276
+ raise "Sidekiq no longer supports this version of Rails"
277
+ elsif ::Rails::VERSION::MAJOR == 4
240
278
  # Painful contortions, see 1791 for discussion
279
+ # No autoloading, we want to force eager load for everything.
241
280
  require File.expand_path("#{options[:require]}/config/application.rb")
242
281
  ::Rails::Application.initializer "sidekiq.eager_load" do
243
282
  ::Rails.application.config.eager_load = true
244
283
  end
245
284
  require 'sidekiq/rails'
246
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")
247
289
  end
248
290
  options[:tag] ||= default_tag
249
291
  else
@@ -263,12 +305,10 @@ module Sidekiq
263
305
  end
264
306
 
265
307
  def validate!
266
- options[:queues] << 'default' if options[:queues].empty?
267
-
268
308
  if !File.exist?(options[:require]) ||
269
309
  (File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
270
310
  logger.info "=================================================================="
271
- logger.info " Please point sidekiq to a Rails 3/4 application or a Ruby file "
311
+ logger.info " Please point sidekiq to a Rails 4/5 application or a Ruby file "
272
312
  logger.info " to load your worker classes with -r [DIR|FILE]."
273
313
  logger.info "=================================================================="
274
314
  logger.info @parser
@@ -290,6 +330,7 @@ module Sidekiq
290
330
 
291
331
  o.on '-d', '--daemon', "Daemonize process" do |arg|
292
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"
293
334
  end
294
335
 
295
336
  o.on '-e', '--environment ENV', "Application environment" do |arg|
@@ -300,6 +341,8 @@ module Sidekiq
300
341
  opts[:tag] = arg
301
342
  end
302
343
 
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.
303
346
  o.on '-i', '--index INT', "unique process index on this machine" do |arg|
304
347
  opts[:index] = Integer(arg.match(/\d+/)[0])
305
348
  end
@@ -327,10 +370,12 @@ module Sidekiq
327
370
 
328
371
  o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
329
372
  opts[:logfile] = arg
373
+ puts "WARNING: Logfile redirection will be removed in Sidekiq 6.0, see #4045. Sidekiq will only log to STDOUT"
330
374
  end
331
375
 
332
376
  o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
333
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"
334
379
  end
335
380
 
336
381
  o.on '-V', '--version', "Print version and exit" do |arg|
@@ -344,11 +389,8 @@ module Sidekiq
344
389
  logger.info @parser
345
390
  die 1
346
391
  end
347
- @parser.parse!(argv)
348
392
 
349
- %w[config/sidekiq.yml config/sidekiq.yml.erb].each do |filename|
350
- opts[:config_file] ||= filename if File.exist?(filename)
351
- end
393
+ @parser.parse!(argv)
352
394
 
353
395
  opts
354
396
  end
@@ -368,16 +410,18 @@ module Sidekiq
368
410
  end
369
411
  end
370
412
 
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) || [])
413
+ def parse_config(path)
414
+ opts = YAML.load(ERB.new(File.read(path)).result) || {}
415
+
416
+ if opts.respond_to? :deep_symbolize_keys!
417
+ opts.deep_symbolize_keys!
377
418
  else
378
- # allow a non-existent config file so Sidekiq
379
- # can be deployed by cap with just the defaults.
419
+ symbolize_keys_deep!(opts)
380
420
  end
421
+
422
+ opts = opts.merge(opts.delete(environment.to_sym) || {})
423
+ parse_queues(opts, opts.delete(:queues) || [])
424
+
381
425
  ns = opts.delete(:namespace)
382
426
  if ns
383
427
  # logger hasn't been initialized yet, puts is all we have.
@@ -391,10 +435,10 @@ module Sidekiq
391
435
  queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
392
436
  end
393
437
 
394
- def parse_queue(opts, q, weight=nil)
395
- [weight.to_i, 1].max.times do
396
- (opts[:queues] ||= []) << q
397
- end
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 }
398
442
  opts[:strict] = false if weight.to_i > 0
399
443
  end
400
444
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'securerandom'
2
3
  require 'sidekiq/middleware/chain'
3
4
 
@@ -35,10 +36,8 @@ module Sidekiq
35
36
  # Sidekiq::Client.new(ConnectionPool.new { Redis.new })
36
37
  #
37
38
  # Generally this is only needed for very large Sidekiq installs processing
38
- # more than thousands jobs per second. I do not recommend sharding unless
39
- # you truly cannot scale any other way (e.g. splitting your app into smaller apps).
40
- # Some features, like the API, do not support sharding: they are designed to work
41
- # against a single Redis instance only.
39
+ # thousands of jobs per second. I don't recommend sharding unless you
40
+ # cannot scale any other way (e.g. splitting your app into smaller apps).
42
41
  def initialize(redis_pool=nil)
43
42
  @redis_pool = redis_pool || Thread.current[:sidekiq_via_pool] || Sidekiq.redis_pool
44
43
  end
@@ -49,11 +48,18 @@ module Sidekiq
49
48
  # queue - the named queue to use, default 'default'
50
49
  # class - the worker class to call, required
51
50
  # args - an array of simple arguments to the perform method, must be JSON-serializable
52
- # retry - whether to retry this job if it fails, true or false, default true
51
+ # at - timestamp to schedule the job (optional), must be Numeric (e.g. Time.now.to_f)
52
+ # retry - whether to retry this job if it fails, default true or an integer number of retries
53
53
  # backtrace - whether to save any error backtrace, default false
54
54
  #
55
+ # If class is set to the class name, the jobs' options will be based on Sidekiq's default
56
+ # worker options. Otherwise, they will be based on the job class's options.
57
+ #
58
+ # Any options valid for a worker class's sidekiq_options are also available here.
59
+ #
55
60
  # All options must be strings, not symbols. NB: because we are serializing to JSON, all
56
- # symbols in 'args' will be converted to strings.
61
+ # symbols in 'args' will be converted to strings. Note that +backtrace: true+ can take quite a bit of
62
+ # space in Redis; a large volume of failing jobs can start Redis swapping if you aren't careful.
57
63
  #
58
64
  # Returns a unique Job ID. If middleware stops the job, nil will be returned instead.
59
65
  #
@@ -71,10 +77,10 @@ module Sidekiq
71
77
  end
72
78
 
73
79
  ##
74
- # Push a large number of jobs to Redis. In practice this method is only
75
- # useful if you are pushing tens of thousands of jobs or more, or if you need
76
- # to ensure that a batch doesn't complete prematurely. This method
77
- # basically cuts down on the redis round trip latency.
80
+ # Push a large number of jobs to Redis. This method cuts out the redis
81
+ # network round trip latency. I wouldn't recommend pushing more than
82
+ # 1000 per call but YMMV based on network quality, size of job args, etc.
83
+ # A large number of jobs can cause a bit of Redis command processing latency.
78
84
  #
79
85
  # Takes the same arguments as #push except that args is expected to be
80
86
  # an Array of Arrays. All other keys are duplicated for each job. Each job
@@ -84,10 +90,15 @@ module Sidekiq
84
90
  # Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less
85
91
  # than the number given if the middleware stopped processing for one or more jobs.
86
92
  def push_bulk(items)
93
+ arg = items['args'].first
94
+ return [] unless arg # no jobs to push
95
+ raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" if !arg.is_a?(Array)
96
+
87
97
  normed = normalize_item(items)
88
98
  payloads = items['args'].map do |args|
89
- raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" if !args.is_a?(Array)
90
- process_single(items['class'], normed.merge('args' => args, 'jid' => SecureRandom.hex(12), 'enqueued_at' => Time.now.to_f))
99
+ copy = normed.merge('args' => args, 'jid' => SecureRandom.hex(12), 'enqueued_at' => Time.now.to_f)
100
+ result = process_single(items['class'], copy)
101
+ result ? result : nil
91
102
  end.compact
92
103
 
93
104
  raw_push(payloads) if !payloads.empty?
@@ -104,26 +115,19 @@ module Sidekiq
104
115
  # end
105
116
  #
106
117
  # Generally this is only needed for very large Sidekiq installs processing
107
- # more than thousands jobs per second. I do not recommend sharding unless
108
- # you truly cannot scale any other way (e.g. splitting your app into smaller apps).
109
- # Some features, like the API, do not support sharding: they are designed to work
110
- # against a single Redis instance.
118
+ # thousands of jobs per second. I do not recommend sharding unless
119
+ # you cannot scale any other way (e.g. splitting your app into smaller apps).
111
120
  def self.via(pool)
112
121
  raise ArgumentError, "No pool given" if pool.nil?
113
- raise RuntimeError, "Sidekiq::Client.via is not re-entrant" if x = Thread.current[:sidekiq_via_pool] && x != pool
122
+ current_sidekiq_pool = Thread.current[:sidekiq_via_pool]
114
123
  Thread.current[:sidekiq_via_pool] = pool
115
124
  yield
116
125
  ensure
117
- Thread.current[:sidekiq_via_pool] = nil
126
+ Thread.current[:sidekiq_via_pool] = current_sidekiq_pool
118
127
  end
119
128
 
120
129
  class << self
121
130
 
122
- # deprecated
123
- def default
124
- @default ||= new
125
- end
126
-
127
131
  def push(item)
128
132
  new.push(item)
129
133
  end
@@ -160,7 +164,7 @@ module Sidekiq
160
164
  ts = (int < 1_000_000_000 ? now + int : int)
161
165
 
162
166
  item = { 'class' => klass, 'args' => args, 'at' => ts, 'queue' => queue }
163
- item.delete('at'.freeze) if ts <= now
167
+ item.delete('at') if ts <= now
164
168
 
165
169
  klass.client_push(item)
166
170
  end
@@ -186,18 +190,18 @@ module Sidekiq
186
190
 
187
191
  def atomic_push(conn, payloads)
188
192
  if payloads.first['at']
189
- conn.zadd('schedule'.freeze, payloads.map do |hash|
190
- at = hash.delete('at'.freeze).to_s
193
+ conn.zadd('schedule', payloads.map do |hash|
194
+ at = hash.delete('at').to_s
191
195
  [at, Sidekiq.dump_json(hash)]
192
196
  end)
193
197
  else
194
198
  q = payloads.first['queue']
195
199
  now = Time.now.to_f
196
200
  to_push = payloads.map do |entry|
197
- entry['enqueued_at'.freeze] = now
201
+ entry['enqueued_at'] = now
198
202
  Sidekiq.dump_json(entry)
199
203
  end
200
- conn.sadd('queues'.freeze, q)
204
+ conn.sadd('queues', q)
201
205
  conn.lpush("queue:#{q}", to_push)
202
206
  end
203
207
  end
@@ -211,23 +215,25 @@ module Sidekiq
211
215
  end
212
216
 
213
217
  def normalize_item(item)
214
- raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash) && item.has_key?('class'.freeze) && item.has_key?('args'.freeze)
218
+ raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash) && item.has_key?('class') && item.has_key?('args')
215
219
  raise(ArgumentError, "Job args must be an Array") unless item['args'].is_a?(Array)
216
- raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item['class'.freeze].is_a?(Class) || item['class'.freeze].is_a?(String)
220
+ raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item['class'].is_a?(Class) || item['class'].is_a?(String)
221
+ raise(ArgumentError, "Job 'at' must be a Numeric timestamp") if item.has_key?('at') && !item['at'].is_a?(Numeric)
222
+ #raise(ArgumentError, "Arguments must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices") unless JSON.load(JSON.dump(item['args'])) == item['args']
217
223
 
218
- normalized_hash(item['class'.freeze])
224
+ normalized_hash(item['class'])
219
225
  .each{ |key, value| item[key] = value if item[key].nil? }
220
226
 
221
- item['class'.freeze] = item['class'.freeze].to_s
222
- item['queue'.freeze] = item['queue'.freeze].to_s
223
- item['jid'.freeze] ||= SecureRandom.hex(12)
224
- item['created_at'.freeze] ||= Time.now.to_f
227
+ item['class'] = item['class'].to_s
228
+ item['queue'] = item['queue'].to_s
229
+ item['jid'] ||= SecureRandom.hex(12)
230
+ item['created_at'] ||= Time.now.to_f
225
231
  item
226
232
  end
227
233
 
228
234
  def normalized_hash(item_class)
229
235
  if item_class.is_a?(Class)
230
- raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") if !item_class.respond_to?('get_sidekiq_options'.freeze)
236
+ raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") if !item_class.respond_to?('get_sidekiq_options')
231
237
  item_class.get_sidekiq_options
232
238
  else
233
239
  Sidekiq.default_worker_options