sidekiq 6.1.2 → 6.2.1

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +49 -1
  3. data/lib/sidekiq/api.rb +22 -7
  4. data/lib/sidekiq/cli.rb +14 -1
  5. data/lib/sidekiq/client.rb +1 -5
  6. data/lib/sidekiq/fetch.rb +9 -1
  7. data/lib/sidekiq/job_retry.rb +1 -0
  8. data/lib/sidekiq/launcher.rb +58 -1
  9. data/lib/sidekiq/logger.rb +3 -2
  10. data/lib/sidekiq/util.rb +28 -0
  11. data/lib/sidekiq/version.rb +1 -1
  12. data/lib/sidekiq/web.rb +33 -78
  13. data/lib/sidekiq/web/action.rb +1 -1
  14. data/lib/sidekiq/web/application.rb +11 -3
  15. data/lib/sidekiq/web/csrf_protection.rb +28 -6
  16. data/lib/sidekiq/web/helpers.rb +23 -2
  17. data/lib/sidekiq/web/router.rb +4 -1
  18. data/sidekiq.gemspec +10 -2
  19. data/web/assets/images/apple-touch-icon.png +0 -0
  20. data/web/assets/stylesheets/application-dark.css +17 -0
  21. data/web/assets/stylesheets/application.css +17 -2
  22. data/web/locales/fr.yml +1 -1
  23. data/web/views/busy.erb +45 -14
  24. data/web/views/layout.erb +1 -0
  25. data/web/views/queue.erb +1 -1
  26. metadata +11 -26
  27. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
  28. data/.github/contributing.md +0 -32
  29. data/.github/workflows/ci.yml +0 -41
  30. data/.gitignore +0 -13
  31. data/.standard.yml +0 -20
  32. data/3.0-Upgrade.md +0 -70
  33. data/4.0-Upgrade.md +0 -53
  34. data/5.0-Upgrade.md +0 -56
  35. data/6.0-Upgrade.md +0 -72
  36. data/COMM-LICENSE +0 -97
  37. data/Ent-2.0-Upgrade.md +0 -37
  38. data/Ent-Changes.md +0 -281
  39. data/Gemfile +0 -24
  40. data/Gemfile.lock +0 -192
  41. data/Pro-2.0-Upgrade.md +0 -138
  42. data/Pro-3.0-Upgrade.md +0 -44
  43. data/Pro-4.0-Upgrade.md +0 -35
  44. data/Pro-5.0-Upgrade.md +0 -25
  45. data/Pro-Changes.md +0 -805
  46. data/Rakefile +0 -10
  47. data/code_of_conduct.md +0 -50
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a80310735fb458e7332a5ecc86072248e0777e1951f77f14965c06313c5b2f8
4
- data.tar.gz: bfd09dc57c4506ca9c4d74dd9a38e88ef18700aafaf92814d662c6df4e4b2e5b
3
+ metadata.gz: 298711914bcb8534a9599c47b00b7410467ce324619ee70e7050d15c42f4c329
4
+ data.tar.gz: '007900de7a1558633520c61870a58eff341e9c11009441dbabe0fbc177e4ed99'
5
5
  SHA512:
6
- metadata.gz: c0e416fe575874c2051e88c64a6cbb30ad2ba4929ca33487ca1323b1963101d3d08ed43b8edbe874bccfa677521b0745baf589b451c7c56ff41b7bc70ee4f88d
7
- data.tar.gz: 4d1cd6d03768f9844bd8edd9bdb545a743084322f4edc01682e04b0990b0fb6aa3f1c6ad4f9a87c1b810a07e03b08eae0405ad2291b42d58eebb257bed7a60f4
6
+ metadata.gz: 592ecc114de13f0e43bba9193e1ffd3a973c89a43fac3ed1b750b6a70e29b5bf128a05657baf3fc2ccb77134f092efac055651907f01c0ed6d3c00d45a5ebdc9
7
+ data.tar.gz: a7baed1f1df451e8bd5183fec4631e49c0761e700c4c95fc070389894894a5fb90103d0adce29da797bc1cae72f8a1f21e71da0279e8c18cb62e3b3b5ae05f0a
data/Changes.md CHANGED
@@ -2,6 +2,54 @@
2
2
 
3
3
  [Sidekiq Changes](https://github.com/mperham/sidekiq/blob/master/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/master/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/master/Ent-Changes.md)
4
4
 
5
+ 6.2.1
6
+ ---------
7
+
8
+ - Update RTT warning logic to handle transient RTT spikes [#4851]
9
+ - Fix very low priority CVE on unescaped queue name [#4852]
10
+ - Add note about sessions and Rails apps in API mode
11
+
12
+ 6.2.0
13
+ ---------
14
+
15
+ - Store Redis RTT and log if poor [#4824]
16
+ - Add process/thread stats to Busy page [#4806]
17
+ - Improve Web UI on mobile devices [#4840]
18
+ - **Refactor Web UI session usage** [#4804]
19
+ Numerous people have hit "Forbidden" errors and struggled with Sidekiq's
20
+ Web UI session requirement. If you have code in your initializer for
21
+ Web sessions, it's quite possible it will need to be removed. Here's
22
+ an overview:
23
+ ```
24
+ Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app,
25
+ make sure you mount Sidekiq::Web *inside* your routes in `config/routes.rb` so
26
+ Sidekiq can reuse the Rails session:
27
+
28
+ Rails.application.routes.draw do
29
+ mount Sidekiq::Web => "/sidekiq"
30
+ ....
31
+ end
32
+
33
+ If this is a bare Rack app, use a session middleware before Sidekiq::Web:
34
+
35
+ # first, use IRB to create a shared secret key for sessions and commit it
36
+ require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) }
37
+
38
+ # now, update your Rack app to include the secret with a session cookie middleware
39
+ use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
40
+ run Sidekiq::Web
41
+
42
+ If this is a Rails app in API mode, you need to enable sessions.
43
+
44
+ https://guides.rubyonrails.org/api_app.html#using-session-middlewares
45
+ ```
46
+
47
+ 6.1.3
48
+ ---------
49
+
50
+ - Warn if Redis is configured to evict data under memory pressure [#4752]
51
+ - Add process RSS on the Busy page [#4717]
52
+
5
53
  6.1.2
6
54
  ---------
7
55
 
@@ -150,7 +198,7 @@ assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs_for(FooMailer).size
150
198
 
151
199
  This release has major breaking changes. Read and test carefully in production.
152
200
 
153
- - With Rails 6.0.1+, ActiveJobs can now use `sidekiq_options` directly to configure Sidekiq
201
+ - With Rails 6.0.2+, ActiveJobs can now use `sidekiq_options` directly to configure Sidekiq
154
202
  features/internals like the retry subsystem. [#4213, pirj]
155
203
  ```ruby
156
204
  class MyJob < ActiveJob::Base
data/lib/sidekiq/api.rb CHANGED
@@ -85,10 +85,10 @@ module Sidekiq
85
85
 
86
86
  default_queue_latency = if (entry = pipe1_res[6].first)
87
87
  job = begin
88
- Sidekiq.load_json(entry)
89
- rescue
90
- {}
91
- end
88
+ Sidekiq.load_json(entry)
89
+ rescue
90
+ {}
91
+ end
92
92
  now = Time.now.to_f
93
93
  thence = job["enqueued_at"] || now
94
94
  now - thence
@@ -791,19 +791,23 @@ module Sidekiq
791
791
  # you'll be happier this way
792
792
  conn.pipelined do
793
793
  procs.each do |key|
794
- conn.hmget(key, "info", "busy", "beat", "quiet")
794
+ conn.hmget(key, "info", "busy", "beat", "quiet", "rss", "rtt_us")
795
795
  end
796
796
  end
797
797
  }
798
798
 
799
- result.each do |info, busy, at_s, quiet|
799
+ result.each do |info, busy, at_s, quiet, rss, rtt|
800
800
  # If a process is stopped between when we query Redis for `procs` and
801
801
  # when we query for `result`, we will have an item in `result` that is
802
802
  # composed of `nil` values.
803
803
  next if info.nil?
804
804
 
805
805
  hash = Sidekiq.load_json(info)
806
- yield Process.new(hash.merge("busy" => busy.to_i, "beat" => at_s.to_f, "quiet" => quiet))
806
+ yield Process.new(hash.merge("busy" => busy.to_i,
807
+ "beat" => at_s.to_f,
808
+ "quiet" => quiet,
809
+ "rss" => rss.to_i,
810
+ "rtt_us" => rtt.to_i))
807
811
  end
808
812
  end
809
813
 
@@ -815,6 +819,17 @@ module Sidekiq
815
819
  Sidekiq.redis { |conn| conn.scard("processes") }
816
820
  end
817
821
 
822
+ # Total number of threads available to execute jobs.
823
+ # For Sidekiq Enterprise customers this number (in production) must be
824
+ # less than or equal to your licensed concurrency.
825
+ def total_concurrency
826
+ sum { |x| x["concurrency"] }
827
+ end
828
+
829
+ def total_rss
830
+ sum { |x| x["rss"] || 0 }
831
+ end
832
+
818
833
  # Returns the identity of the current cluster leader or "" if no leader.
819
834
  # This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
820
835
  # or Sidekiq Pro.
data/lib/sidekiq/cli.rb CHANGED
@@ -59,9 +59,22 @@ module Sidekiq
59
59
 
60
60
  # touch the connection pool so it is created before we
61
61
  # fire startup and start multithreading.
62
- ver = Sidekiq.redis_info["redis_version"]
62
+ info = Sidekiq.redis_info
63
+ ver = info["redis_version"]
63
64
  raise "You are connecting to Redis v#{ver}, Sidekiq requires Redis v4.0.0 or greater" if ver < "4"
64
65
 
66
+ maxmemory_policy = info["maxmemory_policy"]
67
+ if maxmemory_policy != "noeviction"
68
+ logger.warn <<~EOM
69
+
70
+
71
+ WARNING: Your Redis instance will evict Sidekiq data under heavy load.
72
+ The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
73
+ See: https://github.com/mperham/sidekiq/wiki/Using-Redis#memory
74
+
75
+ EOM
76
+ end
77
+
65
78
  # Since the user can pass us a connection pool explicitly in the initializer, we
66
79
  # need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
67
80
  cursize = Sidekiq.redis_pool.size
@@ -19,7 +19,7 @@ module Sidekiq
19
19
  #
20
20
  def middleware(&block)
21
21
  @chain ||= Sidekiq.client_middleware
22
- if block_given?
22
+ if block
23
23
  @chain = @chain.dup
24
24
  yield @chain
25
25
  end
@@ -228,10 +228,6 @@ module Sidekiq
228
228
  end
229
229
 
230
230
  def normalize_item(item)
231
- # 6.0.0 push_bulk bug, #4321
232
- # TODO Remove after a while...
233
- item.delete("at") if item.key?("at") && item["at"].nil?
234
-
235
231
  validate(item)
236
232
  # 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']
237
233
 
data/lib/sidekiq/fetch.rb CHANGED
@@ -36,7 +36,15 @@ module Sidekiq
36
36
  end
37
37
 
38
38
  def retrieve_work
39
- work = Sidekiq.redis { |conn| conn.brpop(*queues_cmd) }
39
+ qs = queues_cmd
40
+ # 4825 Sidekiq Pro with all queues paused will return an
41
+ # empty set of queues with a trailing TIMEOUT value.
42
+ if qs.size <= 1
43
+ sleep(2)
44
+ return nil
45
+ end
46
+
47
+ work = Sidekiq.redis { |conn| conn.brpop(*qs) }
40
48
  UnitOfWork.new(*work) if work
41
49
  end
42
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
@@ -153,13 +153,21 @@ module Sidekiq
153
153
  end
154
154
  end
155
155
 
156
+ rtt = check_rtt
157
+
156
158
  fails = procd = 0
159
+ kb = memory_usage(::Process.pid)
157
160
 
158
161
  _, exists, _, _, msg = Sidekiq.redis { |conn|
159
162
  conn.multi {
160
163
  conn.sadd("processes", key)
161
164
  conn.exists?(key)
162
- conn.hmset(key, "info", to_json, "busy", curstate.size, "beat", Time.now.to_f, "quiet", @done)
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)
163
171
  conn.expire(key, 60)
164
172
  conn.rpop("#{key}-signals")
165
173
  }
@@ -180,6 +188,55 @@ module Sidekiq
180
188
  end
181
189
  end
182
190
 
191
+ # We run the heartbeat every five seconds.
192
+ # Capture five samples of RTT, log a warning if each sample
193
+ # is above our warning threshold.
194
+ RTT_READINGS = RingBuffer.new(5)
195
+ RTT_WARNING_LEVEL = 50_000
196
+
197
+ def check_rtt
198
+ a = b = 0
199
+ Sidekiq.redis do |x|
200
+ a = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
201
+ x.ping
202
+ b = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
203
+ end
204
+ rtt = b - a
205
+ RTT_READINGS << rtt
206
+ # Ideal RTT for Redis is < 1000µs
207
+ # Workable is < 10,000µs
208
+ # Log a warning if it's a disaster.
209
+ if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
210
+ Sidekiq.logger.warn <<~EOM
211
+ Your Redis network connection is performing extremely poorly.
212
+ Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
213
+ Ensure Redis is running in the same AZ or datacenter as Sidekiq.
214
+ EOM
215
+ RTT_READINGS.reset
216
+ end
217
+ rtt
218
+ end
219
+
220
+ MEMORY_GRABBER = case RUBY_PLATFORM
221
+ when /linux/
222
+ ->(pid) {
223
+ IO.readlines("/proc/#{$$}/status").each do |line|
224
+ next unless line.start_with?("VmRSS:")
225
+ break line.split[1].to_i
226
+ end
227
+ }
228
+ when /darwin|bsd/
229
+ ->(pid) {
230
+ `ps -o pid,rss -p #{pid}`.lines.last.split.last.to_i
231
+ }
232
+ else
233
+ ->(pid) { 0 }
234
+ end
235
+
236
+ def memory_usage(pid)
237
+ MEMORY_GRABBER.call(pid)
238
+ end
239
+
183
240
  def to_data
184
241
  @data ||= begin
185
242
  {
@@ -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
data/lib/sidekiq/util.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
3
4
  require "socket"
4
5
  require "securerandom"
5
6
  require "sidekiq/exception_handler"
@@ -8,6 +9,33 @@ module Sidekiq
8
9
  ##
9
10
  # This module is part of Sidekiq core and not intended for extensions.
10
11
  #
12
+
13
+ class RingBuffer
14
+ include Enumerable
15
+ extend Forwardable
16
+ def_delegators :@buf, :[], :each, :size
17
+
18
+ def initialize(size, default = 0)
19
+ @size = size
20
+ @buf = Array.new(size, default)
21
+ @index = 0
22
+ end
23
+
24
+ def <<(element)
25
+ @buf[@index % @size] = element
26
+ @index += 1
27
+ element
28
+ end
29
+
30
+ def buffer
31
+ @buf
32
+ end
33
+
34
+ def reset(default = 0)
35
+ @buf.fill(default)
36
+ end
37
+ end
38
+
11
39
  module Util
12
40
  include ExceptionHandler
13
41
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "6.1.2"
4
+ VERSION = "6.2.1"
5
5
  end
data/lib/sidekiq/web.rb CHANGED
@@ -13,10 +13,8 @@ require "sidekiq/web/application"
13
13
  require "sidekiq/web/csrf_protection"
14
14
 
15
15
  require "rack/content_length"
16
-
17
16
  require "rack/builder"
18
- require "rack/file"
19
- require "rack/session/cookie"
17
+ require "rack/static"
20
18
 
21
19
  module Sidekiq
22
20
  class Web
@@ -40,14 +38,6 @@ module Sidekiq
40
38
  self
41
39
  end
42
40
 
43
- def middlewares
44
- @middlewares ||= []
45
- end
46
-
47
- def use(*middleware_args, &block)
48
- middlewares << [middleware_args, block]
49
- end
50
-
51
41
  def default_tabs
52
42
  DEFAULT_TABS
53
43
  end
@@ -73,32 +63,45 @@ module Sidekiq
73
63
  opts.each { |key| set(key, false) }
74
64
  end
75
65
 
76
- # 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
+
77
74
  def set(attribute, value)
78
75
  send(:"#{attribute}=", value)
79
76
  end
80
77
 
81
- 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
82
87
  attr_writer :locales, :views
83
88
  end
84
89
 
85
90
  def self.inherited(child)
86
91
  child.app_url = app_url
87
- child.session_secret = session_secret
88
92
  child.redis_pool = redis_pool
89
- child.sessions = sessions
90
93
  end
91
94
 
92
95
  def settings
93
96
  self.class.settings
94
97
  end
95
98
 
96
- def use(*middleware_args, &block)
97
- middlewares << [middleware_args, block]
99
+ def middlewares
100
+ @middlewares ||= self.class.middlewares
98
101
  end
99
102
 
100
- def middlewares
101
- @middlewares ||= Web.middlewares.dup
103
+ def use(*args, &block)
104
+ middlewares << [args, block]
102
105
  end
103
106
 
104
107
  def call(env)
@@ -126,18 +129,8 @@ module Sidekiq
126
129
  send(:"#{attribute}=", value)
127
130
  end
128
131
 
129
- # Default values
130
- set :sessions, true
131
-
132
- attr_writer :sessions
133
-
134
- def sessions
135
- unless instance_variable_defined?("@sessions")
136
- @sessions = self.class.sessions
137
- @sessions = @sessions.to_hash.dup if @sessions.respond_to?(:to_hash)
138
- end
139
-
140
- @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}"
141
134
  end
142
135
 
143
136
  def self.register(extension)
@@ -146,57 +139,19 @@ module Sidekiq
146
139
 
147
140
  private
148
141
 
149
- def using?(middleware)
150
- middlewares.any? do |(m, _)|
151
- m.is_a?(Array) && (m[0] == middleware || m[0].is_a?(middleware))
152
- end
153
- end
154
-
155
- def build_sessions
156
- middlewares = self.middlewares
157
-
158
- s = sessions
159
-
160
- # turn on CSRF protection if sessions are enabled and this is not the test env
161
- if s && !using?(CsrfProtection) && ENV["RACK_ENV"] != "test"
162
- middlewares.unshift [[CsrfProtection], nil]
163
- end
164
-
165
- if s && !using?(::Rack::Session::Cookie)
166
- unless (secret = Web.session_secret)
167
- require "securerandom"
168
- secret = SecureRandom.hex(64)
169
- end
170
-
171
- options = {secret: secret}
172
- options = options.merge(s.to_hash) if s.respond_to? :to_hash
173
-
174
- middlewares.unshift [[::Rack::Session::Cookie, options], nil]
175
- end
176
-
177
- # Since Sidekiq::WebApplication no longer calculates its own
178
- # Content-Length response header, we must ensure that the Rack middleware
179
- # that does this is loaded
180
- unless using? ::Rack::ContentLength
181
- middlewares.unshift [[::Rack::ContentLength], nil]
182
- end
183
- end
184
-
185
142
  def build
186
- build_sessions
187
-
188
- middlewares = self.middlewares
189
143
  klass = self.class
144
+ m = middlewares
190
145
 
191
146
  ::Rack::Builder.new do
192
- %w[stylesheets javascripts images].each do |asset_dir|
193
- map "/#{asset_dir}" do
194
- run ::Rack::File.new("#{ASSETS}/#{asset_dir}", {"Cache-Control" => "public, max-age=86400"})
195
- end
196
- end
197
-
198
- middlewares.each { |middleware, block| use(*middleware, &block) }
199
-
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
200
155
  run WebApplication.new(klass)
201
156
  end
202
157
  end