sidekiq 7.1.6 → 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 +68 -0
- data/README.md +2 -2
- data/bin/multi_queue_bench +271 -0
- data/lib/sidekiq/api.rb +77 -11
- data/lib/sidekiq/cli.rb +3 -1
- data/lib/sidekiq/config.rb +4 -4
- data/lib/sidekiq/deploy.rb +2 -2
- data/lib/sidekiq/job.rb +1 -1
- data/lib/sidekiq/job_retry.rb +3 -3
- data/lib/sidekiq/launcher.rb +6 -4
- data/lib/sidekiq/logger.rb +1 -1
- data/lib/sidekiq/metrics/query.rb +4 -1
- data/lib/sidekiq/metrics/tracking.rb +7 -3
- data/lib/sidekiq/middleware/current_attributes.rb +1 -1
- data/lib/sidekiq/paginator.rb +2 -2
- data/lib/sidekiq/processor.rb +1 -1
- data/lib/sidekiq/rails.rb +7 -3
- data/lib/sidekiq/redis_client_adapter.rb +16 -0
- data/lib/sidekiq/redis_connection.rb +3 -6
- data/lib/sidekiq/scheduled.rb +2 -2
- data/lib/sidekiq/testing.rb +9 -3
- 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 +32 -4
- data/lib/sidekiq/web/csrf_protection.rb +8 -5
- data/lib/sidekiq/web/helpers.rb +14 -15
- data/sidekiq.gemspec +1 -1
- data/web/assets/javascripts/application.js +21 -0
- data/web/assets/javascripts/dashboard-charts.js +14 -0
- data/web/assets/javascripts/dashboard.js +7 -9
- data/web/assets/javascripts/metrics.js +34 -0
- data/web/assets/stylesheets/application-rtl.css +10 -0
- data/web/assets/stylesheets/application.css +22 -0
- data/web/views/_footer.erb +13 -1
- data/web/views/_metrics_period_select.erb +1 -1
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +7 -7
- data/web/views/dashboard.erb +23 -33
- data/web/views/filtering.erb +4 -4
- data/web/views/metrics.erb +36 -27
- data/web/views/metrics_for_job.erb +26 -35
- data/web/views/queues.erb +6 -2
- 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,69 @@
|
|
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
|
+
|
38
|
+
7.2.0
|
39
|
+
----------
|
40
|
+
|
41
|
+
- `sidekiq_retries_exhausted` can return `:discard` to avoid the deadset
|
42
|
+
and all death handlers [#6091]
|
43
|
+
- Metrics filtering by job class in Web UI [#5974]
|
44
|
+
- Better readability and formatting for numbers within the Web UI [#6080]
|
45
|
+
- Add explicit error if user code tries to nest test modes [#6078]
|
46
|
+
```ruby
|
47
|
+
Sidekiq::Testing.inline! # global setting
|
48
|
+
Sidekiq::Testing.fake! do # override within block
|
49
|
+
# ok
|
50
|
+
Sidekiq::Testing.inline! do # can't override the override
|
51
|
+
# not ok, nested
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
- **SECURITY** Forbid inline JavaScript execution in Web UI [#6074]
|
56
|
+
- Adjust redis-client adapter to avoid `method_missing` [#6083]
|
57
|
+
This can result in app code breaking if your app's Redis API usage was
|
58
|
+
depending on Sidekiq's adapter to correct invalid redis-client API usage.
|
59
|
+
One example:
|
60
|
+
```ruby
|
61
|
+
# bad, not redis-client native
|
62
|
+
# Unsupported command argument type: TrueClass (TypeError)
|
63
|
+
Sidekiq.redis { |c| c.set("key", "value", nx: true, ex: 15) }
|
64
|
+
# good
|
65
|
+
Sidekiq.redis { |c| c.set("key", "value", "nx", "ex", 15) }
|
66
|
+
```
|
67
|
+
|
5
68
|
7.1.6
|
6
69
|
----------
|
7
70
|
|
@@ -150,6 +213,11 @@ end
|
|
150
213
|
- Job Execution metrics!!!
|
151
214
|
- See `docs/7.0-Upgrade.md` for release notes
|
152
215
|
|
216
|
+
6.5.{10,11,12}
|
217
|
+
----------
|
218
|
+
|
219
|
+
- Fixes for Rails 7.1 [#6067, #6070]
|
220
|
+
|
153
221
|
6.5.9
|
154
222
|
----------
|
155
223
|
|
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
|
@@ -679,7 +678,7 @@ module Sidekiq
|
|
679
678
|
range_start = page * page_size + offset_size
|
680
679
|
range_end = range_start + page_size - 1
|
681
680
|
elements = Sidekiq.redis { |conn|
|
682
|
-
conn.zrange name, range_start, range_end, withscores
|
681
|
+
conn.zrange name, range_start, range_end, "withscores"
|
683
682
|
}
|
684
683
|
break if elements.empty?
|
685
684
|
page -= 1
|
@@ -706,7 +705,7 @@ module Sidekiq
|
|
706
705
|
end
|
707
706
|
|
708
707
|
elements = Sidekiq.redis { |conn|
|
709
|
-
conn.zrange(name, begin_score, end_score, "BYSCORE", withscores
|
708
|
+
conn.zrange(name, begin_score, end_score, "BYSCORE", "withscores")
|
710
709
|
}
|
711
710
|
|
712
711
|
elements.each_with_object([]) do |element, result|
|
@@ -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.
|
@@ -881,7 +880,7 @@ module Sidekiq
|
|
881
880
|
# @api private
|
882
881
|
def cleanup
|
883
882
|
# dont run cleanup more than once per minute
|
884
|
-
return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1",
|
883
|
+
return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1", "NX", "EX", "60") }
|
885
884
|
|
886
885
|
count = 0
|
887
886
|
Sidekiq.redis do |conn|
|
@@ -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/config.rb
CHANGED
@@ -258,9 +258,9 @@ module Sidekiq
|
|
258
258
|
@logger = logger
|
259
259
|
end
|
260
260
|
|
261
|
-
private def
|
262
|
-
|
263
|
-
|
261
|
+
private def parameter_size(handler)
|
262
|
+
target = handler.is_a?(Proc) ? handler : handler.method(:call)
|
263
|
+
target.parameters.size
|
264
264
|
end
|
265
265
|
|
266
266
|
# INTERNAL USE ONLY
|
@@ -269,7 +269,7 @@ module Sidekiq
|
|
269
269
|
p ["!!!!!", ex]
|
270
270
|
end
|
271
271
|
@options[:error_handlers].each do |handler|
|
272
|
-
if
|
272
|
+
if parameter_size(handler) == 2
|
273
273
|
# TODO Remove in 8.0
|
274
274
|
logger.info { "DEPRECATION: Sidekiq exception handlers now take three arguments, see #{handler}" }
|
275
275
|
handler.call(ex, {_config: self}.merge(ctx))
|
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)
|
@@ -44,7 +44,7 @@ module Sidekiq
|
|
44
44
|
|
45
45
|
@pool.with do |c|
|
46
46
|
# only allow one deploy mark for a given label for the next minute
|
47
|
-
lock = c.set("deploylock-#{label}", stamp, nx
|
47
|
+
lock = c.set("deploylock-#{label}", stamp, "nx", "ex", "60")
|
48
48
|
if lock
|
49
49
|
c.multi do |pipe|
|
50
50
|
pipe.hsetnx(key, stamp, label)
|
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
|
@@ -226,7 +225,7 @@ module Sidekiq
|
|
226
225
|
end
|
227
226
|
|
228
227
|
def retries_exhausted(jobinst, msg, exception)
|
229
|
-
begin
|
228
|
+
rv = begin
|
230
229
|
block = jobinst&.sidekiq_retries_exhausted_block
|
231
230
|
|
232
231
|
# the sidekiq_retries_exhausted_block can be defined in a wrapped class (ActiveJob for instance)
|
@@ -239,6 +238,7 @@ module Sidekiq
|
|
239
238
|
handle_exception(e, {context: "Error calling retries_exhausted", job: msg})
|
240
239
|
end
|
241
240
|
|
241
|
+
return if rv == :discard # poof!
|
242
242
|
send_to_morgue(msg) unless msg["dead"] == false
|
243
243
|
|
244
244
|
@capsule.config.death_handlers.each do |handler|
|
@@ -294,7 +294,7 @@ module Sidekiq
|
|
294
294
|
def compress_backtrace(backtrace)
|
295
295
|
serialized = Sidekiq.dump_json(backtrace)
|
296
296
|
compressed = Zlib::Deflate.deflate(serialized)
|
297
|
-
|
297
|
+
[compressed].pack("m0") # Base64.strict_encode64
|
298
298
|
end
|
299
299
|
end
|
300
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
|
|