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

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30f824346db9b0ebf8ee13c6ac0101494e5fd6d05b4ed2ef3ad97c5b17ccbc10
4
- data.tar.gz: bed22f02925116256550bbc34ef9decd70bdbca356d5d61abedd4d14a7dcac45
3
+ metadata.gz: 34d69bb09eb124a8fd4710af9972bdb6a0cd74429bc3e61cdccdae683a4a0203
4
+ data.tar.gz: 8a37c7f172e4efabb061f980a73547d7245585574133b07d159f0412e49dd362
5
5
  SHA512:
6
- metadata.gz: 347e82cf6f215a1e4bd09c3f12d382be79d96bb83ccd1d09f928db19b936c2dd72c0f2e3a8988160086da7928a7911d84eddd005428c7b6a337763c4b60a3492
7
- data.tar.gz: ef0a03f45d4d35e832f36b7a09ae0694838cd0cf3582dc20d53956bb5a61ba07811d410600b43a9bdf777e68dd2549910a125d23885afca8388b8513bb7ac8d1
6
+ metadata.gz: dbb63070d419ad3d2b8192b2864ff4608288c00fe51c7e79e4a3faafd33665cce0c92ece38f9cd2a30ae4b7ff7e66debaf894198b054b88c5d0b5d5ee3790153
7
+ data.tar.gz: 8ba7a30ebdd19734a7cc64031a6eb63e4f317de0ae652bed324c272fd57f422186add097b38ab88aaa2a0a925de6b0845d18739691cbd9e9f2e90f3d79bb9e0a
data/Changes.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  [Sidekiq Changes](https://github.com/sidekiq/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/sidekiq/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/sidekiq/sidekiq/blob/main/Ent-Changes.md)
4
4
 
5
+ 7.2.1
6
+ ----------
7
+
8
+ - Add `Sidekiq::Work` type which replaces the raw Hash as the third parameter in
9
+ `Sidekiq::WorkSet#each { |pid, tid, hash| ... }` [#6145]
10
+ - **DEPRECATED**: direct access to the attributes within the `hash` block parameter above.
11
+ The `Sidekiq::Work` instance contains accessor methods to get at the same data, e.g.
12
+ ```ruby
13
+ work["queue"] # Old
14
+ work.queue # New
15
+ ```
16
+ - Fix Ruby 3.3 warnings around `base64` gem [#6151, earlopain]
17
+
5
18
  7.2.0
6
19
  ----------
7
20
 
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # bin/bench is a helpful script to load test and
5
+ # performance tune Sidekiq's core. It's a configurable script,
6
+ # which accepts the following parameters as ENV variables.
7
+ #
8
+ # QUEUES
9
+ # Number of queues to consume from. Default is 8
10
+ #
11
+ # PROCESSES
12
+ # The number of processes this benchmark will create. Each process, consumes
13
+ # from one of the available queues. When processes are more than the number of
14
+ # queues, they are distributed to processes in round robin. Default is 8
15
+ #
16
+ # ELEMENTS
17
+ # Number of jobs to push to each queue. Default is 1000
18
+ #
19
+ # ITERATIONS
20
+ # Each queue pushes ITERATIONS times ELEMENTS jobs. Default is 1000
21
+ #
22
+ # PORT
23
+ # The port of the Dragonfly instance. Default is 6379
24
+ #
25
+ # IP
26
+ # The ip of the Dragonfly instance. Default is 127.0.0.1
27
+ #
28
+ # Example Usage:
29
+ #
30
+ # > RUBY_YJIT_ENABLE=1 THREADS=10 PROCESSES=8 QUEUES=8 bin/multi_queue_bench
31
+ #
32
+ # None of this script is considered a public API and may change over time.
33
+ #
34
+
35
+ # Quiet some warnings we see when running in warning mode:
36
+ # RUBYOPT=-w bundle exec sidekiq
37
+ $TESTING = false
38
+ puts RUBY_DESCRIPTION
39
+
40
+ require "bundler/setup"
41
+ Bundler.require(:default, :load_test)
42
+
43
+ class LoadWorker
44
+ include Sidekiq::Job
45
+ sidekiq_options retry: 1
46
+ sidekiq_retry_in do |x|
47
+ 1
48
+ end
49
+
50
+ def perform(idx, ts = nil)
51
+ puts(Time.now.to_f - ts) if !ts.nil?
52
+ # raise idx.to_s if idx % 100 == 1
53
+ end
54
+ end
55
+
56
+ def Process.rss
57
+ `ps -o rss= -p #{Process.pid}`.chomp.to_i
58
+ end
59
+
60
+ $iterations = ENV["ITERATIONS"] ? Integer(ENV["ITERATIONS"]) : 1_000
61
+ $elements = ENV["ELEMENTS"] ? Integer(ENV["ELEMENTS"]) : 1_000
62
+ $port = ENV["PORT"] ? Integer(ENV["PORT"]) : 6379
63
+ $ip = ENV["IP"] ? String(ENV["IP"]) : "127.0.0.1"
64
+
65
+ class Loader
66
+ def initialize
67
+ @iter = $iterations
68
+ @count = $elements
69
+ end
70
+
71
+ def configure(queue)
72
+ @x = Sidekiq.configure_embed do |config|
73
+ config.redis = {db: 0, host: $ip, port: $port}
74
+ config.concurrency = Integer(ENV.fetch("THREADS", "30"))
75
+ config.queues = queue
76
+ config.logger.level = Logger::WARN
77
+ config.average_scheduled_poll_interval = 2
78
+ config.reliable! if defined?(Sidekiq::Pro)
79
+ end
80
+
81
+ @self_read, @self_write = IO.pipe
82
+ %w[INT TERM TSTP TTIN].each do |sig|
83
+ trap sig do
84
+ @self_write.puts(sig)
85
+ end
86
+ rescue ArgumentError
87
+ puts "Signal #{sig} not supported"
88
+ end
89
+ end
90
+
91
+ def handle_signal(sig)
92
+ launcher = @x
93
+ Sidekiq.logger.debug "Got #{sig} signal"
94
+ case sig
95
+ when "INT"
96
+ # Handle Ctrl-C in JRuby like MRI
97
+ # http://jira.codehaus.org/browse/JRUBY-4637
98
+ raise Interrupt
99
+ when "TERM"
100
+ # Heroku sends TERM and then waits 30 seconds for process to exit.
101
+ raise Interrupt
102
+ when "TSTP"
103
+ Sidekiq.logger.info "Received TSTP, no longer accepting new work"
104
+ launcher.quiet
105
+ when "TTIN"
106
+ Thread.list.each do |thread|
107
+ Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread["label"]}"
108
+ if thread.backtrace
109
+ Sidekiq.logger.warn thread.backtrace.join("\n")
110
+ else
111
+ Sidekiq.logger.warn "<no backtrace available>"
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ def setup(queue)
118
+ Sidekiq.logger.error("Setup RSS: #{Process.rss}")
119
+ Sidekiq.logger.error("Pushing work to queue: #{queue}")
120
+ start = Time.now
121
+ @iter.times do
122
+ arr = Array.new(@count) { |idx| [idx] }
123
+ #always prepends by queue:: that's why we pass 'q1, q2 etc' instead of `queue::q1`
124
+ Sidekiq::Client.push_bulk("class" => LoadWorker, "args" => arr, "queue" => queue)
125
+ end
126
+ end
127
+
128
+ def monitor_single(queue)
129
+ q = "queue:#{queue}"
130
+ @monitor_single = Thread.new do
131
+ GC.start
132
+ loop do
133
+ sleep 0.2
134
+ total = Sidekiq.redis do |conn|
135
+ conn.llen q
136
+ end
137
+
138
+ if total == 0
139
+ sleep 0.1
140
+ @x.stop
141
+ Process.kill("INT", $$)
142
+ break
143
+ end
144
+
145
+ end
146
+ end
147
+ end
148
+
149
+ def monitor_all(queues)
150
+ @monitor_all = Thread.new do
151
+ GC.start
152
+ loop do
153
+ sleep 0.2
154
+ qsize = 0
155
+ queues.each do |q|
156
+ tmp = Sidekiq.redis do |conn|
157
+ conn.llen q
158
+ end
159
+ qsize = qsize + tmp
160
+ end
161
+ total = qsize
162
+
163
+ if total == 0
164
+ ending = Time.now - @start
165
+ size = @iter * @count * queues.length()
166
+ Sidekiq.logger.error("Done, #{size} jobs in #{ending} sec, #{(size / ending).to_i} jobs/sec")
167
+ Sidekiq.logger.error("Ending RSS: #{Process.rss}")
168
+
169
+ sleep 0.1
170
+ @x.stop
171
+ Process.kill("INT", $$)
172
+ break
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ def run(queues, queue, monitor_all_queues)
179
+ #Sidekiq.logger.warn("Consuming from #{queue}")
180
+ if monitor_all_queues
181
+ monitor_all(queues)
182
+ else
183
+ monitor_single(queue)
184
+ end
185
+
186
+ @start = Time.now
187
+ @x.run
188
+
189
+ while (readable_io = IO.select([@self_read]))
190
+ signal = readable_io.first[0].gets.strip
191
+ handle_signal(signal)
192
+ end
193
+ # normal
194
+ rescue Interrupt
195
+ rescue => e
196
+ raise e if $DEBUG
197
+ warn e.message
198
+ warn e.backtrace.join("\n")
199
+ exit 1
200
+ ensure
201
+ @x.stop
202
+ end
203
+ end
204
+
205
+ def setup(queue)
206
+ ll = Loader.new
207
+ ll.configure(queue)
208
+ ll.setup(queue)
209
+ end
210
+
211
+ def consume(queues, queue, monitor_all_queues)
212
+ ll = Loader.new
213
+ ll.configure(queue)
214
+ ll.run(queues, queue, monitor_all_queues)
215
+ end
216
+
217
+ # We assign one queue to each sidekiq process
218
+ def run(number_of_processes, total_queues)
219
+ read_stream, write_stream = IO.pipe
220
+
221
+ queues = []
222
+ (0..total_queues-1).each do |idx|
223
+ queues.push("queue:q#{idx}")
224
+ end
225
+
226
+ Sidekiq.logger.info("Queues are: #{queues}")
227
+
228
+ # Produce
229
+ start = Time.now
230
+ (0..total_queues-1).each do |idx|
231
+ Process.fork do
232
+ queue_num = "q#{idx}"
233
+ setup(queue_num)
234
+ end
235
+ end
236
+
237
+ queue_sz = $iterations * $elements * total_queues
238
+ Process.waitall
239
+
240
+ ending = Time.now - start
241
+ #Sidekiq.logger.info("Pushed #{queue_sz} in #{ending} secs")
242
+
243
+ # Consume
244
+ (0..number_of_processes-1).each do |idx|
245
+ Process.fork do
246
+ # First process only consumes from it's own queue but monitors all queues.
247
+ # It works as a synchronization point. Once all processes finish
248
+ # (that is, when all queues are emptied) it prints the the stats.
249
+ if idx == 0
250
+ queue = "q#{idx}"
251
+ consume(queues, queue, true)
252
+ else
253
+ queue = "q#{idx % total_queues}"
254
+ consume(queues, queue, false)
255
+ end
256
+ end
257
+ end
258
+
259
+ Process.waitall
260
+ write_stream.close
261
+ results = read_stream.read
262
+ read_stream.close
263
+ end
264
+
265
+ $total_processes = ENV["PROCESSES"] ? Integer(ENV["PROCESSES"]) : 8;
266
+ $total_queues = ENV["QUEUES"] ? Integer(ENV["QUEUES"]) : 8;
267
+
268
+ run($total_processes, $total_queues)
data/lib/sidekiq/api.rb CHANGED
@@ -4,7 +4,6 @@ require "sidekiq"
4
4
 
5
5
  require "zlib"
6
6
  require "set"
7
- require "base64"
8
7
 
9
8
  require "sidekiq/metrics/query"
10
9
 
@@ -491,8 +490,8 @@ module Sidekiq
491
490
  end
492
491
 
493
492
  def uncompress_backtrace(backtrace)
494
- decoded = Base64.decode64(backtrace)
495
- uncompressed = Zlib::Inflate.inflate(decoded)
493
+ strict_base64_decoded = backtrace.unpack1("m0")
494
+ uncompressed = Zlib::Inflate.inflate(strict_base64_decoded)
496
495
  Sidekiq.load_json(uncompressed)
497
496
  end
498
497
  end
@@ -1110,11 +1109,11 @@ module Sidekiq
1110
1109
 
1111
1110
  procs.zip(all_works).each do |key, workers|
1112
1111
  workers.each_pair do |tid, json|
1113
- results << [key, tid, Sidekiq.load_json(json)] unless json.empty?
1112
+ results << [key, tid, Sidekiq::Work.new(key, tid, Sidekiq.load_json(json))] unless json.empty?
1114
1113
  end
1115
1114
  end
1116
1115
 
1117
- results.sort_by { |(_, _, hsh)| hsh["run_at"] }.each(&block)
1116
+ results.sort_by { |(_, _, hsh)| hsh.raw("run_at") }.each(&block)
1118
1117
  end
1119
1118
 
1120
1119
  # Note that #size is only as accurate as Sidekiq's heartbeat,
@@ -1138,6 +1137,59 @@ module Sidekiq
1138
1137
  end
1139
1138
  end
1140
1139
  end
1140
+
1141
+ # Sidekiq::Work represents a job which is currently executing.
1142
+ class Work
1143
+ attr_reader :process_id
1144
+ attr_reader :thread_id
1145
+
1146
+ def initialize(pid, tid, hsh)
1147
+ @process_id = pid
1148
+ @thread_id = tid
1149
+ @hsh = hsh
1150
+ @job = nil
1151
+ end
1152
+
1153
+ def queue
1154
+ @hsh["queue"]
1155
+ end
1156
+
1157
+ def run_at
1158
+ Time.at(@hsh["run_at"])
1159
+ end
1160
+
1161
+ def job
1162
+ @job ||= Sidekiq::JobRecord.new(@hsh["payload"])
1163
+ end
1164
+
1165
+ def payload
1166
+ @hsh["payload"]
1167
+ end
1168
+
1169
+ # deprecated
1170
+ def [](key)
1171
+ kwargs = {uplevel: 1}
1172
+ kwargs[:category] = :deprecated if RUBY_VERSION > "3.0" # TODO
1173
+ warn("Direct access to `Sidekiq::Work` attributes is deprecated, please use `#payload`, `#queue`, `#run_at` or `#job` instead", **kwargs)
1174
+
1175
+ @hsh[key]
1176
+ end
1177
+
1178
+ # :nodoc:
1179
+ # @api private
1180
+ def raw(name)
1181
+ @hsh[name]
1182
+ end
1183
+
1184
+ def method_missing(*all)
1185
+ @hsh.send(*all)
1186
+ end
1187
+
1188
+ def respond_to_missing?(name)
1189
+ @hsh.respond_to?(name)
1190
+ end
1191
+ end
1192
+
1141
1193
  # Since "worker" is a nebulous term, we've deprecated the use of this class name.
1142
1194
  # Is "worker" a process, a type of job, a thread? Undefined!
1143
1195
  # WorkSet better describes the data.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "zlib"
4
- require "base64"
5
4
  require "sidekiq/component"
6
5
 
7
6
  module Sidekiq
@@ -295,7 +294,7 @@ module Sidekiq
295
294
  def compress_backtrace(backtrace)
296
295
  serialized = Sidekiq.dump_json(backtrace)
297
296
  compressed = Zlib::Deflate.deflate(serialized)
298
- Base64.encode64(compressed)
297
+ [compressed].pack("m0") # Base64.strict_encode64
299
298
  end
300
299
  end
301
300
  end
@@ -119,6 +119,7 @@ module Sidekiq
119
119
 
120
120
  def total_avg(metric = "ms")
121
121
  completed = totals["p"] - totals["f"]
122
+ return 0 if completed.zero?
122
123
  totals[metric].to_f / completed
123
124
  end
124
125
 
@@ -103,12 +103,16 @@ module Sidekiq
103
103
  def reset
104
104
  @lock.synchronize {
105
105
  array = [@totals, @jobs, @grams]
106
- @totals = Hash.new(0)
107
- @jobs = Hash.new(0)
108
- @grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
106
+ reset_instance_variables
109
107
  array
110
108
  }
111
109
  end
110
+
111
+ def reset_instance_variables
112
+ @totals = Hash.new(0)
113
+ @jobs = Hash.new(0)
114
+ @grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
115
+ end
112
116
  end
113
117
 
114
118
  class Middleware
data/lib/sidekiq/rails.rb CHANGED
@@ -56,10 +56,10 @@ module Sidekiq
56
56
  # This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
57
57
  # it will appear in the Sidekiq console with all of the job context.
58
58
  unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
59
- if ::Rails::VERSION::STRING < "7.1"
60
- ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
61
- else
59
+ if ::Rails.logger.respond_to?(:broadcast_to)
62
60
  ::Rails.logger.broadcast_to(config.logger)
61
+ else
62
+ ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
63
63
  end
64
64
  end
65
65
  end
@@ -39,9 +39,8 @@ module Sidekiq
39
39
  uri.password = redacted
40
40
  scrubbed_options[:url] = uri.to_s
41
41
  end
42
- if scrubbed_options[:password]
43
- scrubbed_options[:password] = redacted
44
- end
42
+ scrubbed_options[:password] = redacted if scrubbed_options[:password]
43
+ scrubbed_options[:sentinel_password] = redacted if scrubbed_options[:sentinel_password]
45
44
  scrubbed_options[:sentinels]&.each do |sentinel|
46
45
  sentinel[:password] = redacted if sentinel[:password]
47
46
  end
@@ -278,7 +278,7 @@ module Sidekiq
278
278
  def perform_one
279
279
  raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
280
280
  next_job = jobs.first
281
- Queues.delete_for(next_job["jid"], queue, to_s)
281
+ Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
282
282
  process_job(next_job)
283
283
  end
284
284
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "7.2.0"
4
+ VERSION = "7.2.1"
5
5
  MAJOR = 7
6
6
  end
@@ -27,7 +27,6 @@
27
27
  # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
28
 
29
29
  require "securerandom"
30
- require "base64"
31
30
  require "rack/request"
32
31
 
33
32
  module Sidekiq
@@ -143,7 +142,7 @@ module Sidekiq
143
142
  one_time_pad = SecureRandom.random_bytes(token.length)
144
143
  encrypted_token = xor_byte_strings(one_time_pad, token)
145
144
  masked_token = one_time_pad + encrypted_token
146
- Base64.urlsafe_encode64(masked_token)
145
+ encode_token(masked_token)
147
146
  end
148
147
 
149
148
  # Essentially the inverse of +mask_token+.
@@ -168,8 +167,12 @@ module Sidekiq
168
167
  ::Rack::Utils.secure_compare(token.to_s, decode_token(local).to_s)
169
168
  end
170
169
 
170
+ def encode_token(token)
171
+ [token].pack("m0").tr("+/", "-_")
172
+ end
173
+
171
174
  def decode_token(token)
172
- Base64.urlsafe_decode64(token)
175
+ token.tr("-_", "+/").unpack1("m0")
173
176
  end
174
177
 
175
178
  def xor_byte_strings(s1, s2)
data/sidekiq.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |gem|
23
23
  "rubygems_mfa_required" => "true"
24
24
  }
25
25
 
26
- gem.add_dependency "redis-client", ">= 0.14.0"
26
+ gem.add_dependency "redis-client", ">= 0.19.0"
27
27
  gem.add_dependency "connection_pool", ">= 2.3.0"
28
28
  gem.add_dependency "rack", ">= 2.2.4"
29
29
  gem.add_dependency "concurrent-ruby", "< 2"
data/web/views/busy.erb CHANGED
@@ -125,14 +125,14 @@
125
125
  <th><%= t('Arguments') %></th>
126
126
  <th><%= t('Started') %></th>
127
127
  </thead>
128
- <% @workset.each do |process, thread, msg| %>
129
- <% job = Sidekiq::JobRecord.new(msg['payload']) %>
128
+ <% @workset.each do |process, thread, work| %>
129
+ <% job = work.job %>
130
130
  <tr>
131
131
  <td><%= process %></td>
132
132
  <td><%= thread %></td>
133
133
  <td><%= job.jid %></td>
134
134
  <td>
135
- <a href="<%= root_path %>queues/<%= msg['queue'] %>"><%= msg['queue'] %></a>
135
+ <a href="<%= root_path %>queues/<%= work.queue %>"><%= work.queue %></a>
136
136
  </td>
137
137
  <td>
138
138
  <%= job.display_class %>
@@ -141,7 +141,7 @@
141
141
  <td>
142
142
  <div class="args"><%= display_args(job.display_args) %></div>
143
143
  </td>
144
- <td><%= relative_time(Time.at(msg['run_at'])) %></td>
144
+ <td><%= relative_time(work.run_at) %></td>
145
145
  </tr>
146
146
  <% end %>
147
147
  </table>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.2.0
4
+ version: 7.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-31 00:00:00.000000000 Z
11
+ date: 2024-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.14.0
19
+ version: 0.19.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.14.0
26
+ version: 0.19.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: connection_pool
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -78,6 +78,7 @@ files:
78
78
  - Changes.md
79
79
  - LICENSE.txt
80
80
  - README.md
81
+ - bin/multi_queue_bench
81
82
  - bin/sidekiq
82
83
  - bin/sidekiqload
83
84
  - bin/sidekiqmon