sidekiq 6.1.2 → 6.2.2

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +59 -1
  3. data/LICENSE +1 -1
  4. data/lib/sidekiq/api.rb +95 -57
  5. data/lib/sidekiq/cli.rb +14 -1
  6. data/lib/sidekiq/client.rb +1 -5
  7. data/lib/sidekiq/extensions/generic_proxy.rb +3 -1
  8. data/lib/sidekiq/fetch.rb +9 -1
  9. data/lib/sidekiq/job.rb +8 -0
  10. data/lib/sidekiq/job_logger.rb +1 -1
  11. data/lib/sidekiq/job_retry.rb +4 -7
  12. data/lib/sidekiq/launcher.rb +71 -18
  13. data/lib/sidekiq/logger.rb +3 -2
  14. data/lib/sidekiq/middleware/chain.rb +5 -3
  15. data/lib/sidekiq/scheduled.rb +7 -1
  16. data/lib/sidekiq/testing.rb +1 -3
  17. data/lib/sidekiq/util.rb +28 -0
  18. data/lib/sidekiq/version.rb +1 -1
  19. data/lib/sidekiq/web/action.rb +2 -2
  20. data/lib/sidekiq/web/application.rb +14 -6
  21. data/lib/sidekiq/web/csrf_protection.rb +28 -6
  22. data/lib/sidekiq/web/helpers.rb +31 -11
  23. data/lib/sidekiq/web/router.rb +4 -1
  24. data/lib/sidekiq/web.rb +34 -78
  25. data/sidekiq.gemspec +10 -2
  26. data/web/assets/images/apple-touch-icon.png +0 -0
  27. data/web/assets/stylesheets/application-dark.css +18 -14
  28. data/web/assets/stylesheets/application.css +29 -130
  29. data/web/locales/ar.yml +8 -2
  30. data/web/locales/en.yml +3 -0
  31. data/web/locales/es.yml +18 -2
  32. data/web/locales/fr.yml +8 -1
  33. data/web/locales/ja.yml +3 -0
  34. data/web/locales/lt.yml +1 -1
  35. data/web/views/_job_info.erb +1 -1
  36. data/web/views/busy.erb +48 -17
  37. data/web/views/dashboard.erb +14 -6
  38. data/web/views/dead.erb +1 -1
  39. data/web/views/layout.erb +1 -0
  40. data/web/views/morgue.erb +6 -6
  41. data/web/views/queue.erb +1 -1
  42. data/web/views/queues.erb +3 -3
  43. data/web/views/retries.erb +7 -7
  44. data/web/views/retry.erb +1 -1
  45. data/web/views/scheduled.erb +1 -1
  46. metadata +12 -26
  47. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
  48. data/.github/contributing.md +0 -32
  49. data/.github/workflows/ci.yml +0 -41
  50. data/.gitignore +0 -13
  51. data/.standard.yml +0 -20
  52. data/3.0-Upgrade.md +0 -70
  53. data/4.0-Upgrade.md +0 -53
  54. data/5.0-Upgrade.md +0 -56
  55. data/6.0-Upgrade.md +0 -72
  56. data/COMM-LICENSE +0 -97
  57. data/Ent-2.0-Upgrade.md +0 -37
  58. data/Ent-Changes.md +0 -281
  59. data/Gemfile +0 -24
  60. data/Gemfile.lock +0 -192
  61. data/Pro-2.0-Upgrade.md +0 -138
  62. data/Pro-3.0-Upgrade.md +0 -44
  63. data/Pro-4.0-Upgrade.md +0 -35
  64. data/Pro-5.0-Upgrade.md +0 -25
  65. data/Pro-Changes.md +0 -805
  66. data/Rakefile +0 -10
  67. 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: 1b0da541124d097e05936cc860a93009ba170d714cbd8dbc760c1245818e8ed7
4
+ data.tar.gz: 63995ccbb57fa16e4954cda4ab9610506b0a8ff35084492b520fc18608face35
5
5
  SHA512:
6
- metadata.gz: c0e416fe575874c2051e88c64a6cbb30ad2ba4929ca33487ca1323b1963101d3d08ed43b8edbe874bccfa677521b0745baf589b451c7c56ff41b7bc70ee4f88d
7
- data.tar.gz: 4d1cd6d03768f9844bd8edd9bdb545a743084322f4edc01682e04b0990b0fb6aa3f1c6ad4f9a87c1b810a07e03b08eae0405ad2291b42d58eebb257bed7a60f4
6
+ metadata.gz: f3b864db74530840437c0af43475ed0e12a876d6d2cbbd808ecc72722adddbd8d5548c9b777d4e34af5deddf94871567717518f70d787ea7f6a089f2f5b2f4ed
7
+ data.tar.gz: 4569eed5baa10134e99a0b1404c213e3b35c08ec158e2fa8b006918298750919e8d34074aa10d3b4dfb621bb18cbe68be467d1e7822fb855012cff0f64dcdc7b
data/Changes.md CHANGED
@@ -2,6 +2,64 @@
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.2
6
+ ---------
7
+
8
+ - Reduce retry jitter, add jitter to `sidekiq_retry_in` values [#4957]
9
+ - Minimize scheduler load on Redis at scale [#4882]
10
+ - Improve logging of delay jobs [#4904, BuonOno]
11
+ - Minor CSS improvements for buttons and tables, design PRs always welcome!
12
+ - Tweak Web UI `Cache-Control` header [#4966]
13
+ - Rename internal API class `Sidekiq::Job` to `Sidekiq::JobRecord` [#4955]
14
+
15
+ 6.2.1
16
+ ---------
17
+
18
+ - Update RTT warning logic to handle transient RTT spikes [#4851]
19
+ - Fix very low priority CVE on unescaped queue name [#4852]
20
+ - Add note about sessions and Rails apps in API mode
21
+
22
+ 6.2.0
23
+ ---------
24
+
25
+ - Store Redis RTT and log if poor [#4824]
26
+ - Add process/thread stats to Busy page [#4806]
27
+ - Improve Web UI on mobile devices [#4840]
28
+ - **Refactor Web UI session usage** [#4804]
29
+ Numerous people have hit "Forbidden" errors and struggled with Sidekiq's
30
+ Web UI session requirement. If you have code in your initializer for
31
+ Web sessions, it's quite possible it will need to be removed. Here's
32
+ an overview:
33
+ ```
34
+ Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app,
35
+ make sure you mount Sidekiq::Web *inside* your routes in `config/routes.rb` so
36
+ Sidekiq can reuse the Rails session:
37
+
38
+ Rails.application.routes.draw do
39
+ mount Sidekiq::Web => "/sidekiq"
40
+ ....
41
+ end
42
+
43
+ If this is a bare Rack app, use a session middleware before Sidekiq::Web:
44
+
45
+ # first, use IRB to create a shared secret key for sessions and commit it
46
+ require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) }
47
+
48
+ # now, update your Rack app to include the secret with a session cookie middleware
49
+ use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
50
+ run Sidekiq::Web
51
+
52
+ If this is a Rails app in API mode, you need to enable sessions.
53
+
54
+ https://guides.rubyonrails.org/api_app.html#using-session-middlewares
55
+ ```
56
+
57
+ 6.1.3
58
+ ---------
59
+
60
+ - Warn if Redis is configured to evict data under memory pressure [#4752]
61
+ - Add process RSS on the Busy page [#4717]
62
+
5
63
  6.1.2
6
64
  ---------
7
65
 
@@ -150,7 +208,7 @@ assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs_for(FooMailer).size
150
208
 
151
209
  This release has major breaking changes. Read and test carefully in production.
152
210
 
153
- - With Rails 6.0.1+, ActiveJobs can now use `sidekiq_options` directly to configure Sidekiq
211
+ - With Rails 6.0.2+, ActiveJobs can now use `sidekiq_options` directly to configure Sidekiq
154
212
  features/internals like the retry subsystem. [#4213, pirj]
155
213
  ```ruby
156
214
  class MyJob < ActiveJob::Base
data/LICENSE CHANGED
@@ -6,4 +6,4 @@ for license text.
6
6
 
7
7
  Sidekiq Pro has a commercial-friendly license allowing private forks
8
8
  and modifications of Sidekiq. Please see https://sidekiq.org/products/pro.html for
9
- more detail. You can find the commercial license terms in COMM-LICENSE.
9
+ more detail. You can find the commercial license terms in COMM-LICENSE.txt.
data/lib/sidekiq/api.rb CHANGED
@@ -8,7 +8,7 @@ require "base64"
8
8
  module Sidekiq
9
9
  class Stats
10
10
  def initialize
11
- fetch_stats!
11
+ fetch_stats_fast!
12
12
  end
13
13
 
14
14
  def processed
@@ -51,7 +51,8 @@ module Sidekiq
51
51
  Sidekiq::Stats::Queues.new.lengths
52
52
  end
53
53
 
54
- def fetch_stats!
54
+ # O(1) redis calls
55
+ def fetch_stats_fast!
55
56
  pipe1_res = Sidekiq.redis { |conn|
56
57
  conn.pipelined do
57
58
  conn.get("stat:processed")
@@ -64,6 +65,33 @@ module Sidekiq
64
65
  end
65
66
  }
66
67
 
68
+ default_queue_latency = if (entry = pipe1_res[6].first)
69
+ job = begin
70
+ Sidekiq.load_json(entry)
71
+ rescue
72
+ {}
73
+ end
74
+ now = Time.now.to_f
75
+ thence = job["enqueued_at"] || now
76
+ now - thence
77
+ else
78
+ 0
79
+ end
80
+
81
+ @stats = {
82
+ processed: pipe1_res[0].to_i,
83
+ failed: pipe1_res[1].to_i,
84
+ scheduled_size: pipe1_res[2],
85
+ retry_size: pipe1_res[3],
86
+ dead_size: pipe1_res[4],
87
+ processes_size: pipe1_res[5],
88
+
89
+ default_queue_latency: default_queue_latency
90
+ }
91
+ end
92
+
93
+ # O(number of processes + number of queues) redis calls
94
+ def fetch_stats_slow!
67
95
  processes = Sidekiq.redis { |conn|
68
96
  conn.sscan_each("processes").to_a
69
97
  }
@@ -83,30 +111,13 @@ module Sidekiq
83
111
  workers_size = pipe2_res[0...s].sum(&:to_i)
84
112
  enqueued = pipe2_res[s..-1].sum(&:to_i)
85
113
 
86
- default_queue_latency = if (entry = pipe1_res[6].first)
87
- job = begin
88
- Sidekiq.load_json(entry)
89
- rescue
90
- {}
91
- end
92
- now = Time.now.to_f
93
- thence = job["enqueued_at"] || now
94
- now - thence
95
- else
96
- 0
97
- end
98
- @stats = {
99
- processed: pipe1_res[0].to_i,
100
- failed: pipe1_res[1].to_i,
101
- scheduled_size: pipe1_res[2],
102
- retry_size: pipe1_res[3],
103
- dead_size: pipe1_res[4],
104
- processes_size: pipe1_res[5],
114
+ @stats[:workers_size] = workers_size
115
+ @stats[:enqueued] = enqueued
116
+ end
105
117
 
106
- default_queue_latency: default_queue_latency,
107
- workers_size: workers_size,
108
- enqueued: enqueued
109
- }
118
+ def fetch_stats!
119
+ fetch_stats_fast!
120
+ fetch_stats_slow!
110
121
  end
111
122
 
112
123
  def reset(*stats)
@@ -126,7 +137,8 @@ module Sidekiq
126
137
  private
127
138
 
128
139
  def stat(s)
129
- @stats[s]
140
+ fetch_stats_slow! if @stats[s].nil?
141
+ @stats[s] || raise(ArgumentError, "Unknown stat #{s}")
130
142
  end
131
143
 
132
144
  class Queues
@@ -141,7 +153,7 @@ module Sidekiq
141
153
  }
142
154
 
143
155
  array_of_arrays = queues.zip(lengths).sort_by { |_, size| -size }
144
- Hash[array_of_arrays]
156
+ array_of_arrays.to_h
145
157
  end
146
158
  end
147
159
  end
@@ -255,7 +267,7 @@ module Sidekiq
255
267
  break if entries.empty?
256
268
  page += 1
257
269
  entries.each do |entry|
258
- yield Job.new(entry, @name)
270
+ yield JobRecord.new(entry, @name)
259
271
  end
260
272
  deleted_size = initial_size - size
261
273
  end
@@ -265,7 +277,7 @@ module Sidekiq
265
277
  # Find the job with the given JID within this queue.
266
278
  #
267
279
  # This is a slow, inefficient operation. Do not use under
268
- # normal conditions. Sidekiq Pro contains a faster version.
280
+ # normal conditions.
269
281
  def find_job(jid)
270
282
  detect { |j| j.jid == jid }
271
283
  end
@@ -286,9 +298,9 @@ module Sidekiq
286
298
  # sorted set.
287
299
  #
288
300
  # The job should be considered immutable but may be
289
- # removed from the queue via Job#delete.
301
+ # removed from the queue via JobRecord#delete.
290
302
  #
291
- class Job
303
+ class JobRecord
292
304
  attr_reader :item
293
305
  attr_reader :value
294
306
 
@@ -316,21 +328,23 @@ module Sidekiq
316
328
 
317
329
  def display_class
318
330
  # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
319
- @klass ||= case klass
320
- when /\ASidekiq::Extensions::Delayed/
321
- safe_load(args[0], klass) do |target, method, _|
322
- "#{target}.#{method}"
323
- end
324
- when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
325
- job_class = @item["wrapped"] || args[0]
326
- if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob"
327
- # MailerClass#mailer_method
328
- args[0]["arguments"][0..1].join("#")
329
- else
330
- job_class
331
- end
332
- else
333
- klass
331
+ @klass ||= self["display_class"] || begin
332
+ case klass
333
+ when /\ASidekiq::Extensions::Delayed/
334
+ safe_load(args[0], klass) do |target, method, _|
335
+ "#{target}.#{method}"
336
+ end
337
+ when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
338
+ job_class = @item["wrapped"] || args[0]
339
+ if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob"
340
+ # MailerClass#mailer_method
341
+ args[0]["arguments"][0..1].join("#")
342
+ else
343
+ job_class
344
+ end
345
+ else
346
+ klass
347
+ end
334
348
  end
335
349
  end
336
350
 
@@ -443,7 +457,7 @@ module Sidekiq
443
457
  end
444
458
  end
445
459
 
446
- class SortedEntry < Job
460
+ class SortedEntry < JobRecord
447
461
  attr_reader :score
448
462
  attr_reader :parent
449
463
 
@@ -791,19 +805,23 @@ module Sidekiq
791
805
  # you'll be happier this way
792
806
  conn.pipelined do
793
807
  procs.each do |key|
794
- conn.hmget(key, "info", "busy", "beat", "quiet")
808
+ conn.hmget(key, "info", "busy", "beat", "quiet", "rss", "rtt_us")
795
809
  end
796
810
  end
797
811
  }
798
812
 
799
- result.each do |info, busy, at_s, quiet|
813
+ result.each do |info, busy, at_s, quiet, rss, rtt|
800
814
  # If a process is stopped between when we query Redis for `procs` and
801
815
  # when we query for `result`, we will have an item in `result` that is
802
816
  # composed of `nil` values.
803
817
  next if info.nil?
804
818
 
805
819
  hash = Sidekiq.load_json(info)
806
- yield Process.new(hash.merge("busy" => busy.to_i, "beat" => at_s.to_f, "quiet" => quiet))
820
+ yield Process.new(hash.merge("busy" => busy.to_i,
821
+ "beat" => at_s.to_f,
822
+ "quiet" => quiet,
823
+ "rss" => rss.to_i,
824
+ "rtt_us" => rtt.to_i))
807
825
  end
808
826
  end
809
827
 
@@ -815,6 +833,18 @@ module Sidekiq
815
833
  Sidekiq.redis { |conn| conn.scard("processes") }
816
834
  end
817
835
 
836
+ # Total number of threads available to execute jobs.
837
+ # For Sidekiq Enterprise customers this number (in production) must be
838
+ # less than or equal to your licensed concurrency.
839
+ def total_concurrency
840
+ sum { |x| x["concurrency"].to_i }
841
+ end
842
+
843
+ def total_rss_in_kb
844
+ sum { |x| x["rss"].to_i }
845
+ end
846
+ alias_method :total_rss, :total_rss_in_kb
847
+
818
848
  # Returns the identity of the current cluster leader or "" if no leader.
819
849
  # This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
820
850
  # or Sidekiq Pro.
@@ -864,6 +894,10 @@ module Sidekiq
864
894
  self["identity"]
865
895
  end
866
896
 
897
+ def queues
898
+ self["queues"]
899
+ end
900
+
867
901
  def quiet!
868
902
  signal("TSTP")
869
903
  end
@@ -894,8 +928,8 @@ module Sidekiq
894
928
  end
895
929
 
896
930
  ##
897
- # A worker is a thread that is currently processing a job.
898
- # Programmatic access to the current active worker set.
931
+ # The WorkSet stores the work being done by this Sidekiq cluster.
932
+ # It tracks the process and thread working on each job.
899
933
  #
900
934
  # WARNING WARNING WARNING
901
935
  #
@@ -903,17 +937,17 @@ module Sidekiq
903
937
  # If you call #size => 5 and then expect #each to be
904
938
  # called 5 times, you're going to have a bad time.
905
939
  #
906
- # workers = Sidekiq::Workers.new
907
- # workers.size => 2
908
- # workers.each do |process_id, thread_id, work|
940
+ # works = Sidekiq::WorkSet.new
941
+ # works.size => 2
942
+ # works.each do |process_id, thread_id, work|
909
943
  # # process_id is a unique identifier per Sidekiq process
910
944
  # # thread_id is a unique identifier per thread
911
945
  # # work is a Hash which looks like:
912
- # # { 'queue' => name, 'run_at' => timestamp, 'payload' => msg }
946
+ # # { 'queue' => name, 'run_at' => timestamp, 'payload' => job_hash }
913
947
  # # run_at is an epoch Integer.
914
948
  # end
915
949
  #
916
- class Workers
950
+ class WorkSet
917
951
  include Enumerable
918
952
 
919
953
  def each(&block)
@@ -960,4 +994,8 @@ module Sidekiq
960
994
  end
961
995
  end
962
996
  end
997
+ # Since "worker" is a nebulous term, we've deprecated the use of this class name.
998
+ # Is "worker" a process, a type of job, a thread? Undefined!
999
+ # WorkSet better describes the data.
1000
+ Workers = WorkSet
963
1001
  end
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
 
@@ -24,7 +24,9 @@ module Sidekiq
24
24
  if marshalled.size > SIZE_LIMIT
25
25
  ::Sidekiq.logger.warn { "#{@target}.#{name} job argument is #{marshalled.bytesize} bytes, you should refactor it to reduce the size" }
26
26
  end
27
- @performable.client_push({"class" => @performable, "args" => [marshalled]}.merge(@opts))
27
+ @performable.client_push({"class" => @performable,
28
+ "args" => [marshalled],
29
+ "display_class" => "#{@target}.#{name}"}.merge(@opts))
28
30
  end
29
31
  end
30
32
  end
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(TIMEOUT)
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
 
@@ -0,0 +1,8 @@
1
+ require "sidekiq/worker"
2
+
3
+ module Sidekiq
4
+ # Sidekiq::Job is a new alias for Sidekiq::Worker, coming in 6.3.0.
5
+ # You can opt into this by requiring 'sidekiq/job' in your initializer
6
+ # and then using `include Sidekiq::Job` rather than `Sidekiq::Worker`.
7
+ Job = Worker
8
+ end