delayed 2.2.0 → 3.0.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/README.md +3 -3
- data/app/models/delayed/job.rb +0 -9
- data/db/migrate/08_add_run_at_and_name_not_null_check.rb +34 -0
- data/db/migrate/09_validate_run_at_and_name_not_null.rb +41 -0
- data/lib/delayed/active_job_adapter.rb +41 -8
- data/lib/delayed/backend/base.rb +67 -11
- data/lib/delayed/backend/job_preparer.rb +20 -0
- data/lib/delayed/lifecycle.rb +1 -1
- data/lib/delayed/plugins/instrumentation.rb +9 -3
- data/lib/delayed/version.rb +1 -1
- data/lib/delayed/worker.rb +0 -8
- data/spec/delayed/active_job_adapter_spec.rb +285 -46
- data/spec/delayed/job_spec.rb +181 -32
- data/spec/delayed/monitor_spec.rb +1 -0
- data/spec/delayed/plugins/instrumentation_spec.rb +41 -0
- data/spec/helper.rb +9 -0
- data/spec/lifecycle_spec.rb +1 -1
- data/spec/message_sending_spec.rb +3 -13
- data/spec/performable_method_spec.rb +0 -6
- data/spec/sample_jobs.rb +0 -10
- metadata +12 -10
- /data/db/migrate/{1_create_delayed_jobs.rb → 01_create_delayed_jobs.rb} +0 -0
- /data/db/migrate/{2_add_name_to_delayed_jobs.rb → 02_add_name_to_delayed_jobs.rb} +0 -0
- /data/db/migrate/{3_add_index_to_delayed_jobs_name.rb → 03_add_index_to_delayed_jobs_name.rb} +0 -0
- /data/db/migrate/{4_index_live_jobs.rb → 04_index_live_jobs.rb} +0 -0
- /data/db/migrate/{5_index_failed_jobs.rb → 05_index_failed_jobs.rb} +0 -0
- /data/db/migrate/{6_set_postgres_fillfactor.rb → 06_set_postgres_fillfactor.rb} +0 -0
- /data/db/migrate/{7_remove_legacy_index.rb → 07_remove_legacy_index.rb} +0 -0
data/spec/delayed/job_spec.rb
CHANGED
|
@@ -20,12 +20,12 @@ describe Delayed::Job do
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
it 'sets run_at automatically if not set' do
|
|
23
|
-
expect(described_class.
|
|
23
|
+
expect(described_class.enqueue(payload_object: ErrorJob.new).run_at).not_to be_nil
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
it 'does not set run_at automatically if already set' do
|
|
27
27
|
later = described_class.db_time_now + 5.minutes
|
|
28
|
-
job = described_class.
|
|
28
|
+
job = described_class.enqueue(payload_object: ErrorJob.new, run_at: later)
|
|
29
29
|
expect(job.run_at).to be_within(1).of(later)
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -37,12 +37,6 @@ describe Delayed::Job do
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
describe 'enqueue' do
|
|
40
|
-
it "allows enqueue hook to modify job at DB level" do
|
|
41
|
-
later = described_class.db_time_now + 20.minutes
|
|
42
|
-
job = described_class.enqueue payload_object: EnqueueJobMod.new
|
|
43
|
-
expect(described_class.find(job.id).run_at).to be_within(1).of(later)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
40
|
context 'with a hash' do
|
|
47
41
|
it "raises ArgumentError when handler doesn't respond_to :perform" do
|
|
48
42
|
expect { described_class.enqueue(payload_object: Object.new) }.to raise_error(ArgumentError)
|
|
@@ -170,6 +164,151 @@ describe Delayed::Job do
|
|
|
170
164
|
expect(job).to be_persisted
|
|
171
165
|
end
|
|
172
166
|
end
|
|
167
|
+
|
|
168
|
+
context 'when payload defines an :enqueue hook' do
|
|
169
|
+
before do
|
|
170
|
+
stub_const('JobWithEnqueueHook', Class.new do
|
|
171
|
+
def enqueue(_job); end
|
|
172
|
+
|
|
173
|
+
def perform; end
|
|
174
|
+
end)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it 'raises an error naming the payload class' do
|
|
178
|
+
expect { described_class.enqueue(JobWithEnqueueHook.new) }
|
|
179
|
+
.to raise_error(RuntimeError, ':enqueue hook on JobWithEnqueueHook is no longer supported')
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it 'does not invoke the payload enqueue method' do
|
|
183
|
+
payload = JobWithEnqueueHook.new
|
|
184
|
+
expect(payload).not_to receive(:enqueue)
|
|
185
|
+
expect { described_class.enqueue(payload) }.to raise_error(RuntimeError, /:enqueue hook/)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
context 'when payload does not define :enqueue' do
|
|
190
|
+
it 'does not raise' do
|
|
191
|
+
expect { described_class.enqueue(SimpleJob.new) }.not_to raise_error
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
context 'when payload is an ActiveJob wrapper that responds to :enqueue' do
|
|
196
|
+
it 'does not raise' do
|
|
197
|
+
wrapper = ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(ActiveJobJob.new.serialize)
|
|
198
|
+
expect { described_class.enqueue(payload_object: wrapper) }.not_to raise_error
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
context 'when payload is a bare ActiveJob::Base instance' do
|
|
203
|
+
it 'raises' do
|
|
204
|
+
expect { described_class.enqueue(payload_object: ActiveJobJob.new) }.to raise_error(RuntimeError, /Delayed::Job enqueue methods do not accept ActiveJobs/)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
context 'when passed a bare ActiveJob::Base instance' do
|
|
209
|
+
it 'raises' do
|
|
210
|
+
expect { described_class.enqueue(ActiveJobJob.new) }.to raise_error(RuntimeError, /Delayed::Job enqueue methods do not accept ActiveJobs/)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
describe '.enqueue_all' do
|
|
216
|
+
def build_job(payload = SimpleJob.new, opts = {})
|
|
217
|
+
described_class.new(Delayed::Backend::JobPreparer.new(payload, opts).prepare)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'returns 0 when given no jobs' do
|
|
221
|
+
expect(described_class.enqueue_all([])).to eq(0)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it 'inserts all jobs in a single INSERT' do
|
|
225
|
+
jobs = Array.new(3) { build_job }
|
|
226
|
+
expect { described_class.enqueue_all(jobs) }
|
|
227
|
+
.to emit_notification('sql.active_record').with_payload(hash_including(sql: a_string_matching(/\AINSERT INTO/i)))
|
|
228
|
+
expect(described_class.count).to eq(3)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it 'returns the count of enqueued jobs' do
|
|
232
|
+
jobs = Array.new(3) { build_job }
|
|
233
|
+
expect(described_class.enqueue_all(jobs)).to eq(3)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it 'sets id on each job from INSERT RETURNING when supported' do
|
|
237
|
+
skip 'requires INSERT ... RETURNING support' unless described_class.connection.supports_insert_returning?
|
|
238
|
+
|
|
239
|
+
jobs = Array.new(2) { build_job }
|
|
240
|
+
described_class.enqueue_all(jobs)
|
|
241
|
+
expect(jobs.map(&:id)).to match_array(described_class.pluck(:id))
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'fires :enqueue lifecycle once with the jobs array' do
|
|
245
|
+
observed = []
|
|
246
|
+
lifecycle_was = Delayed.lifecycle
|
|
247
|
+
Delayed.instance_variable_set(:@lifecycle, Delayed::Lifecycle.new)
|
|
248
|
+
Delayed.lifecycle.before(:enqueue) { |jobs| observed << jobs }
|
|
249
|
+
|
|
250
|
+
jobs = [build_job]
|
|
251
|
+
described_class.enqueue_all(jobs)
|
|
252
|
+
|
|
253
|
+
expect(observed.size).to eq(1)
|
|
254
|
+
expect(observed.first).to eq(jobs)
|
|
255
|
+
ensure
|
|
256
|
+
Delayed.instance_variable_set(:@lifecycle, lifecycle_was)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
context 'when delay_jobs is false' do
|
|
260
|
+
before { Delayed::Worker.delay_jobs = false }
|
|
261
|
+
|
|
262
|
+
it 'inline-invokes each job and inserts nothing' do
|
|
263
|
+
payload = SimpleJob.new
|
|
264
|
+
expect(payload).to receive(:perform)
|
|
265
|
+
described_class.enqueue_all([build_job(payload)])
|
|
266
|
+
expect(described_class.count).to eq(0)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
context 'when a job payload defines an :enqueue hook' do
|
|
271
|
+
before do
|
|
272
|
+
stub_const('JobWithEnqueueHook', Class.new do
|
|
273
|
+
def enqueue(_job); end
|
|
274
|
+
|
|
275
|
+
def perform; end
|
|
276
|
+
end)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it 'raises before inserting anything' do
|
|
280
|
+
jobs = [build_job(JobWithEnqueueHook.new)]
|
|
281
|
+
expect { described_class.enqueue_all(jobs) }
|
|
282
|
+
.to raise_error(RuntimeError, ':enqueue hook on JobWithEnqueueHook is no longer supported')
|
|
283
|
+
expect(described_class.count).to eq(0)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
context 'when a job payload is a bare ActiveJob::Base instance' do
|
|
288
|
+
it 'raises' do
|
|
289
|
+
jobs = [build_job(ActiveJobJob.new)]
|
|
290
|
+
expect { described_class.enqueue_all(jobs) }.to raise_error(RuntimeError, /Delayed::Job enqueue methods do not accept ActiveJobs/)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
describe '#hook' do
|
|
296
|
+
context 'with :enqueue' do
|
|
297
|
+
before do
|
|
298
|
+
stub_const('JobWithEnqueueHook', Class.new do
|
|
299
|
+
def enqueue(_job); end
|
|
300
|
+
|
|
301
|
+
def perform; end
|
|
302
|
+
end)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
it 'raises rather than invoking the payload hook' do
|
|
306
|
+
job = described_class.new(payload_object: JobWithEnqueueHook.new)
|
|
307
|
+
expect(job.payload_object).not_to receive(:enqueue)
|
|
308
|
+
expect { job.hook(:enqueue) }
|
|
309
|
+
.to raise_error(RuntimeError, ':enqueue hook is no longer supported')
|
|
310
|
+
end
|
|
311
|
+
end
|
|
173
312
|
end
|
|
174
313
|
|
|
175
314
|
describe 'callbacks' do
|
|
@@ -187,9 +326,9 @@ describe Delayed::Job do
|
|
|
187
326
|
|
|
188
327
|
it 'calls before and after callbacks' do
|
|
189
328
|
job = described_class.enqueue(CallbackJob.new)
|
|
190
|
-
expect(CallbackJob.messages).to eq([
|
|
329
|
+
expect(CallbackJob.messages).to eq([])
|
|
191
330
|
job.invoke_job
|
|
192
|
-
expect(CallbackJob.messages).to eq(%w(
|
|
331
|
+
expect(CallbackJob.messages).to eq(%w(before perform success after))
|
|
193
332
|
end
|
|
194
333
|
|
|
195
334
|
it 'calls the after callback with an error' do
|
|
@@ -197,14 +336,14 @@ describe Delayed::Job do
|
|
|
197
336
|
expect(job.payload_object).to receive(:perform).and_raise(RuntimeError.new('fail'))
|
|
198
337
|
|
|
199
338
|
expect { job.invoke_job }.to raise_error(RuntimeError, 'fail')
|
|
200
|
-
expect(CallbackJob.messages).to eq(['
|
|
339
|
+
expect(CallbackJob.messages).to eq(['before', 'error: RuntimeError', 'after'])
|
|
201
340
|
end
|
|
202
341
|
|
|
203
342
|
it 'calls error when before raises an error' do
|
|
204
343
|
job = described_class.enqueue(CallbackJob.new)
|
|
205
344
|
expect(job.payload_object).to receive(:before).and_raise(RuntimeError.new('fail'))
|
|
206
345
|
expect { job.invoke_job }.to raise_error(RuntimeError, 'fail')
|
|
207
|
-
expect(CallbackJob.messages).to eq(['
|
|
346
|
+
expect(CallbackJob.messages).to eq(['error: RuntimeError', 'after'])
|
|
208
347
|
end
|
|
209
348
|
end
|
|
210
349
|
|
|
@@ -462,26 +601,23 @@ describe Delayed::Job do
|
|
|
462
601
|
describe '#name' do
|
|
463
602
|
context 'when name column is populated' do
|
|
464
603
|
it 'is the class name of the job that was enqueued' do
|
|
465
|
-
job = described_class.
|
|
604
|
+
job = described_class.enqueue(payload_object: ErrorJob.new)
|
|
466
605
|
expect(job.name).to eq('ErrorJob')
|
|
467
|
-
job.save!
|
|
468
606
|
expect(job.reload.name).to eq('ErrorJob')
|
|
469
607
|
expect(described_class.group(:name).count).to eq('ErrorJob' => 1)
|
|
470
608
|
end
|
|
471
609
|
|
|
472
610
|
it 'is the class name of the performable job if it is an ActiveJob' do
|
|
473
611
|
job_wrapper = ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(ActiveJobJob.new.serialize)
|
|
474
|
-
job = described_class.
|
|
612
|
+
job = described_class.enqueue(payload_object: job_wrapper)
|
|
475
613
|
expect(job.name).to eq('ActiveJobJob')
|
|
476
|
-
job.save!
|
|
477
614
|
expect(job.reload.name).to eq('ActiveJobJob')
|
|
478
615
|
expect(described_class.group(:name).count).to eq('ActiveJobJob' => 1)
|
|
479
616
|
end
|
|
480
617
|
|
|
481
618
|
it 'is the returned display_name if display_name is defined on the job object' do
|
|
482
|
-
job = described_class.
|
|
619
|
+
job = described_class.enqueue(payload_object: NamedJob.new)
|
|
483
620
|
expect(job.name).to eq('named_job')
|
|
484
|
-
job.save!
|
|
485
621
|
expect(job.reload.name).to eq('named_job')
|
|
486
622
|
expect(described_class.group(:name).count).to eq('named_job' => 1)
|
|
487
623
|
end
|
|
@@ -493,25 +629,38 @@ describe Delayed::Job do
|
|
|
493
629
|
end
|
|
494
630
|
|
|
495
631
|
it 'is the custom name value when set explicitly' do
|
|
496
|
-
job = described_class.
|
|
497
|
-
job.name = 'Custom Name'
|
|
498
|
-
job.save!
|
|
632
|
+
job = described_class.enqueue(payload_object: ErrorJob.new, name: 'Custom Name')
|
|
499
633
|
expect(job.reload.name).to eq('Custom Name')
|
|
500
634
|
expect(described_class.group(:name).count).to eq('Custom Name' => 1)
|
|
501
635
|
end
|
|
502
636
|
end
|
|
503
637
|
|
|
504
638
|
context 'when name column is NULL' do
|
|
639
|
+
before do
|
|
640
|
+
ActiveRecord::Schema.define do
|
|
641
|
+
ValidateRunAtAndNameNotNull.migrate(:down)
|
|
642
|
+
AddRunAtAndNameNotNullCheck.migrate(:down)
|
|
643
|
+
end
|
|
644
|
+
described_class.reset_column_information
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
after do
|
|
648
|
+
described_class.delete_all
|
|
649
|
+
ActiveRecord::Schema.define do
|
|
650
|
+
AddRunAtAndNameNotNullCheck.migrate(:up)
|
|
651
|
+
ValidateRunAtAndNameNotNull.migrate(:up)
|
|
652
|
+
end
|
|
653
|
+
described_class.reset_column_information
|
|
654
|
+
end
|
|
655
|
+
|
|
505
656
|
it 'is the class name of the job that was enqueued' do
|
|
506
|
-
job = described_class.
|
|
507
|
-
job.update_column(:name, nil) # rubocop:disable Rails/SkipsModelValidations
|
|
657
|
+
job = described_class.enqueue(payload_object: ErrorJob.new)
|
|
508
658
|
expect(job.reload.name).to eq('ErrorJob')
|
|
509
659
|
end
|
|
510
660
|
|
|
511
661
|
it 'is the class name of the performable job if it is an ActiveJob' do
|
|
512
662
|
job_wrapper = ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(ActiveJobJob.new.serialize)
|
|
513
|
-
job = described_class.
|
|
514
|
-
job.update_column(:name, nil) # rubocop:disable Rails/SkipsModelValidations
|
|
663
|
+
job = described_class.enqueue(payload_object: job_wrapper)
|
|
515
664
|
expect(job.reload.name).to eq('ActiveJobJob')
|
|
516
665
|
end
|
|
517
666
|
|
|
@@ -542,7 +691,7 @@ describe Delayed::Job do
|
|
|
542
691
|
end
|
|
543
692
|
|
|
544
693
|
it 'is the class name of the job that was enqueued' do
|
|
545
|
-
job = described_class.new(payload_object: ErrorJob.new)
|
|
694
|
+
job = described_class.new(payload_object: ErrorJob.new, run_at: Time.current)
|
|
546
695
|
expect(job.name).to eq('ErrorJob')
|
|
547
696
|
job.save!
|
|
548
697
|
expect(job.reload.name).to eq('ErrorJob')
|
|
@@ -550,14 +699,14 @@ describe Delayed::Job do
|
|
|
550
699
|
|
|
551
700
|
it 'is the class name of the performable job if it is an ActiveJob' do
|
|
552
701
|
job_wrapper = ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(ActiveJobJob.new.serialize)
|
|
553
|
-
job = described_class.new(payload_object: job_wrapper)
|
|
702
|
+
job = described_class.new(payload_object: job_wrapper, run_at: Time.current)
|
|
554
703
|
expect(job.name).to eq('ActiveJobJob')
|
|
555
704
|
job.save!
|
|
556
705
|
expect(job.reload.name).to eq('ActiveJobJob')
|
|
557
706
|
end
|
|
558
707
|
|
|
559
708
|
it 'is the returned display_name if display_name is defined on the job object' do
|
|
560
|
-
job = described_class.new(payload_object: NamedJob.new)
|
|
709
|
+
job = described_class.new(payload_object: NamedJob.new, run_at: Time.current)
|
|
561
710
|
expect(job.name).to eq('named_job')
|
|
562
711
|
job.save!
|
|
563
712
|
expect(job.reload.name).to eq('named_job')
|
|
@@ -835,7 +984,7 @@ describe Delayed::Job do
|
|
|
835
984
|
describe 'running a job' do
|
|
836
985
|
it 'fails after Worker.max_run_time' do
|
|
837
986
|
Delayed::Worker.max_run_time = 1.second
|
|
838
|
-
job = described_class.
|
|
987
|
+
job = described_class.enqueue payload_object: LongRunningJob.new
|
|
839
988
|
worker.perform(job)
|
|
840
989
|
expect(job.error).not_to be_nil
|
|
841
990
|
expect(job.reload.last_error).to match(/expired/)
|
|
@@ -845,7 +994,7 @@ describe Delayed::Job do
|
|
|
845
994
|
|
|
846
995
|
context 'when the job raises a deserialization error' do
|
|
847
996
|
it 'marks the job as failed' do
|
|
848
|
-
job = described_class.
|
|
997
|
+
job = described_class.enqueue payload_object: LongRunningJob.new
|
|
849
998
|
job.update_columns(handler: '--- !ruby/object:JobThatDoesNotExist {}') # rubocop:disable Rails/SkipsModelValidations
|
|
850
999
|
expect_any_instance_of(described_class).to receive(:destroy_failed_jobs?).and_return(false)
|
|
851
1000
|
worker.work_off
|
|
@@ -900,13 +1049,13 @@ describe Delayed::Job do
|
|
|
900
1049
|
|
|
901
1050
|
context 'reschedule' do
|
|
902
1051
|
before do
|
|
903
|
-
@job = described_class.
|
|
1052
|
+
@job = described_class.enqueue payload_object: SimpleJob.new
|
|
904
1053
|
end
|
|
905
1054
|
|
|
906
1055
|
shared_examples_for 'any failure more than Worker.max_attempts times' do
|
|
907
1056
|
context "when the job's payload has a #failure hook" do
|
|
908
1057
|
before do
|
|
909
|
-
@job = described_class.
|
|
1058
|
+
@job = described_class.enqueue payload_object: OnPermanentFailureJob.new
|
|
910
1059
|
expect(@job.payload_object).to respond_to(:failure)
|
|
911
1060
|
end
|
|
912
1061
|
|
|
@@ -3,6 +3,47 @@ require 'helper'
|
|
|
3
3
|
RSpec.describe Delayed::Plugins::Instrumentation do
|
|
4
4
|
let!(:job) { Delayed::Job.enqueue SimpleJob.new, priority: 13, queue: 'test' }
|
|
5
5
|
|
|
6
|
+
it 'emits delayed.job.enqueue when a job is enqueued' do
|
|
7
|
+
expect { Delayed::Job.enqueue SimpleJob.new }.to emit_notification('delayed.job.enqueue').with_payload(
|
|
8
|
+
jobs: [
|
|
9
|
+
hash_including(
|
|
10
|
+
job_name: 'SimpleJob',
|
|
11
|
+
table: 'delayed_jobs',
|
|
12
|
+
database: current_database,
|
|
13
|
+
database_adapter: current_adapter,
|
|
14
|
+
job: an_instance_of(Delayed::Job),
|
|
15
|
+
),
|
|
16
|
+
],
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'emits a single delayed.job.enqueue when a batch is enqueued via the ActiveJob adapter' do
|
|
21
|
+
job_class = Class.new(ActiveJob::Base) do
|
|
22
|
+
def perform; end
|
|
23
|
+
end
|
|
24
|
+
stub_const('BatchedJob', job_class)
|
|
25
|
+
|
|
26
|
+
adapter_was = ActiveJob::Base.queue_adapter
|
|
27
|
+
ActiveJob::Base.queue_adapter = :delayed
|
|
28
|
+
begin
|
|
29
|
+
jobs = Array.new(3) { BatchedJob.new }
|
|
30
|
+
expect { ActiveJob::Base.queue_adapter.enqueue_all(jobs) }
|
|
31
|
+
.to emit_notification('delayed.job.enqueue').with_payload(
|
|
32
|
+
jobs: Array.new(3) {
|
|
33
|
+
hash_including(
|
|
34
|
+
job_name: 'BatchedJob',
|
|
35
|
+
table: 'delayed_jobs',
|
|
36
|
+
database: current_database,
|
|
37
|
+
database_adapter: current_adapter,
|
|
38
|
+
job: an_instance_of(Delayed::Job),
|
|
39
|
+
)
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
ensure
|
|
43
|
+
ActiveJob::Base.queue_adapter = adapter_was
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
6
47
|
it 'emits delayed.job.run' do
|
|
7
48
|
expect { Delayed::Worker.new.work_off }.to emit_notification('delayed.job.run').with_payload(
|
|
8
49
|
job_name: 'SimpleJob',
|
data/spec/helper.rb
CHANGED
|
@@ -92,6 +92,8 @@ ActiveRecord::Schema.define do
|
|
|
92
92
|
run_migration(IndexFailedJobs)
|
|
93
93
|
run_migration(SetPostgresFillfactor)
|
|
94
94
|
run_migration(RemoveLegacyIndex)
|
|
95
|
+
run_migration(AddRunAtAndNameNotNullCheck)
|
|
96
|
+
run_migration(ValidateRunAtAndNameNotNull)
|
|
95
97
|
|
|
96
98
|
# Test that these index migrations can be re-applied idempotently.
|
|
97
99
|
# (In case identical indexes had been manually applied previously.)
|
|
@@ -266,6 +268,10 @@ def current_database
|
|
|
266
268
|
end
|
|
267
269
|
end
|
|
268
270
|
|
|
271
|
+
def current_database_name
|
|
272
|
+
current_adapter == 'sqlite3' ? 'tmp/database.sqlite' : 'delayed_job_test'
|
|
273
|
+
end
|
|
274
|
+
|
|
269
275
|
QueryUnderTest = Struct.new(:sql, :connection) do
|
|
270
276
|
def self.for(query, connection: ActiveRecord::Base.connection)
|
|
271
277
|
new(query.respond_to?(:to_sql) ? query.to_sql : query.to_s, connection)
|
|
@@ -305,10 +311,12 @@ QueryUnderTest = Struct.new(:sql, :connection) do
|
|
|
305
311
|
def postgresql_explain
|
|
306
312
|
connection.execute("SET seq_page_cost = 100")
|
|
307
313
|
connection.execute("SET enable_hashagg = off")
|
|
314
|
+
connection.execute("SET enable_incremental_sort = off")
|
|
308
315
|
connection.execute("SET plan_cache_mode TO force_generic_plan")
|
|
309
316
|
connection.execute("EXPLAIN (VERBOSE) #{sql}").values.flatten.join("\n")
|
|
310
317
|
ensure
|
|
311
318
|
connection.execute("RESET plan_cache_mode")
|
|
319
|
+
connection.execute("RESET enable_incremental_sort")
|
|
312
320
|
connection.execute("RESET enable_hashagg")
|
|
313
321
|
connection.execute("RESET seq_page_cost")
|
|
314
322
|
end
|
|
@@ -334,6 +342,7 @@ QueryUnderTest = Struct.new(:sql, :connection) do
|
|
|
334
342
|
run_at: now + (future ? i.minutes : -i.minutes),
|
|
335
343
|
queue: "queue_#{i}",
|
|
336
344
|
handler: "--- !ruby/object:SimpleJob\n",
|
|
345
|
+
name: 'SimpleJob',
|
|
337
346
|
attempts: erroring ? i : 0,
|
|
338
347
|
failed_at: failed ? now - i.minutes : nil,
|
|
339
348
|
locked_at: locked ? now - i.seconds : nil,
|
data/spec/lifecycle_spec.rb
CHANGED
|
@@ -3,7 +3,7 @@ require 'helper'
|
|
|
3
3
|
describe Delayed::Lifecycle do
|
|
4
4
|
let(:lifecycle) { described_class.new }
|
|
5
5
|
let(:callback) { lambda { |*_args| } }
|
|
6
|
-
let(:arguments) { [
|
|
6
|
+
let(:arguments) { [%i(job_a job_b)] }
|
|
7
7
|
let(:behavior) { double(Object, before!: nil, after!: nil, inside!: nil) }
|
|
8
8
|
let(:wrapped_block) { proc { behavior.inside! } }
|
|
9
9
|
|
|
@@ -136,23 +136,13 @@ describe Delayed::MessageSending do
|
|
|
136
136
|
}.to change { Delayed::Job.count }.by(1)
|
|
137
137
|
end
|
|
138
138
|
|
|
139
|
-
it '
|
|
140
|
-
Delayed::Worker.delay_jobs = ->
|
|
139
|
+
it 'raises when delay_jobs is a Proc' do
|
|
140
|
+
Delayed::Worker.delay_jobs = -> { true }
|
|
141
141
|
fairy_tail = FairyTail.new
|
|
142
142
|
expect {
|
|
143
143
|
expect {
|
|
144
144
|
fairy_tail.delay.tell('a', kwarg: 'b')
|
|
145
|
-
}.
|
|
146
|
-
}.to change { Delayed::Job.count }.by(1)
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
it 'does not delay the job when delay_jobs is a proc returning false' do
|
|
150
|
-
Delayed::Worker.delay_jobs = ->(_job) { false }
|
|
151
|
-
fairy_tail = FairyTail.new
|
|
152
|
-
expect {
|
|
153
|
-
expect {
|
|
154
|
-
fairy_tail.delay.tell('a', kwarg: 'b')
|
|
155
|
-
}.to change { fairy_tail.happy_ending }.from(nil).to %w(a b)
|
|
145
|
+
}.to raise_error('Delayed::Worker.delay_jobs may not be a Proc')
|
|
156
146
|
}.not_to(change { Delayed::Job.count })
|
|
157
147
|
end
|
|
158
148
|
end
|
|
@@ -95,12 +95,6 @@ describe Delayed::PerformableMethod do
|
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
it 'delegates enqueue hook to object' do
|
|
99
|
-
story = Story.create
|
|
100
|
-
expect(story).to receive(:enqueue).with(an_instance_of(Delayed::Job))
|
|
101
|
-
story.delay.tell
|
|
102
|
-
end
|
|
103
|
-
|
|
104
98
|
it 'delegates error hook to object' do
|
|
105
99
|
story = Story.create
|
|
106
100
|
expect(story).to receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError))
|
data/spec/sample_jobs.rb
CHANGED
|
@@ -77,10 +77,6 @@ end
|
|
|
77
77
|
class CallbackJob
|
|
78
78
|
cattr_accessor :messages
|
|
79
79
|
|
|
80
|
-
def enqueue(_job)
|
|
81
|
-
self.class.messages << 'enqueue'
|
|
82
|
-
end
|
|
83
|
-
|
|
84
80
|
def before(_job)
|
|
85
81
|
self.class.messages << 'before'
|
|
86
82
|
end
|
|
@@ -106,12 +102,6 @@ class CallbackJob
|
|
|
106
102
|
end
|
|
107
103
|
end
|
|
108
104
|
|
|
109
|
-
class EnqueueJobMod < SimpleJob
|
|
110
|
-
def enqueue(job)
|
|
111
|
-
job.run_at = 20.minutes.from_now
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
105
|
class ActiveJobJob < ActiveJob::Base # rubocop:disable Rails/ApplicationJob
|
|
116
106
|
def perform(*args, **kwargs); end
|
|
117
107
|
end
|
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:
|
|
4
|
+
version: 3.0.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-
|
|
22
|
+
date: 2026-06-08 00:00:00.000000000 Z
|
|
23
23
|
dependencies:
|
|
24
24
|
- !ruby/object:Gem::Dependency
|
|
25
25
|
name: activerecord
|
|
@@ -66,13 +66,15 @@ files:
|
|
|
66
66
|
- README.md
|
|
67
67
|
- Rakefile
|
|
68
68
|
- app/models/delayed/job.rb
|
|
69
|
-
- db/migrate/
|
|
70
|
-
- db/migrate/
|
|
71
|
-
- db/migrate/
|
|
72
|
-
- db/migrate/
|
|
73
|
-
- db/migrate/
|
|
74
|
-
- db/migrate/
|
|
75
|
-
- db/migrate/
|
|
69
|
+
- db/migrate/01_create_delayed_jobs.rb
|
|
70
|
+
- db/migrate/02_add_name_to_delayed_jobs.rb
|
|
71
|
+
- db/migrate/03_add_index_to_delayed_jobs_name.rb
|
|
72
|
+
- db/migrate/04_index_live_jobs.rb
|
|
73
|
+
- db/migrate/05_index_failed_jobs.rb
|
|
74
|
+
- db/migrate/06_set_postgres_fillfactor.rb
|
|
75
|
+
- db/migrate/07_remove_legacy_index.rb
|
|
76
|
+
- db/migrate/08_add_run_at_and_name_not_null_check.rb
|
|
77
|
+
- db/migrate/09_validate_run_at_and_name_not_null.rb
|
|
76
78
|
- lib/delayed.rb
|
|
77
79
|
- lib/delayed/active_job_adapter.rb
|
|
78
80
|
- lib/delayed/backend/base.rb
|
|
@@ -128,7 +130,7 @@ homepage: http://github.com/betterment/delayed
|
|
|
128
130
|
licenses:
|
|
129
131
|
- MIT
|
|
130
132
|
metadata:
|
|
131
|
-
changelog_uri: https://github.com/betterment/delayed/
|
|
133
|
+
changelog_uri: https://github.com/betterment/delayed/releases
|
|
132
134
|
bug_tracker_uri: https://github.com/betterment/delayed/issues
|
|
133
135
|
source_code_uri: https://github.com/betterment/delayed
|
|
134
136
|
rubygems_mfa_required: 'true'
|
|
File without changes
|
|
File without changes
|
/data/db/migrate/{3_add_index_to_delayed_jobs_name.rb → 03_add_index_to_delayed_jobs_name.rb}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|