sidekiq 7.1.6 → 7.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|