Skip to content

Commit

Permalink
Allow successful jobs to (optionally) stay, recording finished_at.
Browse files Browse the repository at this point in the history
Just like for failed jobs, now we have the option:
Delayed::Job.destroy_successful_jobs = false
  • Loading branch information
Helder Ribeiro authored and PatrickTulskie committed Jul 24, 2009
1 parent 82c9740 commit a7aeece
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 7 deletions.
10 changes: 7 additions & 3 deletions README.textile
Expand Up @@ -25,24 +25,28 @@ The library evolves around a delayed_jobs table which looks as follows:
table.datetime :locked_at # Set when a client is working on this object table.datetime :locked_at # Set when a client is working on this object
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
table.string :locked_by # Who is working on this object (if locked) table.string :locked_by # Who is working on this object (if locked)
table.datetime :finished_at # Used for statiscics / monitoring
table.timestamps table.timestamps
end end


On failure, the job is scheduled again in 5 seconds + N ** 4, where N is the number of retries. On failure, the job is scheduled again in 5 seconds + N ** 4, where N is the number of retries.


The default MAX_ATTEMPTS is 25. After this, the job either deleted (default), or left in the database with "failed_at" set. The default MAX_ATTEMPTS is 25. After this, the job is either deleted (default), or left in the database with "failed_at" set.
With the default of 25 attempts, the last retry will be 20 days later, with the last interval being almost 100 hours. With the default of 25 attempts, the last retry will be 20 days later, with the last interval being almost 100 hours.


The default MAX_RUN_TIME is 4.hours. If your job takes longer than that, another computer could pick it up. It's up to you to The default MAX_RUN_TIME is 4.hours. If your job takes longer than that, another computer could pick it up. It's up to you to
make sure your job doesn't exceed this time. You should set this to the longest time you think the job could take. make sure your job doesn't exceed this time. You should set this to the longest time you think the job could take.


By default, it will delete failed jobs (and it always deletes successful jobs). If you want to keep failed jobs, set By default, it will delete failed jobs. If you want to keep failed jobs, set
Delayed::Job.destroy_failed_jobs = false. The failed jobs will be marked with non-null failed_at. @Delayed::Job.destroy_failed_jobs = false@. The failed jobs will be marked with non-null failed_at.

Same thing for successful jobs. They're deleted by default and, to keep them, set @Delayed::Job.destroy_successful_jobs = false@. They will be marked with finished_at. This is useful for gathering statistics like how long jobs took between entering the queue (created_at) and being finished (finished_at).


Here is an example of changing job parameters in Rails: Here is an example of changing job parameters in Rails:


# config/initializers/delayed_job_config.rb # config/initializers/delayed_job_config.rb
Delayed::Job.destroy_failed_jobs = false Delayed::Job.destroy_failed_jobs = false
Delayed::Job.destroy_successful_jobs = false
silence_warnings do silence_warnings do
Delayed::Job.const_set("MAX_ATTEMPTS", 3) Delayed::Job.const_set("MAX_ATTEMPTS", 3)
Delayed::Job.const_set("MAX_RUN_TIME", 5.minutes) Delayed::Job.const_set("MAX_RUN_TIME", 5.minutes)
Expand Down
13 changes: 10 additions & 3 deletions lib/delayed/job.rb
Expand Up @@ -18,13 +18,19 @@ class Job < ActiveRecord::Base
cattr_accessor :destroy_failed_jobs cattr_accessor :destroy_failed_jobs
self.destroy_failed_jobs = true self.destroy_failed_jobs = true


# By default successful jobs are destroyed after finished.
# If you want to keep them around (for statistics/monitoring),
# set this to false.
cattr_accessor :destroy_successful_jobs
self.destroy_successful_jobs = true

# Every worker has a unique name which by default is the pid of the process. # Every worker has a unique name which by default is the pid of the process.
# There are some advantages to overriding this with something which survives worker retarts: # There are some advantages to overriding this with something which survives worker retarts:
# Workers can safely resume working on tasks which are locked by themselves. The worker will assume that it crashed before. # Workers can safely resume working on tasks which are locked by themselves. The worker will assume that it crashed before.
cattr_accessor :worker_name cattr_accessor :worker_name
self.worker_name = "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}" self.worker_name = "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"


NextTaskSQL = '(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR (locked_by = ?)) AND failed_at IS NULL' NextTaskSQL = '(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR (locked_by = ?)) AND failed_at IS NULL AND finished_at IS NULL'
NextTaskOrder = 'priority DESC, run_at ASC' NextTaskOrder = 'priority DESC, run_at ASC'


ParseObjectFromYaml = /\!ruby\/\w+\:([^\s]+)/ ParseObjectFromYaml = /\!ruby\/\w+\:([^\s]+)/
Expand Down Expand Up @@ -91,9 +97,10 @@ def run_with_lock(max_run_time, worker_name)


begin begin
runtime = Benchmark.realtime do runtime = Benchmark.realtime do
Timeout.timeout(max_run_time.to_i) { invoke_job } invoke_job # TODO: raise error if takes longer than max_run_time
destroy
end end
destroy_successful_jobs ? destroy :
update_attribute(:finished_at, Time.now)
# TODO: warn if runtime > max_run_time ? # TODO: warn if runtime > max_run_time ?
logger.info "* [JOB] #{name} completed after %.4f" % runtime logger.info "* [JOB] #{name} completed after %.4f" % runtime
return true # did work return true # did work
Expand Down
1 change: 1 addition & 0 deletions spec/database.rb
Expand Up @@ -23,6 +23,7 @@
table.datetime :locked_at table.datetime :locked_at
table.string :locked_by table.string :locked_by
table.datetime :failed_at table.datetime :failed_at
table.datetime :finished_at
table.timestamps table.timestamps
end end


Expand Down
44 changes: 43 additions & 1 deletion spec/job_spec.rb
Expand Up @@ -96,7 +96,49 @@ def perform; @@runs += 1; end


M::ModuleJob.runs.should == 1 M::ModuleJob.runs.should == 1
end end


it "should be destroyed if it succeeded and we want to destroy jobs" do
default = Delayed::Job.destroy_successful_jobs
Delayed::Job.destroy_successful_jobs = true

Delayed::Job.enqueue SimpleJob.new
Delayed::Job.work_off

Delayed::Job.count.should == 0

Delayed::Job.destroy_successful_jobs = default
end

it "should be kept if it succeeded and we don't want to destroy jobs" do
default = Delayed::Job.destroy_successful_jobs
Delayed::Job.destroy_successful_jobs = false

Delayed::Job.enqueue SimpleJob.new
Delayed::Job.work_off

Delayed::Job.count.should == 1

Delayed::Job.destroy_successful_jobs = default
end

it "should be finished if it succeeded and we don't want to destroy jobs" do
default = Delayed::Job.destroy_successful_jobs
Delayed::Job.destroy_successful_jobs = false
@job = Delayed::Job.create :payload_object => SimpleJob.new

@job.reload.finished_at.should == nil
Delayed::Job.work_off
@job.reload.finished_at.should_not == nil

Delayed::Job.destroy_successful_jobs = default
end

it "should never find finished jobs" do
@job = Delayed::Job.create :payload_object => SimpleJob.new,
:finished_at => Time.now
Delayed::Job.find_available(1).length.should == 0
end

it "should re-schedule by about 1 second at first and increment this more and more minutes when it fails to execute properly" do it "should re-schedule by about 1 second at first and increment this more and more minutes when it fails to execute properly" do
Delayed::Job.enqueue ErrorJob.new Delayed::Job.enqueue ErrorJob.new
Delayed::Job.work_off(1) Delayed::Job.work_off(1)
Expand Down

0 comments on commit a7aeece

Please sign in to comment.