delayed_job 2.1.0.pre → 2.1.0.pre2
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.
- data/README.textile +1 -16
- data/lib/delayed/backend/active_record.rb +12 -5
- data/lib/delayed/backend/base.rb +12 -3
- data/lib/delayed/backend/shared_spec.rb +472 -0
- data/lib/delayed/command.rb +4 -4
- data/lib/delayed/message_sending.rb +8 -6
- data/lib/delayed/performable_method.rb +9 -1
- data/lib/delayed/tasks.rb +2 -6
- data/lib/delayed/worker.rb +12 -12
- data/lib/delayed_job.rb +0 -4
- data/spec/{backend/active_record_job_spec.rb → active_record_job_spec.rb} +1 -11
- data/spec/message_sending_spec.rb +8 -1
- data/spec/performable_method_spec.rb +19 -1
- data/spec/sample_jobs.rb +36 -0
- data/spec/spec_helper.rb +35 -13
- data/spec/worker_spec.rb +14 -191
- data/spec/yaml_ext_spec.rb +16 -0
- metadata +24 -103
- data/.gitignore +0 -2
- data/Rakefile +0 -53
- data/VERSION +0 -1
- data/benchmarks.rb +0 -33
- data/delayed_job.gemspec +0 -125
- data/lib/delayed/backend/couch_rest.rb +0 -109
- data/lib/delayed/backend/data_mapper.rb +0 -121
- data/lib/delayed/backend/mongo_mapper.rb +0 -106
- data/spec/backend/couch_rest_job_spec.rb +0 -15
- data/spec/backend/data_mapper_job_spec.rb +0 -16
- data/spec/backend/mongo_mapper_job_spec.rb +0 -94
- data/spec/backend/shared_backend_spec.rb +0 -273
- data/spec/setup/active_record.rb +0 -33
- data/spec/setup/couch_rest.rb +0 -7
- data/spec/setup/data_mapper.rb +0 -8
- data/spec/setup/mongo_mapper.rb +0 -17
data/README.textile
CHANGED
@@ -40,7 +40,7 @@ After delayed_job is installed, you will need to setup the backend.
|
|
40
40
|
|
41
41
|
h2. Backends
|
42
42
|
|
43
|
-
delayed_job supports multiple backends for storing the job queue.
|
43
|
+
delayed_job supports multiple backends for storing the job queue. "See the wiki for other backends":http://wiki.github.com/collectiveidea/delayed_job/backends besides Active Record.
|
44
44
|
|
45
45
|
h3. Active Record
|
46
46
|
|
@@ -51,21 +51,6 @@ $ script/generate delayed_job
|
|
51
51
|
$ rake db:migrate
|
52
52
|
</pre>
|
53
53
|
|
54
|
-
h3. MongoMapper
|
55
|
-
|
56
|
-
<pre>
|
57
|
-
# config/initializers/delayed_job.rb
|
58
|
-
Delayed::Worker.backend = :mongo_mapper
|
59
|
-
</pre>
|
60
|
-
|
61
|
-
h3. DataMapper
|
62
|
-
|
63
|
-
<pre>
|
64
|
-
# config/initializers/delayed_job.rb
|
65
|
-
Delayed::Worker.backend = :data_mapper
|
66
|
-
Delayed::Worker.backend.auto_upgrade!
|
67
|
-
</pre>
|
68
|
-
|
69
54
|
h2. Queuing Jobs
|
70
55
|
|
71
56
|
Call @.delay.method(params)@ on any object and it will be processed in the background.
|
@@ -25,11 +25,18 @@ module Delayed
|
|
25
25
|
|
26
26
|
before_save :set_default_run_at
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
if ::ActiveRecord::VERSION::MAJOR >= 3
|
29
|
+
scope :ready_to_run, lambda {|worker_name, max_run_time|
|
30
|
+
where(['(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR locked_by = ?) AND failed_at IS NULL', db_time_now, db_time_now - max_run_time, worker_name])
|
31
|
+
}
|
32
|
+
scope :by_priority, order('priority ASC, run_at ASC')
|
33
|
+
else
|
34
|
+
named_scope :ready_to_run, lambda {|worker_name, max_run_time|
|
35
|
+
{:conditions => ['(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR locked_by = ?) AND failed_at IS NULL', db_time_now, db_time_now - max_run_time, worker_name]}
|
36
|
+
}
|
37
|
+
named_scope :by_priority, :order => 'priority ASC, run_at ASC'
|
38
|
+
end
|
39
|
+
|
33
40
|
def self.after_fork
|
34
41
|
::ActiveRecord::Base.connection.reconnect!
|
35
42
|
end
|
data/lib/delayed/backend/base.rb
CHANGED
@@ -16,7 +16,7 @@ module Delayed
|
|
16
16
|
raise ArgumentError, 'Cannot enqueue items which do not respond to perform'
|
17
17
|
end
|
18
18
|
|
19
|
-
priority = args.first ||
|
19
|
+
priority = args.first || Delayed::Worker.default_priority
|
20
20
|
run_at = args[1]
|
21
21
|
self.create(:payload_object => object, :priority => priority.to_i, :run_at => run_at)
|
22
22
|
end
|
@@ -62,7 +62,16 @@ module Delayed
|
|
62
62
|
|
63
63
|
# Moved into its own method so that new_relic can trace it.
|
64
64
|
def invoke_job
|
65
|
-
payload_object.
|
65
|
+
payload_object.before(self) if payload_object.respond_to?(:before)
|
66
|
+
begin
|
67
|
+
payload_object.perform
|
68
|
+
payload_object.success(self) if payload_object.respond_to?(:success)
|
69
|
+
rescue Exception => e
|
70
|
+
payload_object.failure(self, e) if payload_object.respond_to?(:failure)
|
71
|
+
raise e
|
72
|
+
ensure
|
73
|
+
payload_object.after(self) if payload_object.respond_to?(:after)
|
74
|
+
end
|
66
75
|
end
|
67
76
|
|
68
77
|
# Unlock this job (note: not saved to DB)
|
@@ -79,4 +88,4 @@ module Delayed
|
|
79
88
|
|
80
89
|
end
|
81
90
|
end
|
82
|
-
end
|
91
|
+
end
|
@@ -0,0 +1,472 @@
|
|
1
|
+
require File.expand_path('../../../../spec/sample_jobs', __FILE__)
|
2
|
+
|
3
|
+
shared_examples_for 'a delayed_job backend' do
|
4
|
+
def create_job(opts = {})
|
5
|
+
described_class.create(opts.merge(:payload_object => SimpleJob.new))
|
6
|
+
end
|
7
|
+
|
8
|
+
before do
|
9
|
+
Delayed::Worker.max_priority = nil
|
10
|
+
Delayed::Worker.min_priority = nil
|
11
|
+
Delayed::Worker.default_priority = 99
|
12
|
+
SimpleJob.runs = 0
|
13
|
+
described_class.delete_all
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should set run_at automatically if not set" do
|
17
|
+
described_class.create(:payload_object => ErrorJob.new ).run_at.should_not be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not set run_at automatically if already set" do
|
21
|
+
later = described_class.db_time_now + 5.minutes
|
22
|
+
described_class.create(:payload_object => ErrorJob.new, :run_at => later).run_at.should be_close(later, 1)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "enqueue" do
|
26
|
+
it "should raise ArgumentError when handler doesn't respond_to :perform" do
|
27
|
+
lambda { described_class.enqueue(Object.new) }.should raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should increase count after enqueuing items" do
|
31
|
+
described_class.enqueue SimpleJob.new
|
32
|
+
described_class.count.should == 1
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be able to set priority" do
|
36
|
+
@job = described_class.enqueue SimpleJob.new, 5
|
37
|
+
@job.priority.should == 5
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should use default priority when it is not set" do
|
41
|
+
@job = described_class.enqueue SimpleJob.new
|
42
|
+
@job.priority.should == 99
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should be able to set run_at" do
|
46
|
+
later = described_class.db_time_now + 5.minutes
|
47
|
+
@job = described_class.enqueue SimpleJob.new, 5, later
|
48
|
+
@job.run_at.should be_close(later, 1)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should work with jobs in modules" do
|
52
|
+
M::ModuleJob.runs = 0
|
53
|
+
job = described_class.enqueue M::ModuleJob.new
|
54
|
+
lambda { job.invoke_job }.should change { M::ModuleJob.runs }.from(0).to(1)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "callbacks" do
|
59
|
+
before(:each) do
|
60
|
+
SuccessfulCallbackJob.messages = []
|
61
|
+
FailureCallbackJob.messages = []
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should call before and after callbacks" do
|
65
|
+
job = described_class.enqueue(SuccessfulCallbackJob.new)
|
66
|
+
job.invoke_job
|
67
|
+
SuccessfulCallbackJob.messages.should == ["before perform", "perform", "success!", "after perform"]
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should call the after callback with an error" do
|
71
|
+
job = described_class.enqueue(FailureCallbackJob.new)
|
72
|
+
lambda {job.invoke_job}.should raise_error
|
73
|
+
FailureCallbackJob.messages.should == ["before perform", "error: RuntimeError", "after perform"]
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "payload_object" do
|
79
|
+
it "should raise a DeserializationError when the job class is totally unknown" do
|
80
|
+
job = described_class.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
|
81
|
+
lambda { job.payload_object }.should raise_error(Delayed::Backend::DeserializationError)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should raise a DeserializationError when the job struct is totally unknown" do
|
85
|
+
job = described_class.new :handler => "--- !ruby/struct:StructThatDoesNotExist {}"
|
86
|
+
lambda { job.payload_object }.should raise_error(Delayed::Backend::DeserializationError)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "find_available" do
|
91
|
+
it "should not find failed jobs" do
|
92
|
+
@job = create_job :attempts => 50, :failed_at => described_class.db_time_now
|
93
|
+
described_class.find_available('worker', 5, 1.second).should_not include(@job)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should not find jobs scheduled for the future" do
|
97
|
+
@job = create_job :run_at => (described_class.db_time_now + 1.minute)
|
98
|
+
described_class.find_available('worker', 5, 4.hours).should_not include(@job)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should not find jobs locked by another worker" do
|
102
|
+
@job = create_job(:locked_by => 'other_worker', :locked_at => described_class.db_time_now - 1.minute)
|
103
|
+
described_class.find_available('worker', 5, 4.hours).should_not include(@job)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should find open jobs" do
|
107
|
+
@job = create_job
|
108
|
+
described_class.find_available('worker', 5, 4.hours).should include(@job)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should find expired jobs" do
|
112
|
+
@job = create_job(:locked_by => 'worker', :locked_at => described_class.db_time_now - 2.minutes)
|
113
|
+
described_class.find_available('worker', 5, 1.minute).should include(@job)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should find own jobs" do
|
117
|
+
@job = create_job(:locked_by => 'worker', :locked_at => (described_class.db_time_now - 1.minutes))
|
118
|
+
described_class.find_available('worker', 5, 4.hours).should include(@job)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should find only the right amount of jobs" do
|
122
|
+
10.times { create_job }
|
123
|
+
described_class.find_available('worker', 7, 4.hours).should have(7).jobs
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "when another worker is already performing an task, it" do
|
128
|
+
before :each do
|
129
|
+
@job = described_class.create :payload_object => SimpleJob.new, :locked_by => 'worker1', :locked_at => described_class.db_time_now - 5.minutes
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should not allow a second worker to get exclusive access" do
|
133
|
+
@job.lock_exclusively!(4.hours, 'worker2').should == false
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should allow a second worker to get exclusive access if the timeout has passed" do
|
137
|
+
@job.lock_exclusively!(1.minute, 'worker2').should == true
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should be able to get access to the task if it was started more then max_age ago" do
|
141
|
+
@job.locked_at = described_class.db_time_now - 5.hours
|
142
|
+
@job.save
|
143
|
+
|
144
|
+
@job.lock_exclusively! 4.hours, 'worker2'
|
145
|
+
@job.reload
|
146
|
+
@job.locked_by.should == 'worker2'
|
147
|
+
@job.locked_at.should > (described_class.db_time_now - 1.minute)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should not be found by another worker" do
|
151
|
+
described_class.find_available('worker2', 1, 6.minutes).length.should == 0
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should be found by another worker if the time has expired" do
|
155
|
+
described_class.find_available('worker2', 1, 4.minutes).length.should == 1
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should be able to get exclusive access again when the worker name is the same" do
|
159
|
+
@job.lock_exclusively!(5.minutes, 'worker1').should be_true
|
160
|
+
@job.lock_exclusively!(5.minutes, 'worker1').should be_true
|
161
|
+
@job.lock_exclusively!(5.minutes, 'worker1').should be_true
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context "when another worker has worked on a task since the job was found to be available, it" do
|
166
|
+
|
167
|
+
before :each do
|
168
|
+
@job = described_class.create :payload_object => SimpleJob.new
|
169
|
+
@job_copy_for_worker_2 = described_class.find(@job.id)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should not allow a second worker to get exclusive access if already successfully processed by worker1" do
|
173
|
+
@job.destroy
|
174
|
+
@job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should not allow a second worker to get exclusive access if failed to be processed by worker1 and run_at time is now in future (due to backing off behaviour)" do
|
178
|
+
@job.update_attributes(:attempts => 1, :run_at => described_class.db_time_now + 1.day)
|
179
|
+
@job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context "#name" do
|
184
|
+
it "should be the class name of the job that was enqueued" do
|
185
|
+
described_class.create(:payload_object => ErrorJob.new ).name.should == 'ErrorJob'
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should be the method that will be called if its a performable method object" do
|
189
|
+
job = described_class.new(:payload_object => NamedJob.new)
|
190
|
+
job.name.should == 'named_job'
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should be the instance method that will be called if its a performable method object" do
|
194
|
+
@job = Story.create(:text => "...").delay.save
|
195
|
+
@job.name.should == 'Story#save'
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context "worker prioritization" do
|
200
|
+
before(:each) do
|
201
|
+
Delayed::Worker.max_priority = nil
|
202
|
+
Delayed::Worker.min_priority = nil
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should fetch jobs ordered by priority" do
|
206
|
+
10.times { described_class.enqueue SimpleJob.new, rand(10) }
|
207
|
+
jobs = described_class.find_available('worker', 10)
|
208
|
+
jobs.size.should == 10
|
209
|
+
jobs.each_cons(2) do |a, b|
|
210
|
+
a.priority.should <= b.priority
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should only find jobs greater than or equal to min priority" do
|
215
|
+
min = 5
|
216
|
+
Delayed::Worker.min_priority = min
|
217
|
+
10.times {|i| described_class.enqueue SimpleJob.new, i }
|
218
|
+
jobs = described_class.find_available('worker', 10)
|
219
|
+
jobs.each {|job| job.priority.should >= min}
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should only find jobs less than or equal to max priority" do
|
223
|
+
max = 5
|
224
|
+
Delayed::Worker.max_priority = max
|
225
|
+
10.times {|i| described_class.enqueue SimpleJob.new, i }
|
226
|
+
jobs = described_class.find_available('worker', 10)
|
227
|
+
jobs.each {|job| job.priority.should <= max}
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context "clear_locks!" do
|
232
|
+
before do
|
233
|
+
@job = create_job(:locked_by => 'worker', :locked_at => described_class.db_time_now)
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should clear locks for the given worker" do
|
237
|
+
described_class.clear_locks!('worker')
|
238
|
+
described_class.find_available('worker2', 5, 1.minute).should include(@job)
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should not clear locks for other workers" do
|
242
|
+
described_class.clear_locks!('worker1')
|
243
|
+
described_class.find_available('worker1', 5, 1.minute).should_not include(@job)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context "unlock" do
|
248
|
+
before do
|
249
|
+
@job = create_job(:locked_by => 'worker', :locked_at => described_class.db_time_now)
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should clear locks" do
|
253
|
+
@job.unlock
|
254
|
+
@job.locked_by.should be_nil
|
255
|
+
@job.locked_at.should be_nil
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
context "large handler" do
|
260
|
+
before do
|
261
|
+
text = "Lorem ipsum dolor sit amet. " * 1000
|
262
|
+
@job = described_class.enqueue Delayed::PerformableMethod.new(text, :length, {})
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should have an id" do
|
266
|
+
@job.id.should_not be_nil
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe "yaml serialization" do
|
271
|
+
it "should reload changed attributes" do
|
272
|
+
job = described_class.enqueue SimpleJob.new
|
273
|
+
yaml = job.to_yaml
|
274
|
+
job.priority = 99
|
275
|
+
job.save
|
276
|
+
YAML.load(yaml).priority.should == 99
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should ignore destroyed records" do
|
280
|
+
job = described_class.enqueue SimpleJob.new
|
281
|
+
yaml = job.to_yaml
|
282
|
+
job.destroy
|
283
|
+
lambda { YAML.load(yaml).should be_nil }.should_not raise_error
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
describe "worker integration" do
|
288
|
+
before do
|
289
|
+
Delayed::Job.delete_all
|
290
|
+
|
291
|
+
@worker = Delayed::Worker.new(:max_priority => nil, :min_priority => nil, :quiet => true)
|
292
|
+
|
293
|
+
SimpleJob.runs = 0
|
294
|
+
end
|
295
|
+
|
296
|
+
describe "running a job" do
|
297
|
+
it "should fail after Worker.max_run_time" do
|
298
|
+
begin
|
299
|
+
old_max_run_time = Delayed::Worker.max_run_time
|
300
|
+
Delayed::Worker.max_run_time = 1.second
|
301
|
+
@job = Delayed::Job.create :payload_object => LongRunningJob.new
|
302
|
+
@worker.run(@job)
|
303
|
+
@job.reload.last_error.should =~ /expired/
|
304
|
+
@job.attempts.should == 1
|
305
|
+
ensure
|
306
|
+
Delayed::Worker.max_run_time = old_max_run_time
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
context "worker prioritization" do
|
312
|
+
before(:each) do
|
313
|
+
@worker = Delayed::Worker.new(:max_priority => 5, :min_priority => -5, :quiet => true)
|
314
|
+
end
|
315
|
+
|
316
|
+
it "should only work_off jobs that are >= min_priority" do
|
317
|
+
create_job(:priority => -10)
|
318
|
+
create_job(:priority => 0)
|
319
|
+
@worker.work_off
|
320
|
+
|
321
|
+
SimpleJob.runs.should == 1
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should only work_off jobs that are <= max_priority" do
|
325
|
+
create_job(:priority => 10)
|
326
|
+
create_job(:priority => 0)
|
327
|
+
|
328
|
+
@worker.work_off
|
329
|
+
|
330
|
+
SimpleJob.runs.should == 1
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
context "while running with locked and expired jobs" do
|
335
|
+
before(:each) do
|
336
|
+
@worker.name = 'worker1'
|
337
|
+
end
|
338
|
+
|
339
|
+
it "should not run jobs locked by another worker" do
|
340
|
+
create_job(:locked_by => 'other_worker', :locked_at => (Delayed::Job.db_time_now - 1.minutes))
|
341
|
+
lambda { @worker.work_off }.should_not change { SimpleJob.runs }
|
342
|
+
end
|
343
|
+
|
344
|
+
it "should run open jobs" do
|
345
|
+
create_job
|
346
|
+
lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
|
347
|
+
end
|
348
|
+
|
349
|
+
it "should run expired jobs" do
|
350
|
+
expired_time = Delayed::Job.db_time_now - (1.minutes + Delayed::Worker.max_run_time)
|
351
|
+
create_job(:locked_by => 'other_worker', :locked_at => expired_time)
|
352
|
+
lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
|
353
|
+
end
|
354
|
+
|
355
|
+
it "should run own jobs" do
|
356
|
+
create_job(:locked_by => @worker.name, :locked_at => (Delayed::Job.db_time_now - 1.minutes))
|
357
|
+
lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
describe "failed jobs" do
|
362
|
+
before do
|
363
|
+
# reset defaults
|
364
|
+
Delayed::Worker.destroy_failed_jobs = true
|
365
|
+
Delayed::Worker.max_attempts = 25
|
366
|
+
|
367
|
+
@job = Delayed::Job.enqueue ErrorJob.new
|
368
|
+
end
|
369
|
+
|
370
|
+
it "should record last_error when destroy_failed_jobs = false, max_attempts = 1" do
|
371
|
+
Delayed::Worker.destroy_failed_jobs = false
|
372
|
+
Delayed::Worker.max_attempts = 1
|
373
|
+
@worker.run(@job)
|
374
|
+
@job.reload
|
375
|
+
@job.last_error.should =~ /did not work/
|
376
|
+
@job.attempts.should == 1
|
377
|
+
@job.failed_at.should_not be_nil
|
378
|
+
end
|
379
|
+
|
380
|
+
it "should re-schedule jobs after failing" do
|
381
|
+
@worker.run(@job)
|
382
|
+
@job.reload
|
383
|
+
@job.last_error.should =~ /did not work/
|
384
|
+
@job.last_error.should =~ /sample_jobs.rb:\d+:in `perform'/
|
385
|
+
@job.attempts.should == 1
|
386
|
+
@job.run_at.should > Delayed::Job.db_time_now - 10.minutes
|
387
|
+
@job.run_at.should < Delayed::Job.db_time_now + 10.minutes
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
context "reschedule" do
|
392
|
+
before do
|
393
|
+
@job = Delayed::Job.create :payload_object => SimpleJob.new
|
394
|
+
end
|
395
|
+
|
396
|
+
share_examples_for "any failure more than Worker.max_attempts times" do
|
397
|
+
context "when the job's payload has an #on_permanent_failure hook" do
|
398
|
+
before do
|
399
|
+
@job = Delayed::Job.create :payload_object => OnPermanentFailureJob.new
|
400
|
+
@job.payload_object.should respond_to :on_permanent_failure
|
401
|
+
end
|
402
|
+
|
403
|
+
it "should run that hook" do
|
404
|
+
@job.payload_object.should_receive :on_permanent_failure
|
405
|
+
Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
context "when the job's payload has no #on_permanent_failure hook" do
|
410
|
+
# It's a little tricky to test this in a straightforward way,
|
411
|
+
# because putting a should_not_receive expectation on
|
412
|
+
# @job.payload_object.on_permanent_failure makes that object
|
413
|
+
# incorrectly return true to
|
414
|
+
# payload_object.respond_to? :on_permanent_failure, which is what
|
415
|
+
# reschedule uses to decide whether to call on_permanent_failure.
|
416
|
+
# So instead, we just make sure that the payload_object as it
|
417
|
+
# already stands doesn't respond_to? on_permanent_failure, then
|
418
|
+
# shove it through the iterated reschedule loop and make sure we
|
419
|
+
# don't get a NoMethodError (caused by calling that nonexistent
|
420
|
+
# on_permanent_failure method).
|
421
|
+
|
422
|
+
before do
|
423
|
+
@job.payload_object.should_not respond_to(:on_permanent_failure)
|
424
|
+
end
|
425
|
+
|
426
|
+
it "should not try to run that hook" do
|
427
|
+
lambda do
|
428
|
+
Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
|
429
|
+
end.should_not raise_exception(NoMethodError)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
context "and we want to destroy jobs" do
|
435
|
+
before do
|
436
|
+
Delayed::Worker.destroy_failed_jobs = true
|
437
|
+
end
|
438
|
+
|
439
|
+
it_should_behave_like "any failure more than Worker.max_attempts times"
|
440
|
+
|
441
|
+
it "should be destroyed if it failed more than Worker.max_attempts times" do
|
442
|
+
@job.should_receive(:destroy)
|
443
|
+
Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
|
444
|
+
end
|
445
|
+
|
446
|
+
it "should not be destroyed if failed fewer than Worker.max_attempts times" do
|
447
|
+
@job.should_not_receive(:destroy)
|
448
|
+
(Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) }
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
context "and we don't want to destroy jobs" do
|
453
|
+
before do
|
454
|
+
Delayed::Worker.destroy_failed_jobs = false
|
455
|
+
end
|
456
|
+
|
457
|
+
it_should_behave_like "any failure more than Worker.max_attempts times"
|
458
|
+
|
459
|
+
it "should be failed if it failed more than Worker.max_attempts times" do
|
460
|
+
@job.reload.failed_at.should == nil
|
461
|
+
Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
|
462
|
+
@job.reload.failed_at.should_not == nil
|
463
|
+
end
|
464
|
+
|
465
|
+
it "should not be failed if it failed fewer than Worker.max_attempts times" do
|
466
|
+
(Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) }
|
467
|
+
@job.reload.failed_at.should == nil
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|