sidekiq 6.0.7 → 6.2.0

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +70 -1
  3. data/README.md +2 -6
  4. data/bin/sidekiq +7 -2
  5. data/lib/sidekiq.rb +5 -3
  6. data/lib/sidekiq/api.rb +28 -10
  7. data/lib/sidekiq/cli.rb +23 -7
  8. data/lib/sidekiq/client.rb +16 -11
  9. data/lib/sidekiq/extensions/action_mailer.rb +3 -2
  10. data/lib/sidekiq/extensions/active_record.rb +4 -3
  11. data/lib/sidekiq/extensions/class_methods.rb +5 -4
  12. data/lib/sidekiq/fetch.rb +29 -21
  13. data/lib/sidekiq/job_retry.rb +1 -0
  14. data/lib/sidekiq/launcher.rb +55 -3
  15. data/lib/sidekiq/logger.rb +3 -2
  16. data/lib/sidekiq/manager.rb +4 -4
  17. data/lib/sidekiq/middleware/chain.rb +1 -1
  18. data/lib/sidekiq/processor.rb +4 -4
  19. data/lib/sidekiq/rails.rb +16 -18
  20. data/lib/sidekiq/redis_connection.rb +15 -12
  21. data/lib/sidekiq/sd_notify.rb +1 -1
  22. data/lib/sidekiq/testing.rb +1 -1
  23. data/lib/sidekiq/version.rb +1 -1
  24. data/lib/sidekiq/web.rb +35 -72
  25. data/lib/sidekiq/web/application.rb +9 -5
  26. data/lib/sidekiq/web/csrf_protection.rb +177 -0
  27. data/lib/sidekiq/web/helpers.rb +26 -8
  28. data/lib/sidekiq/web/router.rb +5 -2
  29. data/lib/sidekiq/worker.rb +2 -5
  30. data/sidekiq.gemspec +11 -4
  31. data/web/assets/images/apple-touch-icon.png +0 -0
  32. data/web/assets/javascripts/application.js +3 -8
  33. data/web/assets/stylesheets/application-dark.css +71 -33
  34. data/web/assets/stylesheets/application.css +24 -8
  35. data/web/locales/fr.yml +1 -1
  36. data/web/locales/pl.yml +4 -4
  37. data/web/locales/ru.yml +4 -0
  38. data/web/views/busy.erb +44 -14
  39. data/web/views/layout.erb +1 -0
  40. data/web/views/morgue.erb +1 -1
  41. data/web/views/queues.erb +1 -1
  42. data/web/views/retries.erb +1 -1
  43. data/web/views/scheduled.erb +1 -1
  44. metadata +17 -45
  45. data/.circleci/config.yml +0 -60
  46. data/.github/contributing.md +0 -32
  47. data/.github/issue_template.md +0 -11
  48. data/.gitignore +0 -13
  49. data/.standard.yml +0 -20
  50. data/3.0-Upgrade.md +0 -70
  51. data/4.0-Upgrade.md +0 -53
  52. data/5.0-Upgrade.md +0 -56
  53. data/6.0-Upgrade.md +0 -72
  54. data/COMM-LICENSE +0 -97
  55. data/Ent-2.0-Upgrade.md +0 -37
  56. data/Ent-Changes.md +0 -256
  57. data/Gemfile +0 -24
  58. data/Gemfile.lock +0 -208
  59. data/Pro-2.0-Upgrade.md +0 -138
  60. data/Pro-3.0-Upgrade.md +0 -44
  61. data/Pro-4.0-Upgrade.md +0 -35
  62. data/Pro-5.0-Upgrade.md +0 -25
  63. data/Pro-Changes.md +0 -782
  64. data/Rakefile +0 -10
  65. data/code_of_conduct.md +0 -50
@@ -61,6 +61,7 @@ module Sidekiq
61
61
  #
62
62
  class JobRetry
63
63
  class Handled < ::RuntimeError; end
64
+
64
65
  class Skip < Handled; end
65
66
 
66
67
  include Sidekiq::Util
@@ -22,6 +22,7 @@ module Sidekiq
22
22
  attr_accessor :manager, :poller, :fetcher
23
23
 
24
24
  def initialize(options)
25
+ options[:fetch] ||= BasicFetch.new(options)
25
26
  @manager = Sidekiq::Manager.new(options)
26
27
  @poller = Sidekiq::Scheduled::Poller.new
27
28
  @done = false
@@ -56,7 +57,7 @@ module Sidekiq
56
57
 
57
58
  # Requeue everything in case there was a worker who grabbed work while stopped
58
59
  # This call is a no-op in Sidekiq but necessary for Sidekiq Pro.
59
- strategy = (@options[:fetch] || Sidekiq::BasicFetch)
60
+ strategy = @options[:fetch]
60
61
  strategy.bulk_requeue([], @options)
61
62
 
62
63
  clear_heartbeat
@@ -152,13 +153,21 @@ module Sidekiq
152
153
  end
153
154
  end
154
155
 
156
+ rtt = check_rtt
157
+
155
158
  fails = procd = 0
159
+ kb = memory_usage(::Process.pid)
156
160
 
157
161
  _, exists, _, _, msg = Sidekiq.redis { |conn|
158
162
  conn.multi {
159
163
  conn.sadd("processes", key)
160
- conn.exists(key)
161
- conn.hmset(key, "info", to_json, "busy", curstate.size, "beat", Time.now.to_f, "quiet", @done)
164
+ conn.exists?(key)
165
+ conn.hmset(key, "info", to_json,
166
+ "busy", curstate.size,
167
+ "beat", Time.now.to_f,
168
+ "rtt_us", rtt,
169
+ "quiet", @done,
170
+ "rss", kb)
162
171
  conn.expire(key, 60)
163
172
  conn.rpop("#{key}-signals")
164
173
  }
@@ -179,6 +188,49 @@ module Sidekiq
179
188
  end
180
189
  end
181
190
 
191
+ RTT_WARNING_LEVEL = 50_000
192
+
193
+ def check_rtt
194
+ a = b = 0
195
+ Sidekiq.redis do |x|
196
+ a = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
197
+ x.ping
198
+ b = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
199
+ end
200
+ rtt = b - a
201
+ # Ideal RTT for Redis is < 1000µs
202
+ # Workable is < 10,000µs
203
+ # Log a warning if it's a disaster.
204
+ if rtt > RTT_WARNING_LEVEL
205
+ Sidekiq.logger.warn <<-EOM
206
+ Your Redis network connection is performing extremely poorly.
207
+ Current RTT is #{rtt} µs, ideally this should be < 1000.
208
+ Ensure Redis is running in the same AZ or datacenter as Sidekiq.
209
+ EOM
210
+ end
211
+ rtt
212
+ end
213
+
214
+ MEMORY_GRABBER = case RUBY_PLATFORM
215
+ when /linux/
216
+ ->(pid) {
217
+ IO.readlines("/proc/#{$$}/status").each do |line|
218
+ next unless line.start_with?("VmRSS:")
219
+ break line.split[1].to_i
220
+ end
221
+ }
222
+ when /darwin|bsd/
223
+ ->(pid) {
224
+ `ps -o pid,rss -p #{pid}`.lines.last.split.last.to_i
225
+ }
226
+ else
227
+ ->(pid) { 0 }
228
+ end
229
+
230
+ def memory_usage(pid)
231
+ MEMORY_GRABBER.call(pid)
232
+ end
233
+
182
234
  def to_data
183
235
  @data ||= begin
184
236
  {
@@ -6,10 +6,11 @@ require "time"
6
6
  module Sidekiq
7
7
  module Context
8
8
  def self.with(hash)
9
+ orig_context = current.dup
9
10
  current.merge!(hash)
10
11
  yield
11
12
  ensure
12
- hash.each_key { |key| current.delete(key) }
13
+ Thread.current[:sidekiq_context] = orig_context
13
14
  end
14
15
 
15
16
  def self.current
@@ -89,7 +90,7 @@ module Sidekiq
89
90
  return true if @logdev.nil? || severity < level
90
91
 
91
92
  if message.nil?
92
- if block_given?
93
+ if block
93
94
  message = yield
94
95
  else
95
96
  message = progname
@@ -35,7 +35,7 @@ module Sidekiq
35
35
  @done = false
36
36
  @workers = Set.new
37
37
  @count.times do
38
- @workers << Processor.new(self)
38
+ @workers << Processor.new(self, options)
39
39
  end
40
40
  @plock = Mutex.new
41
41
  end
@@ -56,7 +56,7 @@ module Sidekiq
56
56
  end
57
57
 
58
58
  # hack for quicker development / testing environment #2774
59
- PAUSE_TIME = STDOUT.tty? ? 0.1 : 0.5
59
+ PAUSE_TIME = $stdout.tty? ? 0.1 : 0.5
60
60
 
61
61
  def stop(deadline)
62
62
  quiet
@@ -90,7 +90,7 @@ module Sidekiq
90
90
  @plock.synchronize do
91
91
  @workers.delete(processor)
92
92
  unless @done
93
- p = Processor.new(self)
93
+ p = Processor.new(self, options)
94
94
  @workers << p
95
95
  p.start
96
96
  end
@@ -123,7 +123,7 @@ module Sidekiq
123
123
  # contract says that jobs are run AT LEAST once. Process termination
124
124
  # is delayed until we're certain the jobs are back in Redis because
125
125
  # it is worse to lose a job than to run it twice.
126
- strategy = (@options[:fetch] || Sidekiq::BasicFetch)
126
+ strategy = @options[:fetch]
127
127
  strategy.bulk_requeue(jobs, @options)
128
128
  end
129
129
 
@@ -133,7 +133,7 @@ module Sidekiq
133
133
  return yield if empty?
134
134
 
135
135
  chain = retrieve.dup
136
- traverse_chain = lambda do
136
+ traverse_chain = proc do
137
137
  if chain.empty?
138
138
  yield
139
139
  else
@@ -28,15 +28,15 @@ module Sidekiq
28
28
  attr_reader :thread
29
29
  attr_reader :job
30
30
 
31
- def initialize(mgr)
31
+ def initialize(mgr, options)
32
32
  @mgr = mgr
33
33
  @down = false
34
34
  @done = false
35
35
  @job = nil
36
36
  @thread = nil
37
- @strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options)
38
- @reloader = Sidekiq.options[:reloader]
39
- @job_logger = (mgr.options[:job_logger] || Sidekiq::JobLogger).new
37
+ @strategy = options[:fetch]
38
+ @reloader = options[:reloader] || proc { |&block| block.call }
39
+ @job_logger = (options[:job_logger] || Sidekiq::JobLogger).new
40
40
  @retrier = Sidekiq::JobRetry.new
41
41
  end
42
42
 
data/lib/sidekiq/rails.rb CHANGED
@@ -4,6 +4,22 @@ require "sidekiq/worker"
4
4
 
5
5
  module Sidekiq
6
6
  class Rails < ::Rails::Engine
7
+ class Reloader
8
+ def initialize(app = ::Rails.application)
9
+ @app = app
10
+ end
11
+
12
+ def call
13
+ @app.reloader.wrap do
14
+ yield
15
+ end
16
+ end
17
+
18
+ def inspect
19
+ "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
20
+ end
21
+ end
22
+
7
23
  # By including the Options module, we allow AJs to directly control sidekiq features
8
24
  # via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
9
25
  # AJ retries don't show up in the Sidekiq UI Retries tab, save any error data, can't be
@@ -23,8 +39,6 @@ module Sidekiq
23
39
 
24
40
  # This hook happens after all initializers are run, just before returning
25
41
  # from config/environment.rb back to sidekiq/cli.rb.
26
- # We have to add the reloader after initialize to see if cache_classes has
27
- # been turned on.
28
42
  #
29
43
  # None of this matters on the client-side, only within the Sidekiq process itself.
30
44
  config.after_initialize do
@@ -32,21 +46,5 @@ module Sidekiq
32
46
  Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
33
47
  end
34
48
  end
35
-
36
- class Reloader
37
- def initialize(app = ::Rails.application)
38
- @app = app
39
- end
40
-
41
- def call
42
- @app.reloader.wrap do
43
- yield
44
- end
45
- end
46
-
47
- def inspect
48
- "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
49
- end
50
- end
51
49
  end
52
50
  end
@@ -8,16 +8,14 @@ module Sidekiq
8
8
  class RedisConnection
9
9
  class << self
10
10
  def create(options = {})
11
- options.keys.each do |key|
12
- options[key.to_sym] = options.delete(key)
13
- end
11
+ symbolized_options = options.transform_keys(&:to_sym)
14
12
 
15
- if !options[:url] && (u = determine_redis_provider)
16
- options[:url] = u
13
+ if !symbolized_options[:url] && (u = determine_redis_provider)
14
+ symbolized_options[:url] = u
17
15
  end
18
16
 
19
- size = if options[:size]
20
- options[:size]
17
+ size = if symbolized_options[:size]
18
+ symbolized_options[:size]
21
19
  elsif Sidekiq.server?
22
20
  # Give ourselves plenty of connections. pool is lazy
23
21
  # so we won't create them until we need them.
@@ -30,11 +28,11 @@ module Sidekiq
30
28
 
31
29
  verify_sizing(size, Sidekiq.options[:concurrency]) if Sidekiq.server?
32
30
 
33
- pool_timeout = options[:pool_timeout] || 1
34
- log_info(options)
31
+ pool_timeout = symbolized_options[:pool_timeout] || 1
32
+ log_info(symbolized_options)
35
33
 
36
34
  ConnectionPool.new(timeout: pool_timeout, size: size) do
37
- build_client(options)
35
+ build_client(symbolized_options)
38
36
  end
39
37
  end
40
38
 
@@ -97,7 +95,12 @@ module Sidekiq
97
95
  redacted = "REDACTED"
98
96
 
99
97
  # deep clone so we can muck with these options all we want
100
- scrubbed_options = Marshal.load(Marshal.dump(options))
98
+ #
99
+ # exclude SSL params from dump-and-load because some information isn't
100
+ # safely dumpable in current Rubies
101
+ keys = options.keys
102
+ keys.delete(:ssl_params)
103
+ scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
101
104
  if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
102
105
  uri.password = redacted
103
106
  scrubbed_options[:url] = uri.to_s
@@ -124,7 +127,7 @@ module Sidekiq
124
127
  # initialization code at all.
125
128
  #
126
129
  p = ENV["REDIS_PROVIDER"]
127
- if p && p =~ /\:/
130
+ if p && p =~ /:/
128
131
  raise <<~EOM
129
132
  REDIS_PROVIDER should be set to the name of the variable which contains the Redis URL, not a URL itself.
130
133
  Platforms like Heroku will sell addons that publish a *_URL variable. You need to tell Sidekiq with REDIS_PROVIDER, e.g.:
@@ -85,7 +85,7 @@ module Sidekiq
85
85
  notify(FDSTORE, unset_env)
86
86
  end
87
87
 
88
- # @param [Boolean] true if the service manager expects watchdog keep-alive
88
+ # @return [Boolean] true if the service manager expects watchdog keep-alive
89
89
  # notification messages to be sent from this process.
90
90
  #
91
91
  # If the $WATCHDOG_USEC environment variable is set,
@@ -337,7 +337,7 @@ module Sidekiq
337
337
  Sidekiq::Extensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedModel)
338
338
  end
339
339
 
340
- if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test?
340
+ if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
341
341
  puts("**************************************************")
342
342
  puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
343
343
  puts("**************************************************")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "6.0.7"
4
+ VERSION = "6.2.0"
5
5
  end
data/lib/sidekiq/web.rb CHANGED
@@ -10,12 +10,11 @@ require "sidekiq/web/helpers"
10
10
  require "sidekiq/web/router"
11
11
  require "sidekiq/web/action"
12
12
  require "sidekiq/web/application"
13
+ require "sidekiq/web/csrf_protection"
13
14
 
14
- require "rack/protection"
15
-
15
+ require "rack/content_length"
16
16
  require "rack/builder"
17
- require "rack/file"
18
- require "rack/session/cookie"
17
+ require "rack/static"
19
18
 
20
19
  module Sidekiq
21
20
  class Web
@@ -39,14 +38,6 @@ module Sidekiq
39
38
  self
40
39
  end
41
40
 
42
- def middlewares
43
- @middlewares ||= []
44
- end
45
-
46
- def use(*middleware_args, &block)
47
- middlewares << [middleware_args, block]
48
- end
49
-
50
41
  def default_tabs
51
42
  DEFAULT_TABS
52
43
  end
@@ -72,32 +63,45 @@ module Sidekiq
72
63
  opts.each { |key| set(key, false) }
73
64
  end
74
65
 
75
- # Helper for the Sinatra syntax: Sidekiq::Web.set(:session_secret, Rails.application.secrets...)
66
+ def middlewares
67
+ @middlewares ||= []
68
+ end
69
+
70
+ def use(*args, &block)
71
+ middlewares << [args, block]
72
+ end
73
+
76
74
  def set(attribute, value)
77
75
  send(:"#{attribute}=", value)
78
76
  end
79
77
 
80
- attr_accessor :app_url, :session_secret, :redis_pool, :sessions
78
+ def sessions=(val)
79
+ puts "WARNING: Sidekiq::Web.sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
80
+ end
81
+
82
+ def session_secret=(val)
83
+ puts "WARNING: Sidekiq::Web.session_secret= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
84
+ end
85
+
86
+ attr_accessor :app_url, :redis_pool
81
87
  attr_writer :locales, :views
82
88
  end
83
89
 
84
90
  def self.inherited(child)
85
91
  child.app_url = app_url
86
- child.session_secret = session_secret
87
92
  child.redis_pool = redis_pool
88
- child.sessions = sessions
89
93
  end
90
94
 
91
95
  def settings
92
96
  self.class.settings
93
97
  end
94
98
 
95
- def use(*middleware_args, &block)
96
- middlewares << [middleware_args, block]
99
+ def middlewares
100
+ @middlewares ||= self.class.middlewares
97
101
  end
98
102
 
99
- def middlewares
100
- @middlewares ||= Web.middlewares.dup
103
+ def use(*args, &block)
104
+ middlewares << [args, block]
101
105
  end
102
106
 
103
107
  def call(env)
@@ -125,18 +129,8 @@ module Sidekiq
125
129
  send(:"#{attribute}=", value)
126
130
  end
127
131
 
128
- # Default values
129
- set :sessions, true
130
-
131
- attr_writer :sessions
132
-
133
- def sessions
134
- unless instance_variable_defined?("@sessions")
135
- @sessions = self.class.sessions
136
- @sessions = @sessions.to_hash.dup if @sessions.respond_to?(:to_hash)
137
- end
138
-
139
- @sessions
132
+ def sessions=(val)
133
+ puts "Sidekiq::Web#sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller[2..2].first}"
140
134
  end
141
135
 
142
136
  def self.register(extension)
@@ -145,50 +139,19 @@ module Sidekiq
145
139
 
146
140
  private
147
141
 
148
- def using?(middleware)
149
- middlewares.any? do |(m, _)|
150
- m.is_a?(Array) && (m[0] == middleware || m[0].is_a?(middleware))
151
- end
152
- end
153
-
154
- def build_sessions
155
- middlewares = self.middlewares
156
-
157
- unless using?(::Rack::Protection) || ENV["RACK_ENV"] == "test"
158
- middlewares.unshift [[::Rack::Protection, {use: :authenticity_token}], nil]
159
- end
160
-
161
- s = sessions
162
- return unless s
163
-
164
- unless using? ::Rack::Session::Cookie
165
- unless (secret = Web.session_secret)
166
- require "securerandom"
167
- secret = SecureRandom.hex(64)
168
- end
169
-
170
- options = {secret: secret}
171
- options = options.merge(s.to_hash) if s.respond_to? :to_hash
172
-
173
- middlewares.unshift [[::Rack::Session::Cookie, options], nil]
174
- end
175
- end
176
-
177
142
  def build
178
- build_sessions
179
-
180
- middlewares = self.middlewares
181
143
  klass = self.class
144
+ m = middlewares
182
145
 
183
146
  ::Rack::Builder.new do
184
- %w[stylesheets javascripts images].each do |asset_dir|
185
- map "/#{asset_dir}" do
186
- run ::Rack::File.new("#{ASSETS}/#{asset_dir}", {"Cache-Control" => "public, max-age=86400"})
187
- end
188
- end
189
-
190
- middlewares.each { |middleware, block| use(*middleware, &block) }
191
-
147
+ use Rack::Static, :urls => ["/stylesheets", "/images", "/javascripts"],
148
+ :root => ASSETS,
149
+ :cascade => true,
150
+ :header_rules => [
151
+ [:all, {'Cache-Control' => 'public, max-age=86400'}],
152
+ ]
153
+ m.each { |middleware, block| use(*middleware, &block) }
154
+ use Sidekiq::Web::CsrfProtection unless $TESTING
192
155
  run WebApplication.new(klass)
193
156
  end
194
157
  end