sidekiq 7.2.0 → 7.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changes.md +33 -0
- data/README.md +2 -2
- data/bin/multi_queue_bench +271 -0
- data/lib/sidekiq/api.rb +74 -8
- data/lib/sidekiq/cli.rb +3 -1
- data/lib/sidekiq/deploy.rb +1 -1
- data/lib/sidekiq/job.rb +1 -1
- data/lib/sidekiq/job_retry.rb +1 -2
- data/lib/sidekiq/launcher.rb +6 -4
- data/lib/sidekiq/logger.rb +1 -1
- data/lib/sidekiq/metrics/query.rb +1 -0
- data/lib/sidekiq/metrics/tracking.rb +7 -3
- data/lib/sidekiq/middleware/current_attributes.rb +1 -1
- data/lib/sidekiq/processor.rb +1 -1
- data/lib/sidekiq/rails.rb +7 -3
- data/lib/sidekiq/redis_client_adapter.rb +2 -2
- data/lib/sidekiq/redis_connection.rb +3 -6
- data/lib/sidekiq/scheduled.rb +1 -1
- data/lib/sidekiq/testing.rb +2 -2
- data/lib/sidekiq/transaction_aware_client.rb +7 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +5 -0
- data/lib/sidekiq/web/application.rb +15 -3
- data/lib/sidekiq/web/csrf_protection.rb +8 -5
- data/lib/sidekiq/web/helpers.rb +6 -1
- data/sidekiq.gemspec +1 -1
- data/web/assets/javascripts/application.js +6 -0
- data/web/assets/stylesheets/application-rtl.css +10 -0
- data/web/assets/stylesheets/application.css +13 -0
- data/web/views/_footer.erb +13 -1
- data/web/views/busy.erb +4 -4
- data/web/views/metrics.erb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c43e6b585c25dcfc8ef8364bb36cf74f9167b981ad03faa3a8d76e0d45ebe55
|
4
|
+
data.tar.gz: d8c65dc03008f7280b36af94db753d4c7f68267c2eb0d78cd018322887aabbb0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2687692b873ab82bda2ad32e9be795150cd0a8d3d330bc19f5b509ba729bef33189e06ebac86b1906c2682187391d6cf0d532e47d03fcbea83058109c5816ef
|
7
|
+
data.tar.gz: 431a482baeb03fc4de50fbdfba8717fc332a9d6564fde98a77699a7bd174fa3194431385951cf689c64e04853039c95fcf287084f283e8d381b3b37d5bc665e0
|
data/Changes.md
CHANGED
@@ -2,6 +2,39 @@
|
|
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.4
|
6
|
+
----------
|
7
|
+
|
8
|
+
- Fix XSS in metrics filtering introduced in 7.2.0, CVE-2024-32887
|
9
|
+
Thanks to @UmerAdeemCheema for the security report.
|
10
|
+
|
11
|
+
7.2.3
|
12
|
+
----------
|
13
|
+
|
14
|
+
- [Support Dragonfly.io](https://www.mikeperham.com/2024/02/01/supporting-dragonfly/) as an alternative Redis implementation
|
15
|
+
- Fix error unpacking some compressed error backtraces [#6241]
|
16
|
+
- Fix potential heartbeat data leak [#6227]
|
17
|
+
- Add ability to find a currently running work by jid [#6212, fatkodima]
|
18
|
+
|
19
|
+
7.2.2
|
20
|
+
----------
|
21
|
+
|
22
|
+
- Add `Process.warmup` call in Ruby 3.3+
|
23
|
+
- Batch jobs now skip transactional push [#6160]
|
24
|
+
|
25
|
+
7.2.1
|
26
|
+
----------
|
27
|
+
|
28
|
+
- Add `Sidekiq::Work` type which replaces the raw Hash as the third parameter in
|
29
|
+
`Sidekiq::WorkSet#each { |pid, tid, hash| ... }` [#6145]
|
30
|
+
- **DEPRECATED**: direct access to the attributes within the `hash` block parameter above.
|
31
|
+
The `Sidekiq::Work` instance contains accessor methods to get at the same data, e.g.
|
32
|
+
```ruby
|
33
|
+
work["queue"] # Old
|
34
|
+
work.queue # New
|
35
|
+
```
|
36
|
+
- Fix Ruby 3.3 warnings around `base64` gem [#6151, earlopain]
|
37
|
+
|
5
38
|
7.2.0
|
6
39
|
----------
|
7
40
|
|
data/README.md
CHANGED
@@ -14,11 +14,11 @@ Rails to make background processing dead simple.
|
|
14
14
|
Requirements
|
15
15
|
-----------------
|
16
16
|
|
17
|
-
- Redis: 6.2+
|
17
|
+
- Redis: Redis 6.2+ or Dragonfly 1.13+
|
18
18
|
- Ruby: MRI 2.7+ or JRuby 9.3+.
|
19
19
|
|
20
20
|
Sidekiq 7.0 supports Rails 6.0+ but does not require it.
|
21
|
-
|
21
|
+
As of 7.2, Sidekiq supports Dragonfly as an alternative to Redis for data storage.
|
22
22
|
|
23
23
|
Installation
|
24
24
|
-----------------
|
@@ -0,0 +1,271 @@
|
|
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
|
+
# Sidekiq always prepends "queue:" to the queue name,
|
124
|
+
# that's why we pass 'q1', 'q2', etc instead of 'queue:q1'
|
125
|
+
Sidekiq::Client.push_bulk("class" => LoadWorker, "args" => arr, "queue" => queue)
|
126
|
+
$stdout.write "."
|
127
|
+
end
|
128
|
+
puts "Done"
|
129
|
+
end
|
130
|
+
|
131
|
+
def monitor_single(queue)
|
132
|
+
q = "queue:#{queue}"
|
133
|
+
@monitor_single = Thread.new do
|
134
|
+
GC.start
|
135
|
+
loop do
|
136
|
+
sleep 0.2
|
137
|
+
total = Sidekiq.redis do |conn|
|
138
|
+
conn.llen q
|
139
|
+
end
|
140
|
+
|
141
|
+
if total == 0
|
142
|
+
sleep 0.1
|
143
|
+
@x.stop
|
144
|
+
Process.kill("INT", $$)
|
145
|
+
break
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def monitor_all(queues)
|
153
|
+
@monitor_all = Thread.new do
|
154
|
+
GC.start
|
155
|
+
loop do
|
156
|
+
sleep 0.2
|
157
|
+
qsize = 0
|
158
|
+
queues.each do |q|
|
159
|
+
tmp = Sidekiq.redis do |conn|
|
160
|
+
conn.llen q
|
161
|
+
end
|
162
|
+
qsize = qsize + tmp
|
163
|
+
end
|
164
|
+
total = qsize
|
165
|
+
|
166
|
+
if total == 0
|
167
|
+
ending = Time.now - @start
|
168
|
+
size = @iter * @count * queues.length()
|
169
|
+
Sidekiq.logger.error("Done, #{size} jobs in #{ending} sec, #{(size / ending).to_i} jobs/sec")
|
170
|
+
Sidekiq.logger.error("Ending RSS: #{Process.rss}")
|
171
|
+
|
172
|
+
sleep 0.1
|
173
|
+
@x.stop
|
174
|
+
Process.kill("INT", $$)
|
175
|
+
break
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def run(queues, queue, monitor_all_queues)
|
182
|
+
Sidekiq.logger.warn("Consuming from #{queue}")
|
183
|
+
if monitor_all_queues
|
184
|
+
monitor_all(queues)
|
185
|
+
else
|
186
|
+
monitor_single(queue)
|
187
|
+
end
|
188
|
+
|
189
|
+
@start = Time.now
|
190
|
+
@x.run
|
191
|
+
|
192
|
+
while (readable_io = IO.select([@self_read]))
|
193
|
+
signal = readable_io.first[0].gets.strip
|
194
|
+
handle_signal(signal)
|
195
|
+
end
|
196
|
+
# normal
|
197
|
+
rescue Interrupt
|
198
|
+
rescue => e
|
199
|
+
raise e if $DEBUG
|
200
|
+
warn e.message
|
201
|
+
warn e.backtrace.join("\n")
|
202
|
+
exit 1
|
203
|
+
ensure
|
204
|
+
@x.stop
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def setup(queue)
|
209
|
+
ll = Loader.new
|
210
|
+
ll.configure(queue)
|
211
|
+
ll.setup(queue)
|
212
|
+
end
|
213
|
+
|
214
|
+
def consume(queues, queue, monitor_all_queues)
|
215
|
+
ll = Loader.new
|
216
|
+
ll.configure(queue)
|
217
|
+
ll.run(queues, queue, monitor_all_queues)
|
218
|
+
end
|
219
|
+
|
220
|
+
# We assign one queue to each sidekiq process
|
221
|
+
def run(number_of_processes, total_queues)
|
222
|
+
read_stream, write_stream = IO.pipe
|
223
|
+
|
224
|
+
queues = []
|
225
|
+
(0..total_queues-1).each do |idx|
|
226
|
+
queues.push("queue:q#{idx}")
|
227
|
+
end
|
228
|
+
|
229
|
+
Sidekiq.logger.info("Queues are: #{queues}")
|
230
|
+
|
231
|
+
# Produce
|
232
|
+
start = Time.now
|
233
|
+
(0..total_queues-1).each do |idx|
|
234
|
+
Process.fork do
|
235
|
+
queue_num = "q#{idx}"
|
236
|
+
setup(queue_num)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
queue_sz = $iterations * $elements * total_queues
|
241
|
+
Process.waitall
|
242
|
+
|
243
|
+
ending = Time.now - start
|
244
|
+
#Sidekiq.logger.info("Pushed #{queue_sz} in #{ending} secs")
|
245
|
+
|
246
|
+
# Consume
|
247
|
+
(0..number_of_processes-1).each do |idx|
|
248
|
+
Process.fork do
|
249
|
+
# First process only consumes from it's own queue but monitors all queues.
|
250
|
+
# It works as a synchronization point. Once all processes finish
|
251
|
+
# (that is, when all queues are emptied) it prints the the stats.
|
252
|
+
if idx == 0
|
253
|
+
queue = "q#{idx}"
|
254
|
+
consume(queues, queue, true)
|
255
|
+
else
|
256
|
+
queue = "q#{idx % total_queues}"
|
257
|
+
consume(queues, queue, false)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
Process.waitall
|
263
|
+
write_stream.close
|
264
|
+
results = read_stream.read
|
265
|
+
read_stream.close
|
266
|
+
end
|
267
|
+
|
268
|
+
$total_processes = ENV["PROCESSES"] ? Integer(ENV["PROCESSES"]) : 8;
|
269
|
+
$total_queues = ENV["QUEUES"] ? Integer(ENV["QUEUES"]) : 8;
|
270
|
+
|
271
|
+
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
|
-
|
495
|
-
uncompressed = Zlib::Inflate.inflate(
|
493
|
+
strict_base64_decoded = backtrace.unpack1("m")
|
494
|
+
uncompressed = Zlib::Inflate.inflate(strict_base64_decoded)
|
496
495
|
Sidekiq.load_json(uncompressed)
|
497
496
|
end
|
498
497
|
end
|
@@ -774,7 +773,7 @@ module Sidekiq
|
|
774
773
|
#
|
775
774
|
class ScheduledSet < JobSet
|
776
775
|
def initialize
|
777
|
-
super
|
776
|
+
super("schedule")
|
778
777
|
end
|
779
778
|
end
|
780
779
|
|
@@ -788,7 +787,7 @@ module Sidekiq
|
|
788
787
|
#
|
789
788
|
class RetrySet < JobSet
|
790
789
|
def initialize
|
791
|
-
super
|
790
|
+
super("retry")
|
792
791
|
end
|
793
792
|
|
794
793
|
# Enqueues all jobs pending within the retry set.
|
@@ -809,7 +808,7 @@ module Sidekiq
|
|
809
808
|
#
|
810
809
|
class DeadSet < JobSet
|
811
810
|
def initialize
|
812
|
-
super
|
811
|
+
super("dead")
|
813
812
|
end
|
814
813
|
|
815
814
|
# Add the given job to the Dead set.
|
@@ -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
|
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,
|
@@ -1137,7 +1136,74 @@ module Sidekiq
|
|
1137
1136
|
end
|
1138
1137
|
end
|
1139
1138
|
end
|
1139
|
+
|
1140
|
+
##
|
1141
|
+
# Find the work which represents a job with the given JID.
|
1142
|
+
# *This is a slow O(n) operation*. Do not use for app logic.
|
1143
|
+
#
|
1144
|
+
# @param jid [String] the job identifier
|
1145
|
+
# @return [Sidekiq::Work] the work or nil
|
1146
|
+
def find_work_by_jid(jid)
|
1147
|
+
each do |_process_id, _thread_id, work|
|
1148
|
+
job = work.job
|
1149
|
+
return work if job.jid == jid
|
1150
|
+
end
|
1151
|
+
nil
|
1152
|
+
end
|
1140
1153
|
end
|
1154
|
+
|
1155
|
+
# Sidekiq::Work represents a job which is currently executing.
|
1156
|
+
class Work
|
1157
|
+
attr_reader :process_id
|
1158
|
+
attr_reader :thread_id
|
1159
|
+
|
1160
|
+
def initialize(pid, tid, hsh)
|
1161
|
+
@process_id = pid
|
1162
|
+
@thread_id = tid
|
1163
|
+
@hsh = hsh
|
1164
|
+
@job = nil
|
1165
|
+
end
|
1166
|
+
|
1167
|
+
def queue
|
1168
|
+
@hsh["queue"]
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
def run_at
|
1172
|
+
Time.at(@hsh["run_at"])
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
def job
|
1176
|
+
@job ||= Sidekiq::JobRecord.new(@hsh["payload"])
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
def payload
|
1180
|
+
@hsh["payload"]
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
# deprecated
|
1184
|
+
def [](key)
|
1185
|
+
kwargs = {uplevel: 1}
|
1186
|
+
kwargs[:category] = :deprecated if RUBY_VERSION > "3.0" # TODO
|
1187
|
+
warn("Direct access to `Sidekiq::Work` attributes is deprecated, please use `#payload`, `#queue`, `#run_at` or `#job` instead", **kwargs)
|
1188
|
+
|
1189
|
+
@hsh[key]
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
# :nodoc:
|
1193
|
+
# @api private
|
1194
|
+
def raw(name)
|
1195
|
+
@hsh[name]
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
def method_missing(*all)
|
1199
|
+
@hsh.send(*all)
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
def respond_to_missing?(name)
|
1203
|
+
@hsh.respond_to?(name)
|
1204
|
+
end
|
1205
|
+
end
|
1206
|
+
|
1141
1207
|
# Since "worker" is a nebulous term, we've deprecated the use of this class name.
|
1142
1208
|
# Is "worker" a process, a type of job, a thread? Undefined!
|
1143
1209
|
# WorkSet better describes the data.
|
data/lib/sidekiq/cli.rb
CHANGED
@@ -38,7 +38,7 @@ module Sidekiq # :nodoc:
|
|
38
38
|
# Code within this method is not tested because it alters
|
39
39
|
# global process state irreversibly. PRs which improve the
|
40
40
|
# test coverage of Sidekiq::CLI are welcomed.
|
41
|
-
def run(boot_app: true)
|
41
|
+
def run(boot_app: true, warmup: true)
|
42
42
|
boot_application if boot_app
|
43
43
|
|
44
44
|
if environment == "development" && $stdout.tty? && @config.logger.formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
|
@@ -101,6 +101,8 @@ module Sidekiq # :nodoc:
|
|
101
101
|
# Touch middleware so it isn't lazy loaded by multiple threads, #3043
|
102
102
|
@config.server_middleware
|
103
103
|
|
104
|
+
::Process.warmup if warmup && ::Process.respond_to?(:warmup)
|
105
|
+
|
104
106
|
# Before this point, the process is initializing with just the main thread.
|
105
107
|
# Starting here the process will now have multiple threads running.
|
106
108
|
fire_event(:startup, reverse: false, reraise: true)
|
data/lib/sidekiq/deploy.rb
CHANGED
@@ -34,7 +34,7 @@ module Sidekiq
|
|
34
34
|
# handle an very common error in marking deploys:
|
35
35
|
# having every process mark its deploy, leading
|
36
36
|
# to N marks for each deploy. Instead we round the time
|
37
|
-
# to the minute so that
|
37
|
+
# to the minute so that multiple marks within that minute
|
38
38
|
# will all naturally rollup into one mark per minute.
|
39
39
|
whence = at.utc
|
40
40
|
floor = Time.utc(whence.year, whence.month, whence.mday, whence.hour, whence.min, 0)
|
data/lib/sidekiq/job.rb
CHANGED
@@ -109,7 +109,7 @@ module Sidekiq
|
|
109
109
|
m = "#{name}="
|
110
110
|
undef_method(m) if method_defined?(m) || private_method_defined?(m)
|
111
111
|
end
|
112
|
-
define_singleton_method("#{name}=") do |val|
|
112
|
+
define_singleton_method(:"#{name}=") do |val|
|
113
113
|
singleton_class.class_eval do
|
114
114
|
ACCESSOR_MUTEX.synchronize do
|
115
115
|
undef_method(synchronized_getter) if method_defined?(synchronized_getter) || private_method_defined?(synchronized_getter)
|
data/lib/sidekiq/job_retry.rb
CHANGED
@@ -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
|
-
|
297
|
+
[compressed].pack("m0") # Base64.strict_encode64
|
299
298
|
end
|
300
299
|
end
|
301
300
|
end
|
data/lib/sidekiq/launcher.rb
CHANGED
@@ -145,15 +145,17 @@ module Sidekiq
|
|
145
145
|
flush_stats
|
146
146
|
|
147
147
|
curstate = Processor::WORK_STATE.dup
|
148
|
+
curstate.transform_values! { |val| Sidekiq.dump_json(val) }
|
149
|
+
|
148
150
|
redis do |conn|
|
149
151
|
# work is the current set of executing jobs
|
150
152
|
work_key = "#{key}:work"
|
151
|
-
conn.
|
153
|
+
conn.multi do |transaction|
|
152
154
|
transaction.unlink(work_key)
|
153
|
-
curstate.
|
154
|
-
transaction.hset(work_key,
|
155
|
+
if curstate.size > 0
|
156
|
+
transaction.hset(work_key, curstate)
|
157
|
+
transaction.expire(work_key, 60)
|
155
158
|
end
|
156
|
-
transaction.expire(work_key, 60)
|
157
159
|
end
|
158
160
|
end
|
159
161
|
|
data/lib/sidekiq/logger.rb
CHANGED
@@ -103,12 +103,16 @@ module Sidekiq
|
|
103
103
|
def reset
|
104
104
|
@lock.synchronize {
|
105
105
|
array = [@totals, @jobs, @grams]
|
106
|
-
|
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/processor.rb
CHANGED
@@ -187,7 +187,7 @@ module Sidekiq
|
|
187
187
|
# we didn't properly finish it.
|
188
188
|
rescue Sidekiq::JobRetry::Handled => h
|
189
189
|
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
190
|
-
# signals that we created a retry successfully. We can
|
190
|
+
# signals that we created a retry successfully. We can acknowledge the job.
|
191
191
|
ack = true
|
192
192
|
e = h.cause || h
|
193
193
|
handle_exception(e, {context: "Job raised exception", job: job_hash})
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -20,6 +20,10 @@ module Sidekiq
|
|
20
20
|
def inspect
|
21
21
|
"#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
|
22
22
|
end
|
23
|
+
|
24
|
+
def to_hash
|
25
|
+
{app: @app.class.name}
|
26
|
+
end
|
23
27
|
end
|
24
28
|
|
25
29
|
# By including the Options module, we allow AJs to directly control sidekiq features
|
@@ -56,10 +60,10 @@ module Sidekiq
|
|
56
60
|
# This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
|
57
61
|
# it will appear in the Sidekiq console with all of the job context.
|
58
62
|
unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
59
|
-
if ::Rails
|
60
|
-
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
61
|
-
else
|
63
|
+
if ::Rails.logger.respond_to?(:broadcast_to)
|
62
64
|
::Rails.logger.broadcast_to(config.logger)
|
65
|
+
else
|
66
|
+
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
63
67
|
end
|
64
68
|
end
|
65
69
|
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
|
-
|
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
|
@@ -67,9 +66,7 @@ module Sidekiq
|
|
67
66
|
EOM
|
68
67
|
end
|
69
68
|
|
70
|
-
ENV[
|
71
|
-
p || "REDIS_URL"
|
72
|
-
]
|
69
|
+
ENV[p.to_s] || ENV["REDIS_URL"]
|
73
70
|
end
|
74
71
|
end
|
75
72
|
end
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -144,7 +144,7 @@ module Sidekiq
|
|
144
144
|
# In the example above, each process should schedule every 10 seconds on average. We special
|
145
145
|
# case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds.
|
146
146
|
# As we run more processes, the scheduling interval average will approach an even spread
|
147
|
-
# between 0 and poll interval so we don't need this
|
147
|
+
# between 0 and poll interval so we don't need this artificial boost.
|
148
148
|
#
|
149
149
|
count = process_count
|
150
150
|
interval = poll_interval_average(count)
|
data/lib/sidekiq/testing.rb
CHANGED
@@ -112,7 +112,7 @@ module Sidekiq
|
|
112
112
|
# The Queues class is only for testing the fake queue implementation.
|
113
113
|
# There are 2 data structures involved in tandem. This is due to the
|
114
114
|
# Rspec syntax of change(HardJob.jobs, :size). It keeps a reference
|
115
|
-
# to the array. Because the array was
|
115
|
+
# to the array. Because the array was derived from a filter of the total
|
116
116
|
# jobs enqueued, it appeared as though the array didn't change.
|
117
117
|
#
|
118
118
|
# To solve this, we'll keep 2 hashes containing the jobs. One with keys based
|
@@ -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
|
|
@@ -9,7 +9,14 @@ module Sidekiq
|
|
9
9
|
@redis_client = Client.new(pool: pool, config: config)
|
10
10
|
end
|
11
11
|
|
12
|
+
def batching?
|
13
|
+
Thread.current[:sidekiq_batch]
|
14
|
+
end
|
15
|
+
|
12
16
|
def push(item)
|
17
|
+
# 6160 we can't support both Sidekiq::Batch and transactions.
|
18
|
+
return @redis_client.push(item) if batching?
|
19
|
+
|
13
20
|
# pre-allocate the JID so we can return it immediately and
|
14
21
|
# save it to the database as part of the transaction.
|
15
22
|
item["jid"] ||= SecureRandom.hex(12)
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
@@ -22,6 +22,11 @@ module Sidekiq
|
|
22
22
|
throw :halt, [302, {Web::LOCATION => "#{request.base_url}#{location}"}, []]
|
23
23
|
end
|
24
24
|
|
25
|
+
def reload_page
|
26
|
+
current_location = request.referer.gsub(request.base_url, "")
|
27
|
+
redirect current_location
|
28
|
+
end
|
29
|
+
|
25
30
|
def params
|
26
31
|
indifferent_hash = Hash.new { |hash, key| hash[key.to_s] if Symbol === key }
|
27
32
|
|
@@ -49,9 +49,9 @@ module Sidekiq
|
|
49
49
|
|
50
50
|
head "/" do
|
51
51
|
# HEAD / is the cheapest heartbeat possible,
|
52
|
-
# it hits Redis to ensure connectivity
|
53
|
-
|
54
|
-
""
|
52
|
+
# it hits Redis to ensure connectivity and returns
|
53
|
+
# the size of the default queue
|
54
|
+
Sidekiq.redis { |c| c.llen("queue:default") }.to_s
|
55
55
|
end
|
56
56
|
|
57
57
|
get "/" do
|
@@ -394,6 +394,18 @@ module Sidekiq
|
|
394
394
|
erb :morgue
|
395
395
|
end
|
396
396
|
|
397
|
+
post "/change_locale" do
|
398
|
+
locale = params["locale"]
|
399
|
+
|
400
|
+
match = available_locales.find { |available|
|
401
|
+
locale == available
|
402
|
+
}
|
403
|
+
|
404
|
+
session[:locale] = match if match
|
405
|
+
|
406
|
+
reload_page
|
407
|
+
end
|
408
|
+
|
397
409
|
def call(env)
|
398
410
|
action = self.class.match(env)
|
399
411
|
return [404, {Rack::CONTENT_TYPE => "text/plain", Web::X_CASCADE => "pass"}, ["Not Found"]] unless action
|
@@ -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
|
@@ -57,7 +56,7 @@ module Sidekiq
|
|
57
56
|
end
|
58
57
|
|
59
58
|
def logger(env)
|
60
|
-
@logger ||=
|
59
|
+
@logger ||= env["rack.logger"] || ::Logger.new(env["rack.errors"])
|
61
60
|
end
|
62
61
|
|
63
62
|
def deny(env)
|
@@ -116,7 +115,7 @@ module Sidekiq
|
|
116
115
|
sess = session(env)
|
117
116
|
localtoken = sess[:csrf]
|
118
117
|
|
119
|
-
# Checks that Rack::Session::Cookie
|
118
|
+
# Checks that Rack::Session::Cookie actually contains the csrf token
|
120
119
|
return false if localtoken.nil?
|
121
120
|
|
122
121
|
# Rotate the session token after every use
|
@@ -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
|
-
|
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
|
-
|
175
|
+
token.tr("-_", "+/").unpack1("m0")
|
173
176
|
end
|
174
177
|
|
175
178
|
def xor_byte_strings(s1, s2)
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -121,6 +121,10 @@ module Sidekiq
|
|
121
121
|
#
|
122
122
|
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
123
123
|
def locale
|
124
|
+
# session[:locale] is set via the locale selector from the footer
|
125
|
+
# defined?(session) && session are used to avoid exceptions when running tests
|
126
|
+
return session[:locale] if defined?(session) && session&.[](:locale)
|
127
|
+
|
124
128
|
@locale ||= begin
|
125
129
|
matched_locale = user_preferred_languages.map { |preferred|
|
126
130
|
preferred_language = preferred.split("-", 2).first
|
@@ -340,7 +344,8 @@ module Sidekiq
|
|
340
344
|
end
|
341
345
|
|
342
346
|
def pollable?
|
343
|
-
|
347
|
+
# there's no point to refreshing the metrics pages every N seconds
|
348
|
+
!(current_path == "" || current_path.index("metrics"))
|
344
349
|
end
|
345
350
|
|
346
351
|
def retry_or_delete_or_kill(job, params)
|
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.
|
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"
|
@@ -47,6 +47,8 @@ function addListeners() {
|
|
47
47
|
scheduleLivePoll();
|
48
48
|
}
|
49
49
|
}
|
50
|
+
|
51
|
+
document.getElementById("locale-select").addEventListener("change", updateLocale);
|
50
52
|
}
|
51
53
|
|
52
54
|
function addPollingListeners(_event) {
|
@@ -175,3 +177,7 @@ function replacePage(text) {
|
|
175
177
|
function showError(error) {
|
176
178
|
console.error(error)
|
177
179
|
}
|
180
|
+
|
181
|
+
function updateLocale(event) {
|
182
|
+
event.target.form.submit();
|
183
|
+
};
|
@@ -731,3 +731,16 @@ div.interval-slider input {
|
|
731
731
|
canvas {
|
732
732
|
margin: 20px 0 30px;
|
733
733
|
}
|
734
|
+
|
735
|
+
#locale-select {
|
736
|
+
float: left;
|
737
|
+
margin: 8px 15px;
|
738
|
+
}
|
739
|
+
|
740
|
+
@media (max-width: 767px) {
|
741
|
+
#locale-select {
|
742
|
+
float: none;
|
743
|
+
width: auto;
|
744
|
+
margin: 15px auto;
|
745
|
+
}
|
746
|
+
}
|
data/web/views/_footer.erb
CHANGED
@@ -15,7 +15,19 @@
|
|
15
15
|
<p class="navbar-text"><a rel=help href="https://github.com/sidekiq/sidekiq/wiki">docs</a></p>
|
16
16
|
</li>
|
17
17
|
<li>
|
18
|
-
<
|
18
|
+
<form id="locale-form" class="form-inline" action="<%= root_path %>change_locale" method="post">
|
19
|
+
<%= csrf_tag %>
|
20
|
+
<label class="sr-only" for="locale">Language</label>
|
21
|
+
<select id="locale-select" class="form-control" name="locale">
|
22
|
+
<% available_locales.each do |locale_option| %>
|
23
|
+
<% if locale_option == locale %>
|
24
|
+
<option selected value="<%= locale_option %>"><%= locale_option %></option>
|
25
|
+
<% else %>
|
26
|
+
<option value="<%= locale_option %>"><%= locale_option %></option>
|
27
|
+
<% end %>
|
28
|
+
<% end %>
|
29
|
+
</select>
|
30
|
+
</form>
|
19
31
|
</li>
|
20
32
|
</ul>
|
21
33
|
</div>
|
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,
|
129
|
-
<% job =
|
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/<%=
|
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(
|
144
|
+
<td><%= relative_time(work.run_at) %></td>
|
145
145
|
</tr>
|
146
146
|
<% end %>
|
147
147
|
</table>
|
data/web/views/metrics.erb
CHANGED
@@ -12,7 +12,7 @@
|
|
12
12
|
<form id="metrics-form" class="form-inline" action="<%= root_path %>filter/metrics" method="post">
|
13
13
|
<%= csrf_tag %>
|
14
14
|
<label for="substr"><%= t('Filter') %></label>
|
15
|
-
<input id="class-filter" class="form-control" type="text" name="substr" placeholder="<%= t('Name') %>" value="<%= params[:substr] %>">
|
15
|
+
<input id="class-filter" class="form-control" type="text" name="substr" placeholder="<%= t('Name') %>" value="<%= h params[:substr] %>">
|
16
16
|
<select id="period-selector" class="form-control" name="period">
|
17
17
|
<% @periods.each_key do |code| %>
|
18
18
|
<% if code == @period %>
|
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.
|
4
|
+
version: 7.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Perham
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-04-26 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.
|
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.
|
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
|