delayed 2.1.0 → 2.2.0
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/app/models/delayed/job.rb +26 -10
- data/lib/delayed/backend/job_preparer.rb +13 -0
- data/lib/delayed/exceptions.rb +2 -0
- data/lib/delayed/monitor.rb +69 -43
- data/lib/delayed/version.rb +1 -1
- data/lib/delayed/worker.rb +2 -0
- data/spec/delayed/__snapshots__/monitor_spec.rb.snap +447 -1170
- data/spec/delayed/job_spec.rb +37 -0
- data/spec/delayed/monitor_spec.rb +17 -19
- data/spec/helper.rb +9 -6
- metadata +2 -2
data/spec/delayed/job_spec.rb
CHANGED
|
@@ -13,6 +13,7 @@ describe Delayed::Job do
|
|
|
13
13
|
Delayed::Worker.max_claims = 1 # disable multithreading because SimpleJob is not threadsafe
|
|
14
14
|
Delayed::Worker.default_priority = 99
|
|
15
15
|
Delayed::Worker.delay_jobs = true
|
|
16
|
+
Delayed::Worker.deny_stale_enqueues = false
|
|
16
17
|
Delayed::Worker.default_queue_name = 'default_tracking'
|
|
17
18
|
SimpleJob.runs = 0
|
|
18
19
|
described_class.delete_all
|
|
@@ -133,6 +134,42 @@ describe Delayed::Job do
|
|
|
133
134
|
expect(described_class.enqueue(SimpleJob.new)).to be_instance_of(described_class)
|
|
134
135
|
end
|
|
135
136
|
end
|
|
137
|
+
|
|
138
|
+
context 'with deny_stale_enqueues = true' do
|
|
139
|
+
before { Delayed::Worker.deny_stale_enqueues = true }
|
|
140
|
+
|
|
141
|
+
it 'raises StaleEnqueueError when run_at is beyond lock_timeout in the past' do
|
|
142
|
+
stale_time = described_class.db_time_now - described_class.lock_timeout - 1.minute
|
|
143
|
+
expect {
|
|
144
|
+
described_class.enqueue SimpleJob.new, run_at: stale_time
|
|
145
|
+
}.to raise_error(Delayed::StaleEnqueueError, /Cannot enqueue a job in the distant past/)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'allows run_at within lock_timeout of now' do
|
|
149
|
+
recent_past = described_class.db_time_now - described_class.lock_timeout + 1.minute
|
|
150
|
+
job = described_class.enqueue SimpleJob.new, run_at: recent_past
|
|
151
|
+
expect(job).to be_persisted
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'allows run_at in the future' do
|
|
155
|
+
future = described_class.db_time_now + 5.minutes
|
|
156
|
+
job = described_class.enqueue SimpleJob.new, run_at: future
|
|
157
|
+
expect(job).to be_persisted
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'allows enqueue without run_at' do
|
|
161
|
+
job = described_class.enqueue SimpleJob.new
|
|
162
|
+
expect(job).to be_persisted
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
context 'with deny_stale_enqueues = false (default)' do
|
|
167
|
+
it 'allows run_at far in the past' do
|
|
168
|
+
stale_time = described_class.db_time_now - 1.day
|
|
169
|
+
job = described_class.enqueue SimpleJob.new, run_at: stale_time
|
|
170
|
+
expect(job).to be_persisted
|
|
171
|
+
end
|
|
172
|
+
end
|
|
136
173
|
end
|
|
137
174
|
|
|
138
175
|
describe 'callbacks' do
|
|
@@ -345,7 +345,7 @@ RSpec.describe Delayed::Monitor do
|
|
|
345
345
|
end
|
|
346
346
|
end
|
|
347
347
|
|
|
348
|
-
describe '
|
|
348
|
+
describe 'SQL' do
|
|
349
349
|
let(:monitor) { described_class.new }
|
|
350
350
|
let(:queries) { [] }
|
|
351
351
|
let(:now) { '2025-11-10 17:20:13 UTC' }
|
|
@@ -360,30 +360,28 @@ RSpec.describe Delayed::Monitor do
|
|
|
360
360
|
value = value.value if value.is_a?(ActiveModel::Attribute)
|
|
361
361
|
sql = sql.sub(/(\?|\$\d)/, ActiveRecord::Base.connection.quote(value))
|
|
362
362
|
end
|
|
363
|
-
queries << sql
|
|
363
|
+
queries << QueryUnderTest.for(sql)
|
|
364
|
+
queries << "---"
|
|
364
365
|
end
|
|
365
366
|
end
|
|
366
367
|
|
|
367
|
-
def
|
|
368
|
-
|
|
369
|
-
|
|
368
|
+
def query_descriptions
|
|
369
|
+
described_class::METRICS.each do |metric|
|
|
370
|
+
queries << "-- QUERIES FOR `#{metric}`:"
|
|
371
|
+
queries << "---------------------------------"
|
|
372
|
+
monitor.query_for(metric)
|
|
373
|
+
queries << "-- (no new queries)" unless queries.last == '---'
|
|
374
|
+
end
|
|
375
|
+
queries.dup.map { |query| query.try(:full_description) || query }
|
|
370
376
|
end
|
|
371
377
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
expect(queries_for(metric).map(&:formatted).join("\n")).to match_snapshot
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
it "produces the expected #{current_adapter} query plan for #{metric}" do
|
|
379
|
-
expect(queries_for(metric).map(&:explain).join("\n")).to match_snapshot
|
|
380
|
-
end
|
|
378
|
+
it "runs the expected #{current_adapter} queries with the expected plans" do
|
|
379
|
+
expect(query_descriptions.join("\n")).to match_snapshot
|
|
380
|
+
end
|
|
381
381
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
end
|
|
386
|
-
end
|
|
382
|
+
context 'when using the legacy index', :with_legacy_table_index do
|
|
383
|
+
it "[legacy index] runs the expected #{current_adapter} queries with the expected plans" do
|
|
384
|
+
expect(query_descriptions.join("\n")).to match_snapshot
|
|
387
385
|
end
|
|
388
386
|
end
|
|
389
387
|
end
|
data/spec/helper.rb
CHANGED
|
@@ -228,10 +228,7 @@ RSpec::Matchers.define :emit_notification do |expected_event_name|
|
|
|
228
228
|
diffable
|
|
229
229
|
|
|
230
230
|
match do |block|
|
|
231
|
-
|
|
232
|
-
@expected_value = a_value_within([2, @expected_value.abs * 0.05].max).of(@expected_value)
|
|
233
|
-
end
|
|
234
|
-
|
|
231
|
+
@expected_value = a_value_within([2, @expected_value.abs * 0.05].max).of(@expected_value) if @approximately
|
|
235
232
|
@expected = { event_name: expected_event_name, payload: expected_payload, value: @expected_value }
|
|
236
233
|
@actuals = []
|
|
237
234
|
callback = ->(name, _started, _finished, _unique_id, payload) do
|
|
@@ -274,6 +271,10 @@ QueryUnderTest = Struct.new(:sql, :connection) do
|
|
|
274
271
|
new(query.respond_to?(:to_sql) ? query.to_sql : query.to_s, connection)
|
|
275
272
|
end
|
|
276
273
|
|
|
274
|
+
def full_description
|
|
275
|
+
[formatted, explain].join("\n\n")
|
|
276
|
+
end
|
|
277
|
+
|
|
277
278
|
def formatted
|
|
278
279
|
fmt = sql.squish
|
|
279
280
|
|
|
@@ -287,6 +288,8 @@ QueryUnderTest = Struct.new(:sql, :connection) do
|
|
|
287
288
|
.gsub(/ (AND|OR) /) { "\n #{Regexp.last_match(1).strip} " }
|
|
288
289
|
# normalize and truncate 'AS' names/aliases (changes across Rails versions)
|
|
289
290
|
.gsub(/AS ("|`)?(\w+)("|`)?/) { "AS #{Regexp.last_match(2)[0...63]}" }
|
|
291
|
+
# newline and indent when aliased columns are listed
|
|
292
|
+
.gsub(/AS (\w+),/) { "AS #{Regexp.last_match(1)},\n " }
|
|
290
293
|
# remove quotes around column names in aggregate functions
|
|
291
294
|
.gsub(/(MIN|MAX|COUNT|SUM)\(("|`)(\w+)("|`)\)/) { "#{Regexp.last_match(1)}(#{Regexp.last_match(3)})" }
|
|
292
295
|
end
|
|
@@ -311,7 +314,7 @@ QueryUnderTest = Struct.new(:sql, :connection) do
|
|
|
311
314
|
end
|
|
312
315
|
|
|
313
316
|
def mysql2_explain
|
|
314
|
-
seed_rows! # MySQL needs a bit of data to reach for indexes
|
|
317
|
+
seed_rows! if Delayed::Job.none? # MySQL needs a bit of data to reach for indexes
|
|
315
318
|
connection.execute("ANALYZE TABLE #{Delayed::Job.table_name}")
|
|
316
319
|
connection.execute("SET SESSION max_seeks_for_key = 1")
|
|
317
320
|
connection.execute("EXPLAIN FORMAT=TREE #{sql}").to_a.map(&:first).join("\n")
|
|
@@ -325,7 +328,7 @@ QueryUnderTest = Struct.new(:sql, :connection) do
|
|
|
325
328
|
|
|
326
329
|
def seed_rows!
|
|
327
330
|
now = Delayed::Job.db_time_now
|
|
328
|
-
|
|
331
|
+
100.times do
|
|
329
332
|
[true, false].repeated_combination(5).each_with_index do |(erroring, failed, locked, future), i|
|
|
330
333
|
Delayed::Job.create!(
|
|
331
334
|
run_at: now + (future ? i.minutes : -i.minutes),
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: delayed
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Griffith
|
|
@@ -19,7 +19,7 @@ authors:
|
|
|
19
19
|
autorequire:
|
|
20
20
|
bindir: bin
|
|
21
21
|
cert_chain: []
|
|
22
|
-
date: 2026-02-
|
|
22
|
+
date: 2026-02-12 00:00:00.000000000 Z
|
|
23
23
|
dependencies:
|
|
24
24
|
- !ruby/object:Gem::Dependency
|
|
25
25
|
name: activerecord
|