inst-jobs 3.1.2 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,1238 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "timeout"
4
-
5
- module InDelayedJobTest
6
- def self.check_in_job
7
- Delayed::Job.in_delayed_job?.should == true
8
- end
9
- end
10
-
11
- shared_examples_for "a backend" do
12
- def create_job(opts = {})
13
- Delayed::Job.enqueue(SimpleJob.new, **{ queue: nil }.merge(opts))
14
- end
15
-
16
- before do
17
- SimpleJob.runs = 0
18
- end
19
-
20
- it "sets run_at automatically if not set" do
21
- expect(Delayed::Job.create(payload_object: ErrorJob.new).run_at).not_to be_nil
22
- end
23
-
24
- it "does not set run_at automatically if already set" do
25
- later = Delayed::Job.db_time_now + 5.minutes
26
- expect(Delayed::Job.create(payload_object: ErrorJob.new, run_at: later).run_at).to be_within(1).of(later)
27
- end
28
-
29
- it "raises ArgumentError when handler doesn't respond_to :perform" do
30
- expect { Delayed::Job.enqueue(Object.new) }.to raise_error(ArgumentError)
31
- end
32
-
33
- it "increases count after enqueuing items" do
34
- Delayed::Job.enqueue SimpleJob.new
35
- expect(Delayed::Job.jobs_count(:current)).to eq(1)
36
- end
37
-
38
- it "triggers the lifecycle event around the create" do
39
- called = false
40
- created_job = nil
41
-
42
- Delayed::Worker.lifecycle.after(:create) do |_, result:|
43
- called = true
44
- created_job = result
45
- end
46
-
47
- job = SimpleJob.new
48
- Delayed::Job.enqueue(job)
49
-
50
- expect(called).to be_truthy
51
- expect(created_job).to be_kind_of Delayed::Job
52
- expect(created_job.tag).to eq "SimpleJob#perform"
53
- end
54
-
55
- it "is able to set priority when enqueuing items" do
56
- @job = Delayed::Job.enqueue SimpleJob.new, priority: 5
57
- expect(@job.priority).to eq(5)
58
- end
59
-
60
- it "uses the default priority when enqueuing items" do
61
- Delayed::Job.default_priority = 0
62
- @job = Delayed::Job.enqueue SimpleJob.new
63
- expect(@job.priority).to eq(0)
64
- Delayed::Job.default_priority = 10
65
- @job = Delayed::Job.enqueue SimpleJob.new
66
- expect(@job.priority).to eq(10)
67
- Delayed::Job.default_priority = 0
68
- end
69
-
70
- it "is able to set run_at when enqueuing items" do
71
- later = Delayed::Job.db_time_now + 5.minutes
72
- @job = Delayed::Job.enqueue SimpleJob.new, priority: 5, run_at: later
73
- expect(@job.run_at).to be_within(1).of(later)
74
- end
75
-
76
- it "is able to set expires_at when enqueuing items" do
77
- later = Delayed::Job.db_time_now + 1.day
78
- @job = Delayed::Job.enqueue SimpleJob.new, expires_at: later
79
- expect(@job.expires_at).to be_within(1).of(later)
80
- end
81
-
82
- it "works with jobs in modules" do
83
- M::ModuleJob.runs = 0
84
- job = Delayed::Job.enqueue M::ModuleJob.new
85
- expect { job.invoke_job }.to change(M::ModuleJob, :runs).from(0).to(1)
86
- end
87
-
88
- it "raises an DeserializationError when the job class is totally unknown" do
89
- job = Delayed::Job.new handler: "--- !ruby/object:JobThatDoesNotExist {}"
90
- expect { job.payload_object.perform }.to raise_error(Delayed::Backend::DeserializationError)
91
- end
92
-
93
- it "tries to load the class when it is unknown at the time of the deserialization" do
94
- job = Delayed::Job.new handler: "--- !ruby/object:JobThatDoesNotExist {}"
95
- expect { job.payload_object.perform }.to raise_error(Delayed::Backend::DeserializationError)
96
- end
97
-
98
- it "tries include the namespace when loading unknown objects" do
99
- job = Delayed::Job.new handler: "--- !ruby/object:Delayed::JobThatDoesNotExist {}"
100
- expect { job.payload_object.perform }.to raise_error(Delayed::Backend::DeserializationError)
101
- end
102
-
103
- it "alsoes try to load structs when they are unknown (raises TypeError)" do
104
- job = Delayed::Job.new handler: "--- !ruby/struct:JobThatDoesNotExist {}"
105
- expect { job.payload_object.perform }.to raise_error(Delayed::Backend::DeserializationError)
106
- end
107
-
108
- it "tries include the namespace when loading unknown structs" do
109
- job = Delayed::Job.new handler: "--- !ruby/struct:Delayed::JobThatDoesNotExist {}"
110
- expect { job.payload_object.perform }.to raise_error(Delayed::Backend::DeserializationError)
111
- end
112
-
113
- it "raises an DeserializationError when the handler is invalid YAML" do
114
- job = Delayed::Job.new handler: %(test: ""11")
115
- expect { job.payload_object.perform }.to raise_error(Delayed::Backend::DeserializationError, /parsing error/)
116
- end
117
-
118
- describe "find_available" do
119
- it "does not find failed jobs" do
120
- @job = create_job attempts: 50
121
- @job.fail!
122
- expect(Delayed::Job.find_available(5)).not_to include(@job)
123
- end
124
-
125
- it "does not find jobs scheduled for the future" do
126
- @job = create_job run_at: (Delayed::Job.db_time_now + 1.minute)
127
- expect(Delayed::Job.find_available(5)).not_to include(@job)
128
- end
129
-
130
- it "does not find jobs locked by another worker" do
131
- @job = create_job
132
- expect(Delayed::Job.get_and_lock_next_available("other_worker")).to eq(@job)
133
- expect(Delayed::Job.find_available(5)).not_to include(@job)
134
- end
135
-
136
- it "finds open jobs" do
137
- @job = create_job
138
- expect(Delayed::Job.find_available(5)).to include(@job)
139
- end
140
-
141
- it "returns an empty hash when asking for multiple jobs, and there aren't any" do
142
- locked_jobs = Delayed::Job.get_and_lock_next_available(%w[worker1 worker2])
143
- expect(locked_jobs).to eq({})
144
- end
145
- end
146
-
147
- context "when another worker is already performing an task, it" do
148
- before do
149
- @job = Delayed::Job.create payload_object: SimpleJob.new
150
- expect(Delayed::Job.get_and_lock_next_available("worker1")).to eq(@job)
151
- end
152
-
153
- it "does not allow a second worker to get exclusive access" do
154
- expect(Delayed::Job.get_and_lock_next_available("worker2")).to be_nil
155
- end
156
-
157
- it "is not found by another worker" do
158
- expect(Delayed::Job.find_available(1).length).to eq(0)
159
- end
160
- end
161
-
162
- describe "#name" do
163
- it "is the class name of the job that was enqueued" do
164
- expect(Delayed::Job.create(payload_object: ErrorJob.new).name).to eq("ErrorJob")
165
- end
166
-
167
- it "is the method that will be called if its a performable method object" do
168
- @job = Story.delay(ignore_transaction: true).create
169
- expect(@job.name).to eq("Story.create")
170
- end
171
-
172
- it "is the instance method that will be called if its a performable method object" do
173
- @job = Story.create(text: "...").delay(ignore_transaction: true).save
174
- expect(@job.name).to eq("Story#save")
175
- end
176
- end
177
-
178
- context "worker prioritization" do
179
- it "fetches jobs ordered by priority" do
180
- 10.times { create_job priority: rand(10) }
181
- jobs = Delayed::Job.find_available(10)
182
- expect(jobs.size).to eq(10)
183
- jobs.each_cons(2) do |a, b|
184
- expect(a.priority).to be <= b.priority
185
- end
186
- end
187
-
188
- it "does not find jobs lower than the given priority" do
189
- create_job priority: 5
190
- found = Delayed::Job.get_and_lock_next_available("test1", Delayed::Settings.queue, 10, 20)
191
- expect(found).to be_nil
192
- job2 = create_job priority: 10
193
- found = Delayed::Job.get_and_lock_next_available("test1", Delayed::Settings.queue, 10, 20)
194
- expect(found).to eq(job2)
195
- job3 = create_job priority: 15
196
- found = Delayed::Job.get_and_lock_next_available("test2", Delayed::Settings.queue, 10, 20)
197
- expect(found).to eq(job3)
198
- end
199
-
200
- it "does not find jobs higher than the given priority" do
201
- create_job priority: 25
202
- found = Delayed::Job.get_and_lock_next_available("test1", Delayed::Settings.queue, 10, 20)
203
- expect(found).to be_nil
204
- job2 = create_job priority: 20
205
- found = Delayed::Job.get_and_lock_next_available("test1", Delayed::Settings.queue, 10, 20)
206
- expect(found).to eq(job2)
207
- job3 = create_job priority: 15
208
- found = Delayed::Job.get_and_lock_next_available("test2", Delayed::Settings.queue, 10, 20)
209
- expect(found).to eq(job3)
210
- end
211
- end
212
-
213
- context "clear_locks!" do
214
- before do
215
- @job = create_job(locked_by: "worker", locked_at: Delayed::Job.db_time_now)
216
- end
217
-
218
- it "clears locks for the given worker" do
219
- Delayed::Job.clear_locks!("worker")
220
- expect(Delayed::Job.find_available(5)).to include(@job)
221
- end
222
-
223
- it "does not clear locks for other workers" do
224
- Delayed::Job.clear_locks!("worker1")
225
- expect(Delayed::Job.find_available(5)).not_to include(@job)
226
- end
227
- end
228
-
229
- context "unlock" do
230
- before do
231
- @job = create_job(locked_by: "worker", locked_at: Delayed::Job.db_time_now)
232
- end
233
-
234
- it "clears locks" do
235
- @job.unlock
236
- expect(@job.locked_by).to be_nil
237
- expect(@job.locked_at).to be_nil
238
- end
239
-
240
- it "clears locks from multiple jobs" do
241
- job2 = create_job(locked_by: "worker", locked_at: Delayed::Job.db_time_now)
242
- Delayed::Job.unlock([@job, job2])
243
- expect(@job.locked_at).to be_nil
244
- expect(job2.locked_at).to be_nil
245
- # make sure it was persisted, too
246
- expect(Delayed::Job.find(@job.id).locked_at).to be_nil
247
- end
248
- end
249
-
250
- describe "#transfer_lock" do
251
- it "works" do
252
- job = create_job(locked_by: "worker", locked_at: Delayed::Job.db_time_now)
253
- expect(job.transfer_lock!(from: "worker", to: "worker2")).to be true
254
- expect(Delayed::Job.find(job.id).locked_by).to eq "worker2"
255
- end
256
- end
257
-
258
- context "strands" do
259
- it "runs strand jobs in strict order" do
260
- job1 = create_job(strand: "myjobs")
261
- job2 = create_job(strand: "myjobs")
262
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq(job1)
263
- expect(Delayed::Job.get_and_lock_next_available("w2")).to be_nil
264
- job1.destroy
265
- # update time since the failed lock pushed it forward
266
- job2.run_at = 1.minute.ago
267
- job2.save!
268
- expect(Delayed::Job.get_and_lock_next_available("w3")).to eq(job2)
269
- expect(Delayed::Job.get_and_lock_next_available("w4")).to be_nil
270
- end
271
-
272
- it "fails to lock if an earlier job gets locked" do
273
- job1 = create_job(strand: "myjobs")
274
- job2 = create_job(strand: "myjobs")
275
- expect(Delayed::Job.find_available(2)).to eq([job1])
276
- expect(Delayed::Job.find_available(2)).to eq([job1])
277
-
278
- # job1 gets locked by w1
279
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq(job1)
280
-
281
- # normally w2 would now be able to lock job2, but strands prevent it
282
- expect(Delayed::Job.get_and_lock_next_available("w2")).to be_nil
283
-
284
- # now job1 is done
285
- job1.destroy
286
- # update time since the failed lock pushed it forward
287
- job2.run_at = 1.minute.ago
288
- job2.save!
289
- expect(Delayed::Job.get_and_lock_next_available("w2")).to eq(job2)
290
- end
291
-
292
- it "keeps strand jobs in order as they are rescheduled" do
293
- job1 = create_job(strand: "myjobs")
294
- job2 = create_job(strand: "myjobs")
295
- job3 = create_job(strand: "myjobs")
296
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq(job1)
297
- expect(Delayed::Job.find_available(1)).to eq([])
298
- job1.destroy
299
- expect(Delayed::Job.find_available(1)).to eq([job2])
300
- # move job2's time forward
301
- job2.run_at = 1.second.ago
302
- job2.save!
303
- job3.run_at = 5.seconds.ago
304
- job3.save!
305
- # we should still get job2, not job3
306
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq(job2)
307
- end
308
-
309
- it "allows to run the next job if a failed job is present" do
310
- job1 = create_job(strand: "myjobs")
311
- job2 = create_job(strand: "myjobs")
312
- job1.fail!
313
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq(job2)
314
- end
315
-
316
- it "does not interfere with jobs with no strand" do
317
- jobs = [create_job(strand: nil), create_job(strand: "myjobs")]
318
- locked = [Delayed::Job.get_and_lock_next_available("w1"),
319
- Delayed::Job.get_and_lock_next_available("w2")]
320
- expect(jobs).to eq locked
321
- expect(Delayed::Job.get_and_lock_next_available("w3")).to be_nil
322
- end
323
-
324
- it "does not interfere with jobs in other strands" do
325
- jobs = [create_job(strand: "strand1"), create_job(strand: "strand2")]
326
- locked = [Delayed::Job.get_and_lock_next_available("w1"),
327
- Delayed::Job.get_and_lock_next_available("w2")]
328
- expect(jobs).to eq locked
329
- expect(Delayed::Job.get_and_lock_next_available("w3")).to be_nil
330
- end
331
-
332
- it "does not find next jobs when given no priority" do
333
- jobs = [create_job(strand: "strand1"), create_job(strand: "strand1")]
334
- first = Delayed::Job.get_and_lock_next_available("w1", Delayed::Settings.queue, nil, nil)
335
- second = Delayed::Job.get_and_lock_next_available("w2", Delayed::Settings.queue, nil, nil)
336
- expect(first).to eq jobs.first
337
- expect(second).to be_nil
338
- end
339
-
340
- it "complains if you pass more than one strand-based option" do
341
- expect { create_job(strand: "a", n_strand: "b") }.to raise_error(ArgumentError)
342
- end
343
-
344
- context "singleton" do
345
- it "creates if there's no jobs on the strand" do
346
- @job = create_job(singleton: "myjobs")
347
- expect(@job).to be_present
348
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq(@job)
349
- end
350
-
351
- it "creates if there's another job on the strand, but it's running" do
352
- @job = create_job(singleton: "myjobs")
353
- expect(@job).to be_present
354
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq(@job)
355
-
356
- @job2 = create_job(singleton: "myjobs")
357
- expect(@job).to be_present
358
- expect(@job2).not_to eq(@job)
359
- end
360
-
361
- it "does not create if there's another non-running job on the strand" do
362
- @job = create_job(singleton: "myjobs")
363
- expect(@job).to be_present
364
-
365
- @job2 = create_job(singleton: "myjobs")
366
- expect(@job2).to be_new_record
367
- end
368
-
369
- it "does not create if there's a job running and one waiting on the strand" do
370
- @job = create_job(singleton: "myjobs")
371
- expect(@job).to be_present
372
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq(@job)
373
-
374
- @job2 = create_job(singleton: "myjobs")
375
- expect(@job2).to be_present
376
- expect(@job2).not_to eq(@job)
377
-
378
- @job3 = create_job(singleton: "myjobs")
379
- expect(@job3).to be_new_record
380
- end
381
-
382
- it "updates existing job if new job is set to run sooner" do
383
- job1 = create_job(singleton: "myjobs", run_at: 1.hour.from_now)
384
- job2 = create_job(singleton: "myjobs")
385
- expect(job2).to eq(job1)
386
- # it should be scheduled to run immediately
387
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq(job1)
388
- end
389
-
390
- it "updates existing job to a later date if requested" do
391
- t1 = 1.hour.from_now
392
- t2 = 2.hours.from_now
393
- job1 = create_job(singleton: "myjobs", run_at: t1)
394
- job2 = create_job(singleton: "myjobs", run_at: t2)
395
- expect(job2).to be_new_record
396
-
397
- job3 = create_job(singleton: "myjobs", run_at: t2, on_conflict: :overwrite)
398
- expect(job3).to eq(job1)
399
- expect(job3.run_at.to_i).to eq(t2.to_i)
400
- end
401
-
402
- it "updates existing singleton job handler if requested" do
403
- job1 = Delayed::Job.enqueue(SimpleJob.new, queue: nil, singleton: "myjobs", on_conflict: :overwrite)
404
- job2 = Delayed::Job.enqueue(ErrorJob.new, queue: nil, singleton: "myjobs", on_conflict: :overwrite)
405
- expect(job2).to eq(job1)
406
- expect(job1.reload.handler).to include("ErrorJob")
407
- end
408
-
409
- context "next_in_strand management - deadlocks and race conditions", non_transactional: true, slow: true do
410
- # The following unit tests are fairly slow and non-deterministic. It may be
411
- # easier to make them fail quicker and more consistently by adding a random
412
- # sleep into the appropriate trigger(s).
413
-
414
- def loop_secs(val)
415
- loop_start = Time.now.utc
416
-
417
- loop do
418
- break if Time.now.utc >= loop_start + val
419
-
420
- yield
421
- end
422
- end
423
-
424
- def loop_until_found(params)
425
- found = false
426
-
427
- loop_secs(10.seconds) do
428
- if Delayed::Job.exists?(**params)
429
- found = true
430
- break
431
- end
432
- end
433
-
434
- raise "timed out waiting for condition" unless found
435
- end
436
-
437
- def thread_body
438
- yield
439
- rescue
440
- Thread.current.thread_variable_set(:fail, true)
441
- raise
442
- end
443
-
444
- it "doesn't orphan the singleton when two are queued consecutively" do
445
- # In order to reproduce this one efficiently, you'll probably want to add
446
- # a sleep within delayed_jobs_before_insert_row_tr_fn.
447
- # IF NEW.singleton IS NOT NULL THEN
448
- # ...
449
- # PERFORM pg_sleep(random() * 2);
450
- # END IF;
451
-
452
- threads = []
453
-
454
- threads << Thread.new do
455
- thread_body do
456
- loop do
457
- create_job(singleton: "singleton_job")
458
- create_job(singleton: "singleton_job")
459
- end
460
- end
461
- end
462
-
463
- threads << Thread.new do
464
- thread_body do
465
- loop do
466
- Delayed::Job.get_and_lock_next_available("w1")&.destroy
467
- end
468
- end
469
- end
470
-
471
- threads << Thread.new do
472
- thread_body do
473
- loop do
474
- loop_until_found(singleton: "singleton_job", next_in_strand: true)
475
- end
476
- end
477
- end
478
-
479
- begin
480
- loop_secs(60.seconds) do
481
- if threads.any? { |x| x.thread_variable_get(:fail) }
482
- raise "at least one job became orphaned or other error"
483
- end
484
- end
485
- ensure
486
- threads.each(&:kill)
487
- threads.each(&:join)
488
- end
489
- end
490
-
491
- it "doesn't deadlock when transitioning from strand_a to strand_b" do
492
- # In order to reproduce this one efficiently, you'll probably want to add
493
- # a sleep within delayed_jobs_after_delete_row_tr_fn.
494
- # PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
495
- # PERFORM pg_sleep(random() * 2);
496
-
497
- threads = []
498
-
499
- threads << Thread.new do
500
- thread_body do
501
- loop do
502
- j1 = create_job(singleton: "myjobs", strand: "myjobs2", locked_by: "w1")
503
- j2 = create_job(singleton: "myjobs", strand: "myjobs")
504
-
505
- j1.delete
506
- j2.delete
507
- end
508
- end
509
- end
510
-
511
- threads << Thread.new do
512
- thread_body do
513
- loop do
514
- j1 = create_job(singleton: "myjobs2", strand: "myjobs", locked_by: "w1")
515
- j2 = create_job(singleton: "myjobs2", strand: "myjobs2")
516
-
517
- j1.delete
518
- j2.delete
519
- end
520
- end
521
- end
522
-
523
- threads << Thread.new do
524
- thread_body do
525
- loop do
526
- loop_until_found(singleton: "myjobs", next_in_strand: true)
527
- end
528
- end
529
- end
530
-
531
- threads << Thread.new do
532
- thread_body do
533
- loop do
534
- loop_until_found(singleton: "myjobs2", next_in_strand: true)
535
- end
536
- end
537
- end
538
-
539
- begin
540
- loop_secs(60.seconds) do
541
- if threads.any? { |x| x.thread_variable_get(:fail) }
542
- raise "at least one thread hit a deadlock or other error"
543
- end
544
- end
545
- ensure
546
- threads.each(&:kill)
547
- threads.each(&:join)
548
- end
549
- end
550
- end
551
-
552
- context "next_in_strand management" do
553
- it "handles transitions correctly when going from stranded to not stranded" do
554
- @job1 = create_job(singleton: "myjobs", strand: "myjobs")
555
- Delayed::Job.get_and_lock_next_available("w1")
556
- @job2 = create_job(singleton: "myjobs")
557
-
558
- expect(@job1.reload.next_in_strand).to be true
559
- expect(@job2.reload.next_in_strand).to be false
560
-
561
- @job1.destroy
562
- expect(@job2.reload.next_in_strand).to be true
563
- end
564
-
565
- it "handles transitions correctly when going from not stranded to stranded" do
566
- @job1 = create_job(singleton: "myjobs2", strand: "myjobs")
567
- @job2 = create_job(singleton: "myjobs")
568
- Delayed::Job.get_and_lock_next_available("w1")
569
- Delayed::Job.get_and_lock_next_available("w1")
570
- @job3 = create_job(singleton: "myjobs", strand: "myjobs2")
571
-
572
- expect(@job1.reload.next_in_strand).to be true
573
- expect(@job2.reload.next_in_strand).to be true
574
- expect(@job3.reload.next_in_strand).to be false
575
-
576
- @job2.destroy
577
- expect(@job1.reload.next_in_strand).to be true
578
- expect(@job3.reload.next_in_strand).to be true
579
- end
580
-
581
- it "does not violate n_strand=1 constraints when going from not stranded to stranded" do
582
- @job1 = create_job(singleton: "myjobs2", strand: "myjobs")
583
- @job2 = create_job(singleton: "myjobs")
584
- Delayed::Job.get_and_lock_next_available("w1")
585
- Delayed::Job.get_and_lock_next_available("w1")
586
- @job3 = create_job(singleton: "myjobs", strand: "myjobs")
587
-
588
- expect(@job1.reload.next_in_strand).to be true
589
- expect(@job2.reload.next_in_strand).to be true
590
- expect(@job3.reload.next_in_strand).to be false
591
-
592
- @job2.destroy
593
- expect(@job1.reload.next_in_strand).to be true
594
- expect(@job3.reload.next_in_strand).to be false
595
- end
596
-
597
- it "handles transitions correctly when going from stranded to another strand" do
598
- @job1 = create_job(singleton: "myjobs", strand: "myjobs")
599
- Delayed::Job.get_and_lock_next_available("w1")
600
- @job2 = create_job(singleton: "myjobs", strand: "myjobs2")
601
-
602
- expect(@job1.reload.next_in_strand).to be true
603
- expect(@job2.reload.next_in_strand).to be false
604
-
605
- @job1.destroy
606
- expect(@job2.reload.next_in_strand).to be true
607
- end
608
-
609
- it "does not violate n_strand=1 constraints when going from stranded to another strand" do
610
- @job1 = create_job(singleton: "myjobs2", strand: "myjobs2")
611
- @job2 = create_job(singleton: "myjobs", strand: "myjobs")
612
- Delayed::Job.get_and_lock_next_available("w1")
613
- Delayed::Job.get_and_lock_next_available("w1")
614
- @job3 = create_job(singleton: "myjobs", strand: "myjobs2")
615
-
616
- expect(@job1.reload.next_in_strand).to be true
617
- expect(@job2.reload.next_in_strand).to be true
618
- expect(@job3.reload.next_in_strand).to be false
619
-
620
- @job2.destroy
621
- expect(@job1.reload.next_in_strand).to be true
622
- expect(@job3.reload.next_in_strand).to be false
623
- end
624
-
625
- it "creates first as true, and second as false, then transitions to second when deleted" do
626
- @job1 = create_job(singleton: "myjobs")
627
- Delayed::Job.get_and_lock_next_available("w1")
628
- @job2 = create_job(singleton: "myjobs")
629
- expect(@job1.reload.next_in_strand).to be true
630
- expect(@job2.reload.next_in_strand).to be false
631
-
632
- @job1.destroy
633
- expect(@job2.reload.next_in_strand).to be true
634
- end
635
-
636
- it "when combined with a strand" do
637
- job1 = create_job(singleton: "singleton", strand: "strand")
638
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job1
639
- job2 = create_job(singleton: "singleton", strand: "strand")
640
- expect(job2).not_to eq job1
641
- expect(job2).not_to be_new_record
642
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
643
- job3 = create_job(strand: "strand")
644
- job4 = create_job(strand: "strand")
645
- expect(job3.reload).not_to be_next_in_strand
646
- expect(job4.reload).not_to be_next_in_strand
647
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
648
- job1.destroy
649
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job2
650
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
651
- job2.destroy
652
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job3
653
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
654
- job3.destroy
655
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job4
656
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
657
- end
658
-
659
- it "when combined with a small n_strand" do
660
- allow(Delayed::Settings).to receive(:num_strands).and_return(->(*) { 2 })
661
-
662
- job1 = create_job(singleton: "singleton", n_strand: "strand")
663
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job1
664
- job2 = create_job(singleton: "singleton", n_strand: "strand")
665
- expect(job2).not_to eq job1
666
- expect(job2).not_to be_new_record
667
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
668
- job3 = create_job(n_strand: "strand")
669
- job4 = create_job(n_strand: "strand")
670
- expect(job3.reload).to be_next_in_strand
671
- expect(job4.reload).not_to be_next_in_strand
672
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job3
673
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
674
- # this doesn't unlock job2, even though it's ahead of job4
675
- job3.destroy
676
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job4
677
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
678
- job4.destroy
679
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
680
- job1.destroy
681
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job2
682
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
683
- end
684
-
685
- it "when combined with a larger n_strand" do
686
- allow(Delayed::Settings).to receive(:num_strands).and_return(->(*) { 10 })
687
-
688
- job1 = create_job(singleton: "singleton", n_strand: "strand")
689
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job1
690
- job2 = create_job(singleton: "singleton", n_strand: "strand")
691
- expect(job2).not_to eq job1
692
- expect(job2).not_to be_new_record
693
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
694
- job3 = create_job(n_strand: "strand")
695
- job4 = create_job(n_strand: "strand")
696
- expect(job3.reload).to be_next_in_strand
697
- expect(job4.reload).to be_next_in_strand
698
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job3
699
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job4
700
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
701
- # this doesn't unlock job2
702
- job3.destroy
703
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
704
- job4.destroy
705
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
706
- job1.destroy
707
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq job2
708
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
709
- end
710
- end
711
-
712
- context "with on_conflict: loose and strand-inferred-from-singleton" do
713
- around do |example|
714
- Delayed::Settings.infer_strand_from_singleton = true
715
- example.call
716
- ensure
717
- Delayed::Settings.infer_strand_from_singleton = false
718
- end
719
-
720
- it "does not create if there's another non-running job on the strand" do
721
- @job = create_job(singleton: "myjobs", on_conflict: :loose)
722
- expect(@job).to be_present
723
-
724
- @job2 = create_job(singleton: "myjobs", on_conflict: :loose)
725
- expect(@job2).to be_new_record
726
- end
727
- end
728
-
729
- context "when unlocking with another singleton pending" do
730
- it "deletes the pending singleton" do
731
- @job1 = create_job(singleton: "myjobs", max_attempts: 2)
732
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq(@job1)
733
-
734
- @job2 = create_job(singleton: "myjobs", max_attempts: 2)
735
-
736
- @job1.reload.reschedule
737
- expect { @job1.reload }.not_to raise_error
738
- expect { @job2.reload }.to raise_error(ActiveRecord::RecordNotFound)
739
- end
740
- end
741
- end
742
- end
743
-
744
- context "on hold" do
745
- it "hold/unholds jobs" do
746
- job1 = create_job
747
- job1.hold!
748
- expect(Delayed::Job.get_and_lock_next_available("w1")).to be_nil
749
-
750
- job1.unhold!
751
- expect(Delayed::Job.get_and_lock_next_available("w1")).to eq(job1)
752
- end
753
- end
754
-
755
- context "periodic jobs" do
756
- before do
757
- # make the periodic job get scheduled in the past
758
- @cron_time = 10.minutes.ago
759
- allow(Delayed::Periodic).to receive(:now).and_return(@cron_time)
760
- Delayed::Periodic.scheduled = {}
761
- Delayed::Periodic.cron("my SimpleJob", "*/5 * * * * *") do
762
- Delayed::Job.enqueue(SimpleJob.new)
763
- end
764
- end
765
-
766
- it "schedules jobs if they aren't scheduled yet" do
767
- expect(Delayed::Job.jobs_count(:current)).to eq(0)
768
- Delayed::Periodic.perform_audit!
769
- expect(Delayed::Job.jobs_count(:current)).to eq(1)
770
- job = Delayed::Job.get_and_lock_next_available("test1")
771
- expect(job.tag).to eq("periodic: my SimpleJob")
772
- expect(job.payload_object).to eq(Delayed::Periodic.scheduled["my SimpleJob"])
773
- expect(job.run_at).to be >= @cron_time
774
- expect(job.run_at).to be <= @cron_time + 6.minutes
775
- expect(job.singleton).to eq(job.tag)
776
- end
777
-
778
- it "schedules jobs if there are only failed jobs on the queue" do
779
- expect(Delayed::Job.jobs_count(:current)).to eq(0)
780
- expect { Delayed::Periodic.perform_audit! }.to change { Delayed::Job.jobs_count(:current) }.by(1)
781
- expect(Delayed::Job.jobs_count(:current)).to eq(1)
782
- job = Delayed::Job.get_and_lock_next_available("test1")
783
- job.fail!
784
- expect { Delayed::Periodic.perform_audit! }.to change { Delayed::Job.jobs_count(:current) }.by(1)
785
- end
786
-
787
- it "does not schedule jobs that are already scheduled" do
788
- expect(Delayed::Job.jobs_count(:current)).to eq(0)
789
- Delayed::Periodic.perform_audit!
790
- expect(Delayed::Job.jobs_count(:current)).to eq(1)
791
- job = Delayed::Job.find_available(1).first
792
- Delayed::Periodic.perform_audit!
793
- expect(Delayed::Job.jobs_count(:current)).to eq(1)
794
- # verify that the same job still exists, it wasn't just replaced with a new one
795
- expect(job).to eq(Delayed::Job.find_available(1).first)
796
- end
797
-
798
- it "schedules the next job run after performing" do
799
- expect(Delayed::Job.jobs_count(:current)).to eq(0)
800
- Delayed::Periodic.perform_audit!
801
- expect(Delayed::Job.jobs_count(:current)).to eq(1)
802
- job = Delayed::Job.get_and_lock_next_available("test")
803
- run_job(job)
804
-
805
- job = Delayed::Job.get_and_lock_next_available("test1")
806
- expect(job.tag).to eq("SimpleJob#perform")
807
-
808
- next_scheduled = Delayed::Job.get_and_lock_next_available("test2")
809
- expect(next_scheduled.tag).to eq("periodic: my SimpleJob")
810
- expect(next_scheduled.payload_object).to be_is_a(Delayed::Periodic)
811
- end
812
-
813
- it "rejects duplicate named jobs" do
814
- expect { Delayed::Periodic.cron("my SimpleJob", "*/15 * * * * *") { nil } }.to raise_error(ArgumentError)
815
- end
816
-
817
- it "handles jobs that are no longer scheduled" do
818
- Delayed::Periodic.perform_audit!
819
- Delayed::Periodic.scheduled = {}
820
- job = Delayed::Job.get_and_lock_next_available("test")
821
- run_job(job)
822
- # shouldn't error, and the job should now be deleted
823
- expect(Delayed::Job.jobs_count(:current)).to eq(0)
824
- end
825
-
826
- it "allows overriding schedules using periodic_jobs.yml" do
827
- change_setting(Delayed::Periodic, :overrides, { "my ChangedJob" => "*/10 * * * * *" }) do
828
- Delayed::Periodic.scheduled = {}
829
- Delayed::Periodic.cron("my ChangedJob", "*/5 * * * * *") do
830
- Delayed::Job.enqueue(SimpleJob.new)
831
- end
832
- expect(Delayed::Periodic.scheduled["my ChangedJob"].cron.original).to eq("*/10 * * * * *")
833
- end
834
- end
835
-
836
- it "fails if the override cron line is invalid" do
837
- change_setting(Delayed::Periodic, :overrides, { "my ChangedJob" => "*/10 * * * * * *" }) do # extra asterisk
838
- Delayed::Periodic.scheduled = {}
839
- expect do
840
- Delayed::Periodic.cron("my ChangedJob", "*/5 * * * * *") do
841
- Delayed::Job.enqueue(SimpleJob.new)
842
- end
843
- end.to raise_error(ArgumentError)
844
- end
845
-
846
- expect do
847
- Delayed::Periodic.add_overrides({ "my ChangedJob" => "*/10 * * * * * *" })
848
- end.to raise_error(ArgumentError)
849
- end
850
- end
851
-
852
- it "sets in_delayed_job?" do
853
- job = InDelayedJobTest.delay(ignore_transaction: true).check_in_job
854
- expect(Delayed::Job.in_delayed_job?).to be(false)
855
- job.invoke_job
856
- expect(Delayed::Job.in_delayed_job?).to be(false)
857
- end
858
-
859
- it "fails on job creation if an unsaved AR object is used" do
860
- story = Story.new text: "Once upon..."
861
- expect { story.delay.text }.to raise_error(RuntimeError)
862
-
863
- reader = StoryReader.new
864
- expect { reader.delay.read(story) }.to raise_error(RuntimeError)
865
-
866
- expect { [story, 1, story, false].delay.first }.to raise_error(RuntimeError)
867
- end
868
-
869
- # the sort order of current_jobs and list_jobs depends on the back-end
870
- # implementation, so sort order isn't tested in these specs
871
- describe "current jobs, queue size, strand_size" do
872
- before do
873
- @jobs = []
874
- 3.times { @jobs << create_job(priority: 3) }
875
- @jobs.unshift create_job(priority: 2)
876
- @jobs.unshift create_job(priority: 1)
877
- @jobs << create_job(priority: 3, strand: "test1")
878
- @future_job = create_job(run_at: 5.hours.from_now)
879
- 2.times { @jobs << create_job(priority: 3) }
880
- @jobs << create_job(priority: 3, strand: "test1")
881
- @failed_job = create_job.tap(&:fail!)
882
- @other_queue_job = create_job(queue: "another")
883
- end
884
-
885
- it "returns the queued jobs" do
886
- expect(Delayed::Job.list_jobs(:current, 100).map(&:id).sort).to eq(@jobs.map(&:id).sort)
887
- end
888
-
889
- it "paginates the returned jobs" do
890
- @returned = []
891
- @returned += Delayed::Job.list_jobs(:current, 3, 0)
892
- @returned += Delayed::Job.list_jobs(:current, 4, 3)
893
- @returned += Delayed::Job.list_jobs(:current, 100, 7)
894
- expect(@returned.sort_by(&:id)).to eq(@jobs.sort_by(&:id))
895
- end
896
-
897
- it "returns other queues" do
898
- expect(Delayed::Job.list_jobs(:current, 5, 0, "another")).to eq([@other_queue_job])
899
- end
900
-
901
- it "returns queue size" do
902
- expect(Delayed::Job.jobs_count(:current)).to eq(@jobs.size)
903
- expect(Delayed::Job.jobs_count(:current, "another")).to eq(1)
904
- expect(Delayed::Job.jobs_count(:current, "bogus")).to eq(0)
905
- end
906
-
907
- it "returns strand size" do
908
- expect(Delayed::Job.strand_size("test1")).to eq(2)
909
- expect(Delayed::Job.strand_size("bogus")).to eq(0)
910
- end
911
- end
912
-
913
- it "returns the jobs in a strand" do
914
- strand_jobs = []
915
- 3.times { strand_jobs << create_job(strand: "test1") }
916
- 2.times { create_job(strand: "test2") }
917
- strand_jobs << create_job(strand: "test1", run_at: 5.hours.from_now)
918
- create_job
919
-
920
- jobs = Delayed::Job.list_jobs(:strand, 3, 0, "test1")
921
- expect(jobs.size).to eq(3)
922
-
923
- jobs += Delayed::Job.list_jobs(:strand, 3, 3, "test1")
924
- expect(jobs.size).to eq(4)
925
-
926
- expect(jobs.sort_by(&:id)).to eq(strand_jobs.sort_by(&:id))
927
- end
928
-
929
- it "returns the jobs for a tag" do
930
- tag_jobs = []
931
- 3.times { tag_jobs << "test".delay(ignore_transaction: true).to_s }
932
- 2.times { "test".delay.to_i }
933
- tag_jobs << "test".delay(ignore_transaction: true, run_at: 5.hours.from_now).to_s
934
- tag_jobs << "test".delay(ignore_transaction: true, strand: "test1").to_s
935
- "test".delay(strand: "test1").to_i
936
- create_job
937
-
938
- jobs = Delayed::Job.list_jobs(:tag, 3, 0, "String#to_s")
939
- expect(jobs.size).to eq(3)
940
-
941
- jobs += Delayed::Job.list_jobs(:tag, 3, 3, "String#to_s")
942
- expect(jobs.size).to eq(5)
943
-
944
- expect(jobs.sort_by(&:id)).to eq(tag_jobs.sort_by(&:id))
945
- end
946
-
947
- describe "running_jobs" do
948
- it "returns the running jobs, ordered by locked_at" do
949
- Timecop.freeze(10.minutes.ago) { 3.times { create_job } }
950
- j1 = Timecop.freeze(2.minutes.ago) { Delayed::Job.get_and_lock_next_available("w1") }
951
- j2 = Timecop.freeze(5.minutes.ago) { Delayed::Job.get_and_lock_next_available("w2") }
952
- j3 = Timecop.freeze(5.seconds.ago) { Delayed::Job.get_and_lock_next_available("w3") }
953
- expect([j1, j2, j3].compact.size).to eq(3)
954
-
955
- expect(Delayed::Job.running_jobs).to eq([j2, j1, j3])
956
- end
957
- end
958
-
959
- describe "future jobs" do
960
- it "finds future jobs once their run_at rolls by" do
961
- Timecop.freeze do
962
- @job = create_job run_at: 5.minutes.from_now
963
- expect(Delayed::Job.find_available(5)).not_to include(@job)
964
- end
965
- Timecop.freeze(1.hour.from_now) do
966
- expect(Delayed::Job.find_available(5)).to include(@job)
967
- expect(Delayed::Job.get_and_lock_next_available("test")).to eq(@job)
968
- end
969
- end
970
-
971
- it "returns future jobs sorted by their run_at" do
972
- @j1 = create_job
973
- @j2 = create_job run_at: 1.hour.from_now
974
- @j3 = create_job run_at: 30.minutes.from_now
975
- expect(Delayed::Job.list_jobs(:future, 1)).to eq([@j3])
976
- expect(Delayed::Job.list_jobs(:future, 5)).to eq([@j3, @j2])
977
- expect(Delayed::Job.list_jobs(:future, 1, 1)).to eq([@j2])
978
- end
979
- end
980
-
981
- describe "failed jobs" do
982
- # the sort order of failed_jobs depends on the back-end implementation,
983
- # so sort order isn't tested here
984
- it "returns the list of failed jobs" do
985
- jobs = []
986
- 3.times { jobs << create_job(priority: 3) }
987
- jobs = jobs.sort_by(&:id)
988
- expect(Delayed::Job.list_jobs(:failed, 1)).to eq([])
989
- jobs[0].fail!
990
- jobs[1].fail!
991
- failed = (Delayed::Job.list_jobs(:failed, 1, 0) + Delayed::Job.list_jobs(:failed, 1, 1)).sort_by(&:id)
992
- expect(failed.size).to eq(2)
993
- expect(failed[0].original_job_id).to eq(jobs[0].id)
994
- expect(failed[1].original_job_id).to eq(jobs[1].id)
995
- end
996
- end
997
-
998
- describe "bulk_update" do
999
- shared_examples_for "scope" do
1000
- before do
1001
- @affected_jobs = []
1002
- @ignored_jobs = []
1003
- end
1004
-
1005
- it "holds and unhold a scope of jobs" do
1006
- expect(@affected_jobs.all?(&:on_hold?)).to be false
1007
- expect(@ignored_jobs.any?(&:on_hold?)).to be false
1008
- expect(Delayed::Job.bulk_update("hold", flavor: @flavor, query: @query)).to eq(@affected_jobs.size)
1009
-
1010
- expect(@affected_jobs.all? { |j| Delayed::Job.find(j.id).on_hold? }).to be true
1011
- expect(@ignored_jobs.any? { |j| Delayed::Job.find(j.id).on_hold? }).to be false
1012
-
1013
- expect(Delayed::Job.bulk_update("unhold", flavor: @flavor, query: @query)).to eq(@affected_jobs.size)
1014
-
1015
- expect(@affected_jobs.any? { |j| Delayed::Job.find(j.id).on_hold? }).to be false
1016
- expect(@ignored_jobs.any? { |j| Delayed::Job.find(j.id).on_hold? }).to be false
1017
- end
1018
-
1019
- it "deletes a scope of jobs" do
1020
- expect(Delayed::Job.bulk_update("destroy", flavor: @flavor, query: @query)).to eq(@affected_jobs.size)
1021
- expect(Delayed::Job.where(id: @affected_jobs.map(&:id))).not_to exist
1022
- expect(Delayed::Job.where(id: @ignored_jobs.map(&:id)).count).to eq @ignored_jobs.size
1023
- end
1024
- end
1025
-
1026
- describe "scope: current" do
1027
- include_examples "scope"
1028
- before do # rubocop:disable RSpec/HooksBeforeExamples
1029
- @flavor = "current"
1030
- Timecop.freeze(5.minutes.ago) do
1031
- 3.times { @affected_jobs << create_job }
1032
- @ignored_jobs << create_job(run_at: 2.hours.from_now)
1033
- @ignored_jobs << create_job(queue: "q2")
1034
- end
1035
- end
1036
- end
1037
-
1038
- describe "scope: future" do
1039
- include_examples "scope"
1040
- before do # rubocop:disable RSpec/HooksBeforeExamples
1041
- @flavor = "future"
1042
- Timecop.freeze(5.minutes.ago) do
1043
- 3.times { @affected_jobs << create_job(run_at: 2.hours.from_now) }
1044
- @ignored_jobs << create_job
1045
- @ignored_jobs << create_job(queue: "q2", run_at: 2.hours.from_now)
1046
- end
1047
- end
1048
- end
1049
-
1050
- describe "scope: strand" do
1051
- include_examples "scope"
1052
- before do # rubocop:disable RSpec/HooksBeforeExamples
1053
- @flavor = "strand"
1054
- @query = "s1"
1055
- Timecop.freeze(5.minutes.ago) do
1056
- @affected_jobs << create_job(strand: "s1")
1057
- @affected_jobs << create_job(strand: "s1", run_at: 2.hours.from_now)
1058
- @ignored_jobs << create_job
1059
- @ignored_jobs << create_job(strand: "s2")
1060
- @ignored_jobs << create_job(strand: "s2", run_at: 2.hours.from_now)
1061
- end
1062
- end
1063
- end
1064
-
1065
- describe "scope: tag" do
1066
- include_examples "scope"
1067
- before do # rubocop:disable RSpec/HooksBeforeExamples
1068
- @flavor = "tag"
1069
- @query = "String#to_i"
1070
- Timecop.freeze(5.minutes.ago) do
1071
- @affected_jobs << "test".delay(ignore_transaction: true).to_i
1072
- @affected_jobs << "test".delay(strand: "s1", ignore_transaction: true).to_i
1073
- @affected_jobs << "test".delay(run_at: 2.hours.from_now, ignore_transaction: true).to_i
1074
- @ignored_jobs << create_job
1075
- @ignored_jobs << create_job(run_at: 1.hour.from_now)
1076
- end
1077
- end
1078
- end
1079
-
1080
- it "holds and un-hold given job ids" do
1081
- j1 = "test".delay(ignore_transaction: true).to_i
1082
- j2 = create_job(run_at: 2.hours.from_now)
1083
- j3 = "test".delay(strand: "s1", ignore_transaction: true).to_i
1084
- expect(Delayed::Job.bulk_update("hold", ids: [j1.id, j2.id])).to eq(2)
1085
- expect(Delayed::Job.find(j1.id).on_hold?).to be true
1086
- expect(Delayed::Job.find(j2.id).on_hold?).to be true
1087
- expect(Delayed::Job.find(j3.id).on_hold?).to be false
1088
-
1089
- expect(Delayed::Job.bulk_update("unhold", ids: [j2.id, j3.id])).to eq(1)
1090
- expect(Delayed::Job.find(j1.id).on_hold?).to be true
1091
- expect(Delayed::Job.find(j2.id).on_hold?).to be false
1092
- expect(Delayed::Job.find(j3.id).on_hold?).to be false
1093
- end
1094
-
1095
- it "does not hold locked jobs" do
1096
- job1 = Delayed::Job.new(tag: "tag")
1097
- job1.create_and_lock!("worker")
1098
- expect(job1.on_hold?).to be false
1099
- expect(Delayed::Job.bulk_update("hold", ids: [job1.id])).to eq(0)
1100
- expect(Delayed::Job.find(job1.id).on_hold?).to be false
1101
- end
1102
-
1103
- it "does not unhold locked jobs" do
1104
- job1 = Delayed::Job.new(tag: "tag")
1105
- job1.create_and_lock!("worker")
1106
- expect(Delayed::Job.bulk_update("unhold", ids: [job1.id])).to eq(0)
1107
- expect(Delayed::Job.find(job1.id).on_hold?).to be false
1108
- expect(Delayed::Job.find(job1.id).locked?).to be true
1109
- end
1110
-
1111
- it "deletes given job ids" do
1112
- jobs = (0..2).map { create_job }
1113
- expect(Delayed::Job.bulk_update("destroy", ids: jobs[0, 2].map(&:id))).to eq(2)
1114
- expect(Delayed::Job.order(:id).where(id: jobs.map(&:id))).to eq jobs[2, 1]
1115
- end
1116
-
1117
- it "does not delete locked jobs" do
1118
- job1 = Delayed::Job.new(tag: "tag")
1119
- job1.create_and_lock!("worker")
1120
- expect(Delayed::Job.bulk_update("destroy", ids: [job1.id])).to eq(0)
1121
- expect(Delayed::Job.find(job1.id).locked?).to be true
1122
- end
1123
- end
1124
-
1125
- describe "tag_counts" do
1126
- before do
1127
- @cur = []
1128
- 3.times { @cur << "test".delay(ignore_transaction: true).to_s }
1129
- 5.times { @cur << "test".delay(ignore_transaction: true).to_i }
1130
- 2.times { @cur << "test".delay(ignore_transaction: true).upcase }
1131
- "test".delay(ignore_transaction: true).downcase.fail!
1132
- @future = []
1133
- 5.times { @future << "test".delay(run_at: 3.hours.from_now, ignore_transaction: true).downcase }
1134
- @cur << "test".delay(ignore_transaction: true).downcase
1135
- end
1136
-
1137
- it "returns a sorted list of popular current tags" do
1138
- expect(Delayed::Job.tag_counts(:current, 1)).to eq([{ tag: "String#to_i", count: 5 }])
1139
- expect(Delayed::Job.tag_counts(:current, 1, 1)).to eq([{ tag: "String#to_s", count: 3 }])
1140
- expect(Delayed::Job.tag_counts(:current, 5)).to eq([{ tag: "String#to_i", count: 5 },
1141
- { tag: "String#to_s", count: 3 },
1142
- { tag: "String#upcase", count: 2 },
1143
- { tag: "String#downcase", count: 1 }])
1144
- @cur[0, 4].each(&:destroy)
1145
- @future[0].run_at = @future[1].run_at = 1.hour.ago
1146
- @future[0].save!
1147
- @future[1].save!
1148
-
1149
- expect(Delayed::Job.tag_counts(:current, 5)).to eq([{ tag: "String#to_i", count: 4 },
1150
- { tag: "String#downcase", count: 3 },
1151
- { tag: "String#upcase", count: 2 }])
1152
- end
1153
-
1154
- it "returns a sorted list of all popular tags" do
1155
- expect(Delayed::Job.tag_counts(:all, 1)).to eq([{ tag: "String#downcase", count: 6 }])
1156
- expect(Delayed::Job.tag_counts(:all, 1, 1)).to eq([{ tag: "String#to_i", count: 5 }])
1157
- expect(Delayed::Job.tag_counts(:all, 5)).to eq([{ tag: "String#downcase", count: 6 },
1158
- { tag: "String#to_i", count: 5 },
1159
- { tag: "String#to_s", count: 3 },
1160
- { tag: "String#upcase", count: 2 }])
1161
-
1162
- @cur[0, 4].each(&:destroy)
1163
- @future[0].destroy
1164
- @future[1].fail!
1165
- @future[2].fail!
1166
-
1167
- expect(Delayed::Job.tag_counts(:all, 5)).to eq([{ tag: "String#to_i", count: 4 },
1168
- { tag: "String#downcase", count: 3 },
1169
- { tag: "String#upcase", count: 2 }])
1170
- end
1171
- end
1172
-
1173
- it "unlocks orphaned jobs" do
1174
- change_setting(Delayed::Settings, :max_attempts, 2) do
1175
- job1 = Delayed::Job.new(tag: "tag")
1176
- job2 = Delayed::Job.new(tag: "tag")
1177
- job3 = Delayed::Job.new(tag: "tag")
1178
- job4 = Delayed::Job.new(tag: "tag")
1179
- job1.create_and_lock!("Jobworker:#{Process.pid}")
1180
- `echo ''`
1181
- child_pid = $?.pid
1182
- job2.create_and_lock!("Jobworker:#{child_pid}")
1183
- job3.create_and_lock!("someoneelse:#{Process.pid}")
1184
- job4.create_and_lock!("Jobworker:notanumber")
1185
-
1186
- expect(Delayed::Job.unlock_orphaned_jobs(nil, "Jobworker")).to eq(1)
1187
-
1188
- expect(Delayed::Job.find(job1.id).locked_by).not_to be_nil
1189
- expect(Delayed::Job.find(job2.id).locked_by).to be_nil
1190
- expect(Delayed::Job.find(job3.id).locked_by).not_to be_nil
1191
- expect(Delayed::Job.find(job4.id).locked_by).not_to be_nil
1192
-
1193
- expect(Delayed::Job.unlock_orphaned_jobs(nil, "Jobworker")).to eq(0)
1194
- end
1195
- end
1196
-
1197
- it "removes an un-reschedulable job" do
1198
- change_setting(Delayed::Settings, :max_attempts, -1) do
1199
- job = Delayed::Job.new(tag: "tag")
1200
- `echo ''`
1201
- child_pid = $?.pid
1202
- job.create_and_lock!("Jobworker:#{child_pid}")
1203
- Timeout.timeout(1) do
1204
- # if this takes longer than a second it's hung
1205
- # in an infinite loop, which would be bad.
1206
- expect(Delayed::Job.unlock_orphaned_jobs(nil, "Jobworker")).to eq(1)
1207
- end
1208
- expect { Delayed::Job.find(job.id) }.to raise_error(ActiveRecord::RecordNotFound)
1209
- end
1210
- end
1211
-
1212
- it "unlocks orphaned jobs given a pid" do
1213
- change_setting(Delayed::Settings, :max_attempts, 2) do
1214
- job1 = Delayed::Job.new(tag: "tag")
1215
- job2 = Delayed::Job.new(tag: "tag")
1216
- job3 = Delayed::Job.new(tag: "tag")
1217
- job4 = Delayed::Job.new(tag: "tag")
1218
- job1.create_and_lock!("Jobworker:#{Process.pid}")
1219
- `echo ''`
1220
- child_pid = $?.pid
1221
- `echo ''`
1222
- child_pid2 = $?.pid
1223
- job2.create_and_lock!("Jobworker:#{child_pid}")
1224
- job3.create_and_lock!("someoneelse:#{Process.pid}")
1225
- job4.create_and_lock!("Jobworker:notanumber")
1226
-
1227
- expect(Delayed::Job.unlock_orphaned_jobs(child_pid2, "Jobworker")).to eq(0)
1228
- expect(Delayed::Job.unlock_orphaned_jobs(child_pid, "Jobworker")).to eq(1)
1229
-
1230
- expect(Delayed::Job.find(job1.id).locked_by).not_to be_nil
1231
- expect(Delayed::Job.find(job2.id).locked_by).to be_nil
1232
- expect(Delayed::Job.find(job3.id).locked_by).not_to be_nil
1233
- expect(Delayed::Job.find(job4.id).locked_by).not_to be_nil
1234
-
1235
- expect(Delayed::Job.unlock_orphaned_jobs(child_pid, "Jobworker")).to eq(0)
1236
- end
1237
- end
1238
- end