inst-jobs 2.0.0 → 3.1.0

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