sidekiq 6.2.1 → 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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 298711914bcb8534a9599c47b00b7410467ce324619ee70e7050d15c42f4c329
4
- data.tar.gz: '007900de7a1558633520c61870a58eff341e9c11009441dbabe0fbc177e4ed99'
3
+ metadata.gz: 1b0da541124d097e05936cc860a93009ba170d714cbd8dbc760c1245818e8ed7
4
+ data.tar.gz: 63995ccbb57fa16e4954cda4ab9610506b0a8ff35084492b520fc18608face35
5
5
  SHA512:
6
- metadata.gz: 592ecc114de13f0e43bba9193e1ffd3a973c89a43fac3ed1b750b6a70e29b5bf128a05657baf3fc2ccb77134f092efac055651907f01c0ed6d3c00d45a5ebdc9
7
- data.tar.gz: a7baed1f1df451e8bd5183fec4631e49c0761e700c4c95fc070389894894a5fb90103d0adce29da797bc1cae72f8a1f21e71da0279e8c18cb62e3b3b5ae05f0a
6
+ metadata.gz: f3b864db74530840437c0af43475ed0e12a876d6d2cbbd808ecc72722adddbd8d5548c9b777d4e34af5deddf94871567717518f70d787ea7f6a089f2f5b2f4ed
7
+ data.tar.gz: 4569eed5baa10134e99a0b1404c213e3b35c08ec158e2fa8b006918298750919e8d34074aa10d3b4dfb621bb18cbe68be467d1e7822fb855012cff0f64dcdc7b
data/Changes.md CHANGED
@@ -2,6 +2,16 @@
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
+
5
15
  6.2.1
6
16
  ---------
7
17
 
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,25 +65,6 @@ module Sidekiq
64
65
  end
65
66
  }
66
67
 
67
- processes = Sidekiq.redis { |conn|
68
- conn.sscan_each("processes").to_a
69
- }
70
-
71
- queues = Sidekiq.redis { |conn|
72
- conn.sscan_each("queues").to_a
73
- }
74
-
75
- pipe2_res = Sidekiq.redis { |conn|
76
- conn.pipelined do
77
- processes.each { |key| conn.hget(key, "busy") }
78
- queues.each { |queue| conn.llen("queue:#{queue}") }
79
- end
80
- }
81
-
82
- s = processes.size
83
- workers_size = pipe2_res[0...s].sum(&:to_i)
84
- enqueued = pipe2_res[s..-1].sum(&:to_i)
85
-
86
68
  default_queue_latency = if (entry = pipe1_res[6].first)
87
69
  job = begin
88
70
  Sidekiq.load_json(entry)
@@ -95,6 +77,7 @@ module Sidekiq
95
77
  else
96
78
  0
97
79
  end
80
+
98
81
  @stats = {
99
82
  processed: pipe1_res[0].to_i,
100
83
  failed: pipe1_res[1].to_i,
@@ -103,10 +86,38 @@ module Sidekiq
103
86
  dead_size: pipe1_res[4],
104
87
  processes_size: pipe1_res[5],
105
88
 
106
- default_queue_latency: default_queue_latency,
107
- workers_size: workers_size,
108
- enqueued: enqueued
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!
95
+ processes = Sidekiq.redis { |conn|
96
+ conn.sscan_each("processes").to_a
97
+ }
98
+
99
+ queues = Sidekiq.redis { |conn|
100
+ conn.sscan_each("queues").to_a
109
101
  }
102
+
103
+ pipe2_res = Sidekiq.redis { |conn|
104
+ conn.pipelined do
105
+ processes.each { |key| conn.hget(key, "busy") }
106
+ queues.each { |queue| conn.llen("queue:#{queue}") }
107
+ end
108
+ }
109
+
110
+ s = processes.size
111
+ workers_size = pipe2_res[0...s].sum(&:to_i)
112
+ enqueued = pipe2_res[s..-1].sum(&:to_i)
113
+
114
+ @stats[:workers_size] = workers_size
115
+ @stats[:enqueued] = enqueued
116
+ end
117
+
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
 
@@ -823,12 +837,13 @@ module Sidekiq
823
837
  # For Sidekiq Enterprise customers this number (in production) must be
824
838
  # less than or equal to your licensed concurrency.
825
839
  def total_concurrency
826
- sum { |x| x["concurrency"] }
840
+ sum { |x| x["concurrency"].to_i }
827
841
  end
828
842
 
829
- def total_rss
830
- sum { |x| x["rss"] || 0 }
843
+ def total_rss_in_kb
844
+ sum { |x| x["rss"].to_i }
831
845
  end
846
+ alias_method :total_rss, :total_rss_in_kb
832
847
 
833
848
  # Returns the identity of the current cluster leader or "" if no leader.
834
849
  # This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
@@ -879,6 +894,10 @@ module Sidekiq
879
894
  self["identity"]
880
895
  end
881
896
 
897
+ def queues
898
+ self["queues"]
899
+ end
900
+
882
901
  def quiet!
883
902
  signal("TSTP")
884
903
  end
@@ -909,8 +928,8 @@ module Sidekiq
909
928
  end
910
929
 
911
930
  ##
912
- # A worker is a thread that is currently processing a job.
913
- # 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.
914
933
  #
915
934
  # WARNING WARNING WARNING
916
935
  #
@@ -918,17 +937,17 @@ module Sidekiq
918
937
  # If you call #size => 5 and then expect #each to be
919
938
  # called 5 times, you're going to have a bad time.
920
939
  #
921
- # workers = Sidekiq::Workers.new
922
- # workers.size => 2
923
- # 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|
924
943
  # # process_id is a unique identifier per Sidekiq process
925
944
  # # thread_id is a unique identifier per thread
926
945
  # # work is a Hash which looks like:
927
- # # { 'queue' => name, 'run_at' => timestamp, 'payload' => msg }
946
+ # # { 'queue' => name, 'run_at' => timestamp, 'payload' => job_hash }
928
947
  # # run_at is an epoch Integer.
929
948
  # end
930
949
  #
931
- class Workers
950
+ class WorkSet
932
951
  include Enumerable
933
952
 
934
953
  def each(&block)
@@ -975,4 +994,8 @@ module Sidekiq
975
994
  end
976
995
  end
977
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
978
1001
  end
@@ -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
@@ -40,7 +40,7 @@ module Sidekiq
40
40
  # 4825 Sidekiq Pro with all queues paused will return an
41
41
  # empty set of queues with a trailing TIMEOUT value.
42
42
  if qs.size <= 1
43
- sleep(2)
43
+ sleep(TIMEOUT)
44
44
  return nil
45
45
  end
46
46
 
@@ -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
@@ -38,7 +38,7 @@ module Sidekiq
38
38
  # If we're using a wrapper class, like ActiveJob, use the "wrapped"
39
39
  # attribute to expose the underlying thing.
40
40
  h = {
41
- class: job_hash["wrapped"] || job_hash["class"],
41
+ class: job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"],
42
42
  jid: job_hash["jid"]
43
43
  }
44
44
  h[:bid] = job_hash["bid"] if job_hash["bid"]
@@ -214,16 +214,12 @@ module Sidekiq
214
214
  end
215
215
 
216
216
  def delay_for(worker, count, exception)
217
+ jitter = rand(10) * (count + 1)
217
218
  if worker&.sidekiq_retry_in_block
218
219
  custom_retry_in = retry_in(worker, count, exception).to_i
219
- return custom_retry_in if custom_retry_in > 0
220
+ return custom_retry_in + jitter if custom_retry_in > 0
220
221
  end
221
- seconds_to_delay(count)
222
- end
223
-
224
- # delayed_job uses the same basic formula
225
- def seconds_to_delay(count)
226
- (count**4) + 15 + (rand(30) * (count + 1))
222
+ (count**4) + 15 + jitter
227
223
  end
228
224
 
229
225
  def retry_in(worker, count, exception)
@@ -238,26 +238,22 @@ module Sidekiq
238
238
  end
239
239
 
240
240
  def to_data
241
- @data ||= begin
242
- {
243
- "hostname" => hostname,
244
- "started_at" => Time.now.to_f,
245
- "pid" => ::Process.pid,
246
- "tag" => @options[:tag] || "",
247
- "concurrency" => @options[:concurrency],
248
- "queues" => @options[:queues].uniq,
249
- "labels" => @options[:labels],
250
- "identity" => identity
251
- }
252
- end
241
+ @data ||= {
242
+ "hostname" => hostname,
243
+ "started_at" => Time.now.to_f,
244
+ "pid" => ::Process.pid,
245
+ "tag" => @options[:tag] || "",
246
+ "concurrency" => @options[:concurrency],
247
+ "queues" => @options[:queues].uniq,
248
+ "labels" => @options[:labels],
249
+ "identity" => identity
250
+ }
253
251
  end
254
252
 
255
253
  def to_json
256
- @json ||= begin
257
- # this data changes infrequently so dump it to a string
258
- # now so we don't need to dump it every heartbeat.
259
- Sidekiq.dump_json(to_data)
260
- end
254
+ # this data changes infrequently so dump it to a string
255
+ # now so we don't need to dump it every heartbeat.
256
+ @json ||= Sidekiq.dump_json(to_data)
261
257
  end
262
258
  end
263
259
  end
@@ -90,12 +90,12 @@ module Sidekiq
90
90
  end
91
91
 
92
92
  def add(klass, *args)
93
- remove(klass) if exists?(klass)
93
+ remove(klass)
94
94
  entries << Entry.new(klass, *args)
95
95
  end
96
96
 
97
97
  def prepend(klass, *args)
98
- remove(klass) if exists?(klass)
98
+ remove(klass)
99
99
  entries.insert(0, Entry.new(klass, *args))
100
100
  end
101
101
 
@@ -132,7 +132,7 @@ module Sidekiq
132
132
  def invoke(*args)
133
133
  return yield if empty?
134
134
 
135
- chain = retrieve.dup
135
+ chain = retrieve
136
136
  traverse_chain = proc do
137
137
  if chain.empty?
138
138
  yield
@@ -144,6 +144,8 @@ module Sidekiq
144
144
  end
145
145
  end
146
146
 
147
+ private
148
+
147
149
  class Entry
148
150
  attr_reader :klass
149
151
 
@@ -49,6 +49,7 @@ module Sidekiq
49
49
  @sleeper = ConnectionPool::TimedStack.new
50
50
  @done = false
51
51
  @thread = nil
52
+ @count_calls = 0
52
53
  end
53
54
 
54
55
  # Shut down this instance, will pause until the thread is dead.
@@ -152,8 +153,13 @@ module Sidekiq
152
153
  end
153
154
 
154
155
  def process_count
155
- pcount = Sidekiq::ProcessSet.new.size
156
+ # The work buried within Sidekiq::ProcessSet#cleanup can be
157
+ # expensive at scale. Cut it down by 90% with this counter.
158
+ # NB: This method is only called by the scheduler thread so we
159
+ # don't need to worry about the thread safety of +=.
160
+ pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
156
161
  pcount = 1 if pcount == 0
162
+ @count_calls += 1
157
163
  pcount
158
164
  end
159
165