sidekiq 6.1.0 → 6.2.1

Sign up to get free protection for your applications and to get access to all the features.
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