Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added on_permanent_failure hook

  • Loading branch information...
commit d2f14cd601af19be048041eef07752f7b8d43a6a 1 parent 54d4091
Phil Darnowsky authored
View
1  .gitignore
@@ -1 +1,2 @@
*.gem
+*.swp
View
14 README.textile
@@ -123,6 +123,20 @@ end
Delayed::Job.enqueue NewsletterJob.new('lorem ipsum...', Customers.find(:all).collect(&:email))
</pre>
+You can also add an optional on_permanent_failure method which will run if the job has failed too many times to be retried:
+
+<pre>
+class ParanoidNewsletterJob < NewsletterJob
+ def perform
+ emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
+ end
+
+ def on_permanent_failure
+ page_sysadmin_in_the_middle_of_the_night
+ end
+end
+</pre>
+
h2. Gory Details
The library evolves around a delayed_jobs table which looks as follows:
View
6 lib/delayed/worker.rb
@@ -127,6 +127,12 @@ def reschedule(job, time = nil)
job.save!
else
say "* [JOB] PERMANENTLY removing #{job.name} because of #{job.attempts} consecutive failures.", Logger::INFO
+
+ if job.payload_object.respond_to? :on_permanent_failure
+ say "* [JOB] Running on_permanent_failure hook"
+ job.payload_object.on_permanent_failure
+ end
+
self.class.destroy_failed_jobs ? job.destroy : job.update_attributes(:failed_at => Delayed::Job.db_time_now)
end
end
View
9 spec/sample_jobs.rb
@@ -1,3 +1,5 @@
+require 'ruby-debug'
+
class SimpleJob
cattr_accessor :runs; self.runs = 0
def perform; @@runs += 1; end
@@ -12,10 +14,15 @@ class LongRunningJob
def perform; sleep 250; end
end
+class OnPermanentFailureJob < SimpleJob
+ def on_permanent_failure
+ end
+end
+
module M
class ModuleJob
cattr_accessor :runs; self.runs = 0
def perform; @@runs += 1; end
end
-end
+end
View
48 spec/worker_spec.rb
@@ -1,4 +1,5 @@
require 'spec_helper'
+require 'ruby-debug'
describe Delayed::Worker do
def job_create(opts = {})
@@ -134,12 +135,52 @@ def job_create(opts = {})
before do
@job = Delayed::Job.create :payload_object => SimpleJob.new
end
-
+
+ share_examples_for "any failure more than Worker.max_attempts times" do
+ context "when the job's payload has an #on_permanent_failure hook" do
+ before do
+ @job = Delayed::Job.create :payload_object => OnPermanentFailureJob.new
+ @job.payload_object.should respond_to :on_permanent_failure
+ end
+
+ it "should run that hook" do
+ @job.payload_object.should_receive :on_permanent_failure
+ Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
+ end
+ end
+
+ context "when the job's payload has no #on_permanent_failure hook" do
+ # It's a little tricky to test this in a straightforward way,
+ # because putting a should_not_receive expectation on
+ # @job.payload_object.on_permanent_failure makes that object
+ # incorrectly return true to
+ # payload_object.respond_to? :on_permanent_failure, which is what
+ # reschedule uses to decide whether to call on_permanent_failure.
+ # So instead, we just make sure that the payload_object as it
+ # already stands doesn't respond_to? on_permanent_failure, then
+ # shove it through the iterated reschedule loop and make sure we
+ # don't get a NoMethodError (caused by calling that nonexistent
+ # on_permanent_failure method).
+
+ before do
+ @job.payload_object.should_not respond_to(:on_permanent_failure)
+ end
+
+ it "should not try to run that hook" do
+ lambda do
+ Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
+ end.should_not raise_exception(NoMethodError)
+ end
+ end
+ end
+
context "and we want to destroy jobs" do
before do
Delayed::Worker.destroy_failed_jobs = true
end
-
+
+ it_should_behave_like "any failure more than Worker.max_attempts times"
+
it "should be destroyed if it failed more than Worker.max_attempts times" do
@job.should_receive(:destroy)
Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
@@ -156,6 +197,8 @@ def job_create(opts = {})
Delayed::Worker.destroy_failed_jobs = false
end
+ it_should_behave_like "any failure more than Worker.max_attempts times"
+
it "should be failed if it failed more than Worker.max_attempts times" do
@job.reload.failed_at.should == nil
Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
@@ -166,7 +209,6 @@ def job_create(opts = {})
(Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) }
@job.reload.failed_at.should == nil
end
-
end
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.