sidekiq 6.1.0 → 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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +64 -2
  3. data/README.md +1 -2
  4. data/lib/sidekiq.rb +1 -1
  5. data/lib/sidekiq/api.rb +27 -9
  6. data/lib/sidekiq/cli.rb +20 -12
  7. data/lib/sidekiq/client.rb +1 -5
  8. data/lib/sidekiq/extensions/action_mailer.rb +3 -2
  9. data/lib/sidekiq/extensions/active_record.rb +1 -1
  10. data/lib/sidekiq/fetch.rb +9 -3
  11. data/lib/sidekiq/job_retry.rb +1 -0
  12. data/lib/sidekiq/launcher.rb +58 -1
  13. data/lib/sidekiq/logger.rb +3 -2
  14. data/lib/sidekiq/manager.rb +1 -1
  15. data/lib/sidekiq/middleware/chain.rb +1 -1
  16. data/lib/sidekiq/util.rb +28 -0
  17. data/lib/sidekiq/version.rb +1 -1
  18. data/lib/sidekiq/web.rb +33 -78
  19. data/lib/sidekiq/web/action.rb +1 -1
  20. data/lib/sidekiq/web/application.rb +12 -6
  21. data/lib/sidekiq/web/csrf_protection.rb +30 -3
  22. data/lib/sidekiq/web/helpers.rb +23 -2
  23. data/lib/sidekiq/web/router.rb +4 -1
  24. data/sidekiq.gemspec +10 -2
  25. data/web/assets/images/apple-touch-icon.png +0 -0
  26. data/web/assets/javascripts/application.js +1 -6
  27. data/web/assets/stylesheets/application-dark.css +59 -32
  28. data/web/assets/stylesheets/application.css +19 -8
  29. data/web/locales/fr.yml +1 -1
  30. data/web/locales/ru.yml +4 -0
  31. data/web/views/busy.erb +47 -16
  32. data/web/views/layout.erb +1 -0
  33. data/web/views/morgue.erb +1 -1
  34. data/web/views/queue.erb +1 -1
  35. data/web/views/queues.erb +1 -1
  36. data/web/views/retries.erb +1 -1
  37. data/web/views/scheduled.erb +1 -1
  38. metadata +14 -29
  39. data/.circleci/config.yml +0 -71
  40. data/.github/contributing.md +0 -32
  41. data/.github/issue_template.md +0 -11
  42. data/.gitignore +0 -13
  43. data/.standard.yml +0 -20
  44. data/3.0-Upgrade.md +0 -70
  45. data/4.0-Upgrade.md +0 -53
  46. data/5.0-Upgrade.md +0 -56
  47. data/6.0-Upgrade.md +0 -72
  48. data/COMM-LICENSE +0 -97
  49. data/Ent-2.0-Upgrade.md +0 -37
  50. data/Ent-Changes.md +0 -269
  51. data/Gemfile +0 -24
  52. data/Gemfile.lock +0 -208
  53. data/Pro-2.0-Upgrade.md +0 -138
  54. data/Pro-3.0-Upgrade.md +0 -44
  55. data/Pro-4.0-Upgrade.md +0 -35
  56. data/Pro-5.0-Upgrade.md +0 -25
  57. data/Pro-Changes.md +0 -790
  58. data/Rakefile +0 -10
  59. data/code_of_conduct.md +0 -50
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e22aedd56d4f0b3a30fc73ceec6d246813b1b7e2f233dc2e19fe435c1689abd8
4
- data.tar.gz: 8beb1e49cc43aa6f179f72cbd191832151facb48f01d67b4b6c979aa63cbbf1a
3
+ metadata.gz: 298711914bcb8534a9599c47b00b7410467ce324619ee70e7050d15c42f4c329
4
+ data.tar.gz: '007900de7a1558633520c61870a58eff341e9c11009441dbabe0fbc177e4ed99'
5
5
  SHA512:
6
- metadata.gz: 8ad1d321a4e319cf7760f0984085c635cb98fd9aef600665bf90b7a301caa53e9d9fb42dc064196e083d115fd6719eaa1445263c32e4906c6c9de4789970c9e0
7
- data.tar.gz: 4a4e5d184b7ba0cd8dcba3f27b8dc7ec65138ba1b34cd5414e2e6610f9a590c9bcae09940d96b960deb9db3d25b7b0314b1ff0c630b877e10ae2461b6fe645be
6
+ metadata.gz: 592ecc114de13f0e43bba9193e1ffd3a973c89a43fac3ed1b750b6a70e29b5bf128a05657baf3fc2ccb77134f092efac055651907f01c0ed6d3c00d45a5ebdc9
7
+ data.tar.gz: a7baed1f1df451e8bd5183fec4631e49c0761e700c4c95fc070389894894a5fb90103d0adce29da797bc1cae72f8a1f21e71da0279e8c18cb62e3b3b5ae05f0a
data/Changes.md CHANGED
@@ -2,7 +2,69 @@
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
- HEAD
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
+
53
+ 6.1.2
54
+ ---------
55
+
56
+ - Improve readability in dark mode Web UI [#4674]
57
+ - Fix Web UI crash with corrupt session [#4672]
58
+ - Allow middleware to yield arguments [#4673, @eugeneius]
59
+ - Migrate CI from CircleCI to GitHub Actions [#4677]
60
+
61
+ 6.1.1
62
+ ---------
63
+
64
+ - Jobs are now sorted by age in the Busy Workers table. [#4641]
65
+ - Fix "check all" JS logic in Web UI [#4619]
66
+
67
+ 6.1.0
6
68
  ---------
7
69
 
8
70
  - Web UI - Dark Mode fixes [#4543, natematykiewicz]
@@ -136,7 +198,7 @@ assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs_for(FooMailer).size
136
198
 
137
199
  This release has major breaking changes. Read and test carefully in production.
138
200
 
139
- - 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
140
202
  features/internals like the retry subsystem. [#4213, pirj]
141
203
  ```ruby
142
204
  class MyJob < ActiveJob::Base
data/README.md CHANGED
@@ -2,8 +2,7 @@ Sidekiq
2
2
  ==============
3
3
 
4
4
  [![Gem Version](https://badge.fury.io/rb/sidekiq.svg)](https://rubygems.org/gems/sidekiq)
5
- [![Codecov](https://codecov.io/gh/mperham/sidekiq/branch/master/graph/badge.svg)](https://codecov.io/gh/mperham/sidekiq)
6
- [![Build Status](https://circleci.com/gh/mperham/sidekiq/tree/master.svg?style=svg)](https://circleci.com/gh/mperham/sidekiq/tree/master)
5
+ ![Build](https://github.com/mperham/sidekiq/workflows/CI/badge.svg)
7
6
 
8
7
  Simple, efficient background processing for Ruby.
9
8
 
data/lib/sidekiq.rb CHANGED
@@ -198,7 +198,7 @@ module Sidekiq
198
198
  end
199
199
 
200
200
  def self.logger
201
- @logger ||= Sidekiq::Logger.new(STDOUT, level: Logger::INFO)
201
+ @logger ||= Sidekiq::Logger.new($stdout, level: Logger::INFO)
202
202
  end
203
203
 
204
204
  def self.logger=(logger)
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.
@@ -916,7 +931,8 @@ module Sidekiq
916
931
  class Workers
917
932
  include Enumerable
918
933
 
919
- def each
934
+ def each(&block)
935
+ results = []
920
936
  Sidekiq.redis do |conn|
921
937
  procs = conn.sscan_each("processes").to_a
922
938
  procs.sort.each do |key|
@@ -930,10 +946,12 @@ module Sidekiq
930
946
  p = hsh["payload"]
931
947
  # avoid breaking API, this is a side effect of the JSON optimization in #4316
932
948
  hsh["payload"] = Sidekiq.load_json(p) if p.is_a?(String)
933
- yield key, tid, hsh
949
+ results << [key, tid, hsh]
934
950
  end
935
951
  end
936
952
  end
953
+
954
+ results.sort_by { |(_, _, hsh)| hsh["run_at"] }.each(&block)
937
955
  end
938
956
 
939
957
  # Note that #size is only as accurate as Sidekiq's heartbeat,
data/lib/sidekiq/cli.rb CHANGED
@@ -33,8 +33,9 @@ module Sidekiq
33
33
  # Code within this method is not tested because it alters
34
34
  # global process state irreversibly. PRs which improve the
35
35
  # test coverage of Sidekiq::CLI are welcomed.
36
- def run
37
- boot_system
36
+ def run(boot_app: true)
37
+ boot_application if boot_app
38
+
38
39
  if environment == "development" && $stdout.tty? && Sidekiq.log_formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
39
40
  print_banner
40
41
  end
@@ -43,7 +44,7 @@ module Sidekiq
43
44
  self_read, self_write = IO.pipe
44
45
  sigs = %w[INT TERM TTIN TSTP]
45
46
  # USR1 and USR2 don't work on the JVM
46
- sigs << "USR2" unless jruby?
47
+ sigs << "USR2" if Sidekiq.pro? && !jruby?
47
48
  sigs.each do |sig|
48
49
  trap sig do
49
50
  self_write.puts(sig)
@@ -58,9 +59,22 @@ module Sidekiq
58
59
 
59
60
  # touch the connection pool so it is created before we
60
61
  # fire startup and start multithreading.
61
- ver = Sidekiq.redis_info["redis_version"]
62
+ info = Sidekiq.redis_info
63
+ ver = info["redis_version"]
62
64
  raise "You are connecting to Redis v#{ver}, Sidekiq requires Redis v4.0.0 or greater" if ver < "4"
63
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
+
64
78
  # Since the user can pass us a connection pool explicitly in the initializer, we
65
79
  # need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
66
80
  cursize = Sidekiq.redis_pool.size
@@ -239,7 +253,7 @@ module Sidekiq
239
253
  Sidekiq.options
240
254
  end
241
255
 
242
- def boot_system
256
+ def boot_application
243
257
  ENV["RACK_ENV"] = ENV["RAILS_ENV"] = environment
244
258
 
245
259
  if File.directory?(options[:require])
@@ -357,12 +371,6 @@ module Sidekiq
357
371
  Sidekiq.logger.level = ::Logger::DEBUG if options[:verbose]
358
372
  end
359
373
 
360
- INTERNAL_OPTIONS = [
361
- # These are options that are set internally and cannot be
362
- # set via the config file or command line arguments.
363
- :strict
364
- ]
365
-
366
374
  def parse_config(path)
367
375
  opts = YAML.load(ERB.new(File.read(path)).result) || {}
368
376
 
@@ -373,7 +381,7 @@ module Sidekiq
373
381
  end
374
382
 
375
383
  opts = opts.merge(opts.delete(environment.to_sym) || {})
376
- opts.delete(*INTERNAL_OPTIONS)
384
+ opts.delete(:strict)
377
385
 
378
386
  parse_queues(opts, opts.delete(:queues) || [])
379
387
 
@@ -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
 
@@ -5,9 +5,10 @@ require "sidekiq/extensions/generic_proxy"
5
5
  module Sidekiq
6
6
  module Extensions
7
7
  ##
8
- # Adds 'delay', 'delay_for' and `delay_until` methods to ActionMailer to offload arbitrary email
9
- # delivery to Sidekiq. Example:
8
+ # Adds +delay+, +delay_for+ and +delay_until+ methods to ActionMailer to offload arbitrary email
9
+ # delivery to Sidekiq.
10
10
  #
11
+ # @example
11
12
  # UserMailer.delay.send_welcome_email(new_user)
12
13
  # UserMailer.delay_for(5.days).send_welcome_email(new_user)
13
14
  # UserMailer.delay_until(5.days.from_now).send_welcome_email(new_user)
@@ -5,7 +5,7 @@ require "sidekiq/extensions/generic_proxy"
5
5
  module Sidekiq
6
6
  module Extensions
7
7
  ##
8
- # Adds 'delay', 'delay_for' and `delay_until` methods to ActiveRecord to offload instance method
8
+ # Adds +delay+, +delay_for+ and +delay_until+ methods to ActiveRecord to offload instance method
9
9
  # execution to Sidekiq.
10
10
  #
11
11
  # @example
data/lib/sidekiq/fetch.rb CHANGED
@@ -36,12 +36,18 @@ 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
 
43
- # By leaving this as a class method, it can be pluggable and used by the Manager actor. Making it
44
- # an instance method will make it async to the Fetcher actor
45
51
  def bulk_requeue(inprogress, options)
46
52
  return if inprogress.empty?
47
53
 
@@ -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