inst-jobs 3.0.8 → 3.1.14

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