delayed_job 1.8.4 → 1.8.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -17,8 +17,7 @@ h2. Installation
17
17
  To install as a gem, add the following to @config/environment.rb@:
18
18
 
19
19
  <pre>
20
- config.gem 'collectiveidea-delayed_job', :lib => 'delayed_job',
21
- :source => 'http://gems.github.com'
20
+ config.gem 'delayed_job'
22
21
  </pre>
23
22
 
24
23
  Rake tasks are not automatically loaded from gems, so you'll need to add the following to your Rakefile:
@@ -118,7 +117,7 @@ The library evolves around a delayed_jobs table which looks as follows:
118
117
  table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
119
118
  table.text :handler # YAML-encoded string of the object that will do work
120
119
  table.text :last_error # reason for last failure (See Note below)
121
- table.datetime :run_at # When to run. Could be Time.now for immediately, or sometime in the future.
120
+ table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
122
121
  table.datetime :locked_at # Set when a client is working on this object
123
122
  table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
124
123
  table.string :locked_by # Who is working on this object (if locked)
@@ -127,10 +126,10 @@ The library evolves around a delayed_jobs table which looks as follows:
127
126
 
128
127
  On failure, the job is scheduled again in 5 seconds + N ** 4, where N is the number of retries.
129
128
 
130
- The default MAX_ATTEMPTS is 25. After this, the job either deleted (default), or left in the database with "failed_at" set.
129
+ The default Job::max_attempts is 25. After this, the job either deleted (default), or left in the database with "failed_at" set.
131
130
  With the default of 25 attempts, the last retry will be 20 days later, with the last interval being almost 100 hours.
132
131
 
133
- 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
132
+ The default Job::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
134
133
  make sure your job doesn't exceed this time. You should set this to the longest time you think the job could take.
135
134
 
136
135
  By default, it will delete failed jobs (and it always deletes successful jobs). If you want to keep failed jobs, set
@@ -142,8 +141,9 @@ Here is an example of changing job parameters in Rails:
142
141
  # config/initializers/delayed_job_config.rb
143
142
  Delayed::Job.destroy_failed_jobs = false
144
143
  silence_warnings do
145
- Delayed::Job.const_set("MAX_ATTEMPTS", 3)
146
- Delayed::Job.const_set("MAX_RUN_TIME", 5.minutes)
144
+ Delayed::Worker::sleep_delay = 60
145
+ Delayed::Job::max_attempts = 3
146
+ Delayed::Job::max_run_time = 5.minutes
147
147
  end
148
148
  </pre>
149
149
 
data/Rakefile CHANGED
@@ -2,7 +2,7 @@
2
2
  begin
3
3
  require 'jeweler'
4
4
  rescue LoadError
5
- puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
5
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
6
6
  exit 1
7
7
  end
8
8
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.8.4
1
+ 1.8.5
@@ -10,5 +10,5 @@
10
10
 
11
11
  check process delayed_job
12
12
  with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.pid
13
- start program = "RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start"
14
- stop program = "RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop"
13
+ start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start"
14
+ stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop"
data/delayed_job.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  # Generated by jeweler
2
- # DO NOT EDIT THIS FILE
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{delayed_job}
8
- s.version = "1.8.4"
8
+ s.version = "1.8.5"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Brandon Keepers", "Tobias L\303\274tke"]
12
- s.date = %q{2009-10-06}
12
+ s.date = %q{2010-03-15}
13
13
  s.description = %q{Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks.}
14
14
  s.email = %q{tobi@leetsoft.com}
15
15
  s.extra_rdoc_files = [
@@ -64,3 +64,4 @@ Gem::Specification.new do |s|
64
64
  else
65
65
  end
66
66
  end
67
+
@@ -5,7 +5,7 @@ class CreateDelayedJobs < ActiveRecord::Migration
5
5
  table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
6
6
  table.text :handler # YAML-encoded string of the object that will do work
7
7
  table.text :last_error # reason for last failure (See Note below)
8
- table.datetime :run_at # When to run. Could be Time.now for immediately, or sometime in the future.
8
+ table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
9
9
  table.datetime :locked_at # Set when a client is working on this object
10
10
  table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
11
11
  table.string :locked_by # Who is working on this object (if locked)
@@ -54,13 +54,16 @@ module Delayed
54
54
  # Re-open file handles
55
55
  @files_to_reopen.each do |file|
56
56
  begin
57
- file.reopen File.join(RAILS_ROOT, 'log', 'delayed_job.log'), 'w+'
57
+ file.reopen File.join(RAILS_ROOT, 'log', 'delayed_job.log'), 'a+'
58
58
  file.sync = true
59
59
  rescue ::Exception
60
60
  end
61
61
  end
62
62
 
63
63
  Delayed::Worker.logger = Rails.logger
64
+ if Delayed::Worker.logger.respond_to? :auto_flushing=
65
+ Delayed::Worker.logger.auto_flushing = true
66
+ end
64
67
  ActiveRecord::Base.connection.reconnect!
65
68
 
66
69
  Delayed::Job.worker_name = "#{worker_name} #{Delayed::Job.worker_name}"
data/lib/delayed/job.rb CHANGED
@@ -8,8 +8,11 @@ module Delayed
8
8
  # A job object that is persisted to the database.
9
9
  # Contains the work object as a YAML field.
10
10
  class Job < ActiveRecord::Base
11
- MAX_ATTEMPTS = 25
12
- MAX_RUN_TIME = 4.hours
11
+ @@max_attempts = 25
12
+ @@max_run_time = 4.hours
13
+
14
+ cattr_accessor :max_attempts, :max_run_time
15
+
13
16
  set_table_name :delayed_jobs
14
17
 
15
18
  # By default failed jobs are destroyed after too many attempts.
@@ -21,8 +24,24 @@ module Delayed
21
24
  # Every worker has a unique name which by default is the pid of the process.
22
25
  # There are some advantages to overriding this with something which survives worker retarts:
23
26
  # Workers can safely resume working on tasks which are locked by themselves. The worker will assume that it crashed before.
24
- cattr_accessor :worker_name
25
- self.worker_name = "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
27
+ @@worker_name = nil
28
+
29
+ def self.worker_name
30
+ return @@worker_name unless @@worker_name.nil?
31
+ "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
32
+ end
33
+
34
+ def self.worker_name=(val)
35
+ @@worker_name = val
36
+ end
37
+
38
+ def worker_name
39
+ self.class.worker_name
40
+ end
41
+
42
+ def worker_name=(val)
43
+ @@worker_name = val
44
+ end
26
45
 
27
46
  NextTaskSQL = '(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR (locked_by = ?)) AND failed_at IS NULL'
28
47
  NextTaskOrder = 'priority DESC, run_at ASC'
@@ -65,16 +84,17 @@ module Delayed
65
84
  # Reschedule the job in the future (when a job fails).
66
85
  # Uses an exponential scale depending on the number of failed attempts.
67
86
  def reschedule(message, backtrace = [], time = nil)
68
- if (self.attempts += 1) < MAX_ATTEMPTS
87
+ self.last_error = message + "\n" + backtrace.join("\n")
88
+
89
+ if (self.attempts += 1) < max_attempts
69
90
  time ||= Job.db_time_now + (attempts ** 4) + 5
70
91
 
71
92
  self.run_at = time
72
- self.last_error = message + "\n" + backtrace.join("\n")
73
93
  self.unlock
74
94
  save!
75
95
  else
76
- logger.info "* [JOB] PERMANENTLY removing #{self.name} because of #{attempts} consequetive failures."
77
- destroy_failed_jobs ? destroy : update_attribute(:failed_at, Time.now)
96
+ logger.info "* [JOB] PERMANENTLY removing #{self.name} because of #{attempts} consecutive failures."
97
+ destroy_failed_jobs ? destroy : update_attribute(:failed_at, Delayed::Job.db_time_now)
78
98
  end
79
99
  end
80
100
 
@@ -118,7 +138,7 @@ module Delayed
118
138
  end
119
139
 
120
140
  # Find a few candidate jobs to run (in case some immediately get locked by others).
121
- def self.find_available(limit = 5, max_run_time = MAX_RUN_TIME)
141
+ def self.find_available(limit = 5, max_run_time = max_run_time)
122
142
 
123
143
  time_now = db_time_now
124
144
 
@@ -145,7 +165,7 @@ module Delayed
145
165
 
146
166
  # Run the next job we can get an exclusive lock on.
147
167
  # If no jobs are left we return nil
148
- def self.reserve_and_run_one_job(max_run_time = MAX_RUN_TIME)
168
+ def self.reserve_and_run_one_job(max_run_time = max_run_time)
149
169
 
150
170
  # We get up to 5 jobs from the db. In case we cannot get exclusive access to a job we try the next.
151
171
  # this leads to a more even distribution of jobs across the worker processes
@@ -247,7 +267,7 @@ module Delayed
247
267
  # Note: This does not ping the DB to get the time, so all your clients
248
268
  # must have syncronized clocks.
249
269
  def self.db_time_now
250
- (ActiveRecord::Base.default_timezone == :utc) ? Time.now.utc : Time.now
270
+ (ActiveRecord::Base.default_timezone == :utc) ? Time.now.utc : Time.zone.now
251
271
  end
252
272
 
253
273
  protected
@@ -4,7 +4,7 @@ module Delayed
4
4
  AR_STRING_FORMAT = /^AR\:([A-Z][\w\:]+)\:(\d+)$/
5
5
 
6
6
  def initialize(object, method, args)
7
- raise NoMethodError, "undefined method `#{method}' for #{self.inspect}" unless object.respond_to?(method)
7
+ raise NoMethodError, "undefined method `#{method}' for #{object.inspect}" unless object.respond_to?(method)
8
8
 
9
9
  self.object = dump(object)
10
10
  self.args = args.map { |a| dump(a) }
@@ -9,19 +9,23 @@
9
9
 
10
10
  Capistrano::Configuration.instance.load do
11
11
  namespace :delayed_job do
12
+ def rails_env
13
+ fetch(:rails_env, false) ? "RAILS_ENV=#{fetch(:rails_env)}" : ''
14
+ end
15
+
12
16
  desc "Stop the delayed_job process"
13
17
  task :stop, :roles => :app do
14
- run "cd #{current_path}; RAILS_ENV=#{rails_env} script/delayed_job stop"
18
+ run "cd #{current_path};#{rails_env} script/delayed_job stop"
15
19
  end
16
20
 
17
21
  desc "Start the delayed_job process"
18
22
  task :start, :roles => :app do
19
- run "cd #{current_path}; RAILS_ENV=#{rails_env} script/delayed_job start"
23
+ run "cd #{current_path};#{rails_env} script/delayed_job start"
20
24
  end
21
25
 
22
26
  desc "Restart the delayed_job process"
23
27
  task :restart, :roles => :app do
24
- run "cd #{current_path}; RAILS_ENV=#{rails_env} script/delayed_job restart"
28
+ run "cd #{current_path};#{rails_env} script/delayed_job restart"
25
29
  end
26
30
  end
27
31
  end
@@ -1,6 +1,8 @@
1
1
  module Delayed
2
2
  class Worker
3
- SLEEP = 5
3
+ @@sleep_delay = 5
4
+
5
+ cattr_accessor :sleep_delay
4
6
 
5
7
  cattr_accessor :logger
6
8
  self.logger = if defined?(Merb::Logger)
@@ -33,7 +35,7 @@ module Delayed
33
35
  break if $exit
34
36
 
35
37
  if count.zero?
36
- sleep(SLEEP)
38
+ sleep(@@sleep_delay)
37
39
  else
38
40
  say "#{count} jobs processed at %.4f j/s, %d failed ..." % [count / realtime, result.last]
39
41
  end
data/spec/database.rb CHANGED
@@ -11,6 +11,7 @@ require 'spec'
11
11
  ActiveRecord::Base.logger = Logger.new('/tmp/dj.log')
12
12
  ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => '/tmp/jobs.sqlite')
13
13
  ActiveRecord::Migration.verbose = false
14
+ ActiveRecord::Base.default_timezone = :utc if Time.zone.nil?
14
15
 
15
16
  ActiveRecord::Schema.define do
16
17
 
data/spec/job_spec.rb CHANGED
@@ -58,7 +58,7 @@ describe Delayed::Job do
58
58
  end
59
59
 
60
60
  it "should be able to set run_at when enqueuing items" do
61
- later = 5.minutes.from_now
61
+ later = (Delayed::Job.db_time_now+5.minutes)
62
62
  Delayed::Job.enqueue SimpleJob.new, 5, later
63
63
 
64
64
  # use be close rather than equal to because millisecond values cn be lost in DB round trip
@@ -111,6 +111,19 @@ describe Delayed::Job do
111
111
  job.run_at.should < Delayed::Job.db_time_now + 10.minutes
112
112
  end
113
113
 
114
+ it "should record last_error when destroy_failed_jobs = false, max_attempts = 1" do
115
+ Delayed::Job.destroy_failed_jobs = false
116
+ Delayed::Job::max_attempts = 1
117
+ job = Delayed::Job.enqueue ErrorJob.new
118
+ Delayed::Job.work_off
119
+ job.reload
120
+ job.last_error.should =~ /did not work/
121
+ job.last_error.should =~ /job_spec.rb/
122
+ job.attempts.should == 1
123
+
124
+ job.failed_at.should_not == nil
125
+ end
126
+
114
127
  it "should raise an DeserializationError when the job class is totally unknown" do
115
128
 
116
129
  job = Delayed::Job.new
@@ -162,14 +175,14 @@ describe Delayed::Job do
162
175
  Delayed::Job.destroy_failed_jobs = true
163
176
  end
164
177
 
165
- it "should be destroyed if it failed more than MAX_ATTEMPTS times" do
178
+ it "should be destroyed if it failed more than Job::max_attempts times" do
166
179
  @job.should_receive(:destroy)
167
- Delayed::Job::MAX_ATTEMPTS.times { @job.reschedule 'FAIL' }
180
+ Delayed::Job::max_attempts.times { @job.reschedule 'FAIL' }
168
181
  end
169
182
 
170
- it "should not be destroyed if failed fewer than MAX_ATTEMPTS times" do
183
+ it "should not be destroyed if failed fewer than Job::max_attempts times" do
171
184
  @job.should_not_receive(:destroy)
172
- (Delayed::Job::MAX_ATTEMPTS - 1).times { @job.reschedule 'FAIL' }
185
+ (Delayed::Job::max_attempts - 1).times { @job.reschedule 'FAIL' }
173
186
  end
174
187
  end
175
188
 
@@ -178,21 +191,21 @@ describe Delayed::Job do
178
191
  Delayed::Job.destroy_failed_jobs = false
179
192
  end
180
193
 
181
- it "should be failed if it failed more than MAX_ATTEMPTS times" do
194
+ it "should be failed if it failed more than Job::max_attempts times" do
182
195
  @job.reload.failed_at.should == nil
183
- Delayed::Job::MAX_ATTEMPTS.times { @job.reschedule 'FAIL' }
196
+ Delayed::Job::max_attempts.times { @job.reschedule 'FAIL' }
184
197
  @job.reload.failed_at.should_not == nil
185
198
  end
186
199
 
187
- it "should not be failed if it failed fewer than MAX_ATTEMPTS times" do
188
- (Delayed::Job::MAX_ATTEMPTS - 1).times { @job.reschedule 'FAIL' }
200
+ it "should not be failed if it failed fewer than Job::max_attempts times" do
201
+ (Delayed::Job::max_attempts - 1).times { @job.reschedule 'FAIL' }
189
202
  @job.reload.failed_at.should == nil
190
203
  end
191
204
 
192
205
  end
193
206
  end
194
207
 
195
- it "should fail after MAX_RUN_TIME" do
208
+ it "should fail after Job::max_run_time" do
196
209
  @job = Delayed::Job.create :payload_object => LongRunningJob.new
197
210
  Delayed::Job.reserve_and_run_one_job(1.second)
198
211
  @job.reload.last_error.should =~ /expired/
@@ -200,7 +213,7 @@ describe Delayed::Job do
200
213
  end
201
214
 
202
215
  it "should never find failed jobs" do
203
- @job = Delayed::Job.create :payload_object => SimpleJob.new, :attempts => 50, :failed_at => Time.now
216
+ @job = Delayed::Job.create :payload_object => SimpleJob.new, :attempts => 50, :failed_at => Delayed::Job.db_time_now
204
217
  Delayed::Job.find_available(1).length.should == 0
205
218
  end
206
219
 
@@ -262,7 +275,7 @@ describe Delayed::Job do
262
275
  end
263
276
 
264
277
  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
265
- @job.update_attributes(:attempts => 1, :run_at => Time.now + 1.day)
278
+ @job.update_attributes(:attempts => 1, :run_at => 1.day.from_now)
266
279
  @job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
267
280
  end
268
281
  end
@@ -379,7 +392,7 @@ describe Delayed::Job do
379
392
  context "while running with locked and expired jobs, it" do
380
393
  before(:each) do
381
394
  Delayed::Job.worker_name = 'worker1'
382
- exp_time = Delayed::Job.db_time_now - (1.minutes + Delayed::Job::MAX_RUN_TIME)
395
+ exp_time = Delayed::Job.db_time_now - (1.minutes + Delayed::Job::max_run_time)
383
396
  Delayed::Job.create(:payload_object => SimpleJob.new, :locked_by => 'worker1', :locked_at => exp_time)
384
397
  Delayed::Job.create(:payload_object => SimpleJob.new, :locked_by => 'worker2', :locked_at => (Delayed::Job.db_time_now - 1.minutes))
385
398
  Delayed::Job.create(:payload_object => SimpleJob.new)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delayed_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.4
4
+ version: 1.8.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Keepers
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-10-06 00:00:00 -04:00
13
+ date: 2010-03-15 00:00:00 -05:00
14
14
  default_executable:
15
15
  dependencies: []
16
16