delayed_job 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  *.gem
2
+ *.swp
data/README.textile CHANGED
@@ -123,6 +123,20 @@ end
123
123
  Delayed::Job.enqueue NewsletterJob.new('lorem ipsum...', Customers.find(:all).collect(&:email))
124
124
  </pre>
125
125
 
126
+ You can also add an optional on_permanent_failure method which will run if the job has failed too many times to be retried:
127
+
128
+ <pre>
129
+ class ParanoidNewsletterJob < NewsletterJob
130
+ def perform
131
+ emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
132
+ end
133
+
134
+ def on_permanent_failure
135
+ page_sysadmin_in_the_middle_of_the_night
136
+ end
137
+ end
138
+ </pre>
139
+
126
140
  h2. Gory Details
127
141
 
128
142
  The library evolves around a delayed_jobs table which looks as follows:
data/Rakefile CHANGED
@@ -27,6 +27,7 @@ Jeweler::Tasks.new do |s|
27
27
  s.add_development_dependency "dm-core"
28
28
  s.add_development_dependency "dm-observer"
29
29
  s.add_development_dependency "dm-aggregates"
30
+ s.add_development_dependency "dm-validations"
30
31
  s.add_development_dependency "do_sqlite3"
31
32
  s.add_development_dependency "database_cleaner"
32
33
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.1
1
+ 2.0.2
data/benchmarks.rb CHANGED
@@ -6,8 +6,20 @@ require 'benchmark'
6
6
 
7
7
  Delayed::Worker.logger = Logger.new('/dev/null')
8
8
 
9
+ BACKENDS = []
10
+ Dir.glob("#{File.dirname(__FILE__)}/spec/setup/*.rb") do |backend|
11
+ begin
12
+ backend = File.basename(backend, '.rb')
13
+ require "spec/setup/#{backend}"
14
+ BACKENDS << backend.to_sym
15
+ rescue LoadError
16
+ puts "Unable to load #{backend} backend! #{$!}"
17
+ end
18
+ end
19
+
20
+
9
21
  Benchmark.bm(10) do |x|
10
- [:active_record, :mongo_mapper, :data_mapper].each do |backend|
22
+ BACKENDS.each do |backend|
11
23
  require "spec/setup/#{backend}"
12
24
  Delayed::Worker.backend = backend
13
25
 
data/delayed_job.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{delayed_job}
8
- s.version = "2.0.1"
8
+ s.version = "2.0.2"
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{2010-04-03}
12
+ s.date = %q{2010-04-08}
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
 
15
15
  This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job).}
@@ -29,6 +29,7 @@ This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)
29
29
  "generators/delayed_job/delayed_job_generator.rb",
30
30
  "generators/delayed_job/templates/migration.rb",
31
31
  "generators/delayed_job/templates/script",
32
+ "init.rb",
32
33
  "lib/delayed/backend/active_record.rb",
33
34
  "lib/delayed/backend/base.rb",
34
35
  "lib/delayed/backend/data_mapper.rb",
@@ -36,11 +37,11 @@ This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)
36
37
  "lib/delayed/command.rb",
37
38
  "lib/delayed/message_sending.rb",
38
39
  "lib/delayed/performable_method.rb",
40
+ "lib/delayed/railtie.rb",
39
41
  "lib/delayed/recipes.rb",
40
42
  "lib/delayed/tasks.rb",
41
43
  "lib/delayed/worker.rb",
42
44
  "lib/delayed_job.rb",
43
- "rails/init.rb",
44
45
  "recipes/delayed_job.rb",
45
46
  "spec/backend/active_record_job_spec.rb",
46
47
  "spec/backend/data_mapper_job_spec.rb",
@@ -81,6 +82,7 @@ This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)
81
82
  s.add_development_dependency(%q<dm-core>, [">= 0"])
82
83
  s.add_development_dependency(%q<dm-observer>, [">= 0"])
83
84
  s.add_development_dependency(%q<dm-aggregates>, [">= 0"])
85
+ s.add_development_dependency(%q<dm-validations>, [">= 0"])
84
86
  s.add_development_dependency(%q<do_sqlite3>, [">= 0"])
85
87
  s.add_development_dependency(%q<database_cleaner>, [">= 0"])
86
88
  else
@@ -91,6 +93,7 @@ This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)
91
93
  s.add_dependency(%q<dm-core>, [">= 0"])
92
94
  s.add_dependency(%q<dm-observer>, [">= 0"])
93
95
  s.add_dependency(%q<dm-aggregates>, [">= 0"])
96
+ s.add_dependency(%q<dm-validations>, [">= 0"])
94
97
  s.add_dependency(%q<do_sqlite3>, [">= 0"])
95
98
  s.add_dependency(%q<database_cleaner>, [">= 0"])
96
99
  end
@@ -102,6 +105,7 @@ This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)
102
105
  s.add_dependency(%q<dm-core>, [">= 0"])
103
106
  s.add_dependency(%q<dm-observer>, [">= 0"])
104
107
  s.add_dependency(%q<dm-aggregates>, [">= 0"])
108
+ s.add_dependency(%q<dm-validations>, [">= 0"])
105
109
  s.add_dependency(%q<do_sqlite3>, [">= 0"])
106
110
  s.add_dependency(%q<database_cleaner>, [">= 0"])
107
111
  end
data/init.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'delayed_job'
2
+
3
+ config.after_initialize do
4
+ Delayed::Worker.guess_backend
5
+ end
@@ -28,14 +28,14 @@ module Delayed
28
28
  storage_names[:default] = 'delayed_jobs'
29
29
 
30
30
  property :id, Serial
31
- property :priority, Integer, :default => 0
31
+ property :priority, Integer, :default => 0, :index => :run_at_priority
32
32
  property :attempts, Integer, :default => 0
33
- property :handler, String
34
- property :run_at, Time
35
- property :locked_at, Time
33
+ property :handler, Text, :lazy => false
34
+ property :run_at, Time, :index => :run_at_priority
35
+ property :locked_at, Time, :index => true
36
36
  property :locked_by, String
37
37
  property :failed_at, Time
38
- property :last_error, String
38
+ property :last_error, Text
39
39
 
40
40
  def self.db_time_now
41
41
  Time.now
@@ -69,6 +69,7 @@ module Delayed
69
69
  # Lock this job for this worker.
70
70
  # Returns true if we have the lock, false otherwise.
71
71
  def lock_exclusively!(max_run_time, worker = worker_name)
72
+
72
73
  now = self.class.db_time_now
73
74
  overtime = now - max_run_time
74
75
 
@@ -43,11 +43,11 @@ module Delayed
43
43
  end
44
44
 
45
45
  def self.after_fork
46
- ::MongoMapper.connection.connect_to_master
46
+ ::MongoMapper.connect(RAILS_ENV)
47
47
  end
48
48
 
49
49
  def self.db_time_now
50
- ::MongoMapper.time_class.now.utc
50
+ Time.now.utc
51
51
  end
52
52
 
53
53
  def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
@@ -62,8 +62,8 @@ module Delayed
62
62
 
63
63
  where = "this.locked_at == null || this.locked_at < #{make_date(right_now - max_run_time)}"
64
64
 
65
- (conditions[:priority] ||= {})['$gte'] = Worker.min_priority if Worker.min_priority
66
- (conditions[:priority] ||= {})['$lte'] = Worker.max_priority if Worker.max_priority
65
+ (conditions[:priority] ||= {})['$gte'] = Worker.min_priority.to_i if Worker.min_priority
66
+ (conditions[:priority] ||= {})['$lte'] = Worker.max_priority.to_i if Worker.max_priority
67
67
 
68
68
  results = all(conditions.merge(:locked_by => worker_name))
69
69
  results += all(conditions.merge('$where' => where)) if results.size < limit
@@ -42,9 +42,12 @@ module Delayed
42
42
  @files_to_reopen << file unless file.closed?
43
43
  end
44
44
 
45
+ dir = "#{RAILS_ROOT}/tmp/pids"
46
+ Dir.mkdir(dir) unless File.exists?(dir)
47
+
45
48
  worker_count.times do |worker_index|
46
49
  process_name = worker_count == 1 ? "delayed_job" : "delayed_job.#{worker_index}"
47
- Daemons.run_proc(process_name, :dir => "#{RAILS_ROOT}/tmp/pids", :dir_mode => :normal, :ARGV => @args) do |*args|
50
+ Daemons.run_proc(process_name, :dir => dir, :dir_mode => :normal, :ARGV => @args) do |*args|
48
51
  run process_name
49
52
  end
50
53
  end
@@ -0,0 +1,10 @@
1
+ require 'delayed_job'
2
+ require 'rails'
3
+
4
+ module Delayed
5
+ class Railtie < Rails::Railtie
6
+ initializer :after_initialize do
7
+ Delayed::Worker.guess_backend
8
+ end
9
+ end
10
+ end
@@ -32,6 +32,17 @@ module Delayed
32
32
  @@backend = backend
33
33
  silence_warnings { ::Delayed.const_set(:Job, backend) }
34
34
  end
35
+
36
+ def self.guess_backend
37
+ self.backend ||= if defined?(ActiveRecord)
38
+ :active_record
39
+ elsif defined?(MongoMapper)
40
+ :mongo_mapper
41
+ else
42
+ logger.warn "Could not decide on a backend, defaulting to active_record"
43
+ :active_record
44
+ end
45
+ end
35
46
 
36
47
  def initialize(options={})
37
48
  @quiet = options[:quiet]
@@ -127,6 +138,12 @@ module Delayed
127
138
  job.save!
128
139
  else
129
140
  say "* [JOB] PERMANENTLY removing #{job.name} because of #{job.attempts} consecutive failures.", Logger::INFO
141
+
142
+ if job.payload_object.respond_to? :on_permanent_failure
143
+ say "* [JOB] Running on_permanent_failure hook"
144
+ job.payload_object.on_permanent_failure
145
+ end
146
+
130
147
  self.class.destroy_failed_jobs ? job.destroy : job.update_attributes(:failed_at => Delayed::Job.db_time_now)
131
148
  end
132
149
  end
data/lib/delayed_job.rb CHANGED
@@ -4,6 +4,7 @@ require File.dirname(__FILE__) + '/delayed/message_sending'
4
4
  require File.dirname(__FILE__) + '/delayed/performable_method'
5
5
  require File.dirname(__FILE__) + '/delayed/backend/base'
6
6
  require File.dirname(__FILE__) + '/delayed/worker'
7
+ require File.dirname(__FILE__) + '/delayed/railtie' if defined?(::Rails::Railtie)
7
8
 
8
9
  Object.send(:include, Delayed::MessageSending)
9
10
  Module.send(:include, Delayed::MessageSending::ClassMethods)
@@ -243,4 +243,23 @@ shared_examples_for 'a backend' do
243
243
  end
244
244
  end
245
245
 
246
+ context "large handler" do
247
+ @@text = %{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus eu vehicula augue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque odio lectus, volutpat sed dictum rutrum, interdum aliquam neque. Vivamus quis velit nisi, quis dictum purus. Duis magna nisi, faucibus nec molestie vitae, dictum eget odio. Nunc nulla mauris, vestibulum at dapibus nec, dapibus et lectus. Nullam sapien lacus, consectetur eget mattis in, rhoncus sed ipsum. Nullam nec nibh nisl. Integer ut erat in arcu feugiat semper. Nulla gravida sapien quam. Vestibulum pharetra elementum posuere. Fusce mattis justo auctor nibh facilisis vitae consectetur nibh vehicula.
248
+
249
+ Ut at pharetra justo. Donec dictum ornare tortor in feugiat. Sed ac purus sem. Aenean dignissim, erat vel bibendum mollis, elit neque mollis mauris, vitae pretium diam enim non leo. Aliquam aliquet, odio id iaculis varius, metus nibh fermentum sapien, a euismod turpis lectus sit amet turpis. Morbi sapien est, scelerisque in placerat in, varius nec mauris. Aliquam erat volutpat. Quisque suscipit tincidunt libero, sed tincidunt libero iaculis et. Vivamus sed faucibus elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus dignissim sem sed tortor semper et lacinia leo viverra. Nulla nec quam at arcu ullamcorper imperdiet vitae in ligula. Quisque placerat vulputate orci sit amet tempor. Duis sed quam nulla. Cras quis mi nibh, at euismod velit. Etiam nec nunc libero, sed condimentum diam.
250
+
251
+ Duis nec mauris in est suscipit viverra a in nibh. Suspendisse nec nulla tortor. Etiam et nulla tellus. Nam feugiat adipiscing commodo. Curabitur scelerisque varius lacus non hendrerit. Vivamus nec enim non turpis auctor tempus sit amet in nisi. Sed ligula nulla, condimentum sed tempor vel, imperdiet id mauris. Quisque mollis ante eu magna tempus porttitor. Integer est libero, consectetur sed tristique a, scelerisque id risus. Donec lacinia justo eget diam fringilla vitae egestas dolor feugiat. Vivamus massa ante, mattis et hendrerit nec, dictum vitae nulla. Pellentesque at nisl et odio suscipit ullamcorper cursus quis enim. Ut nec tellus molestie erat dignissim mollis. Curabitur quis ipsum sapien, sed tincidunt massa. Vestibulum volutpat pretium fringilla.
252
+
253
+ Integer at lorem sit amet nibh suscipit euismod et ut ante. Maecenas feugiat hendrerit dolor, eget egestas velit consequat eget. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse ut nunc odio. Vivamus semper, sem vitae sollicitudin auctor, leo mi vulputate augue, eget venenatis libero nunc ut dolor. Phasellus vulputate, metus et dapibus tempus, tellus arcu ullamcorper leo, porttitor dictum lectus turpis blandit sapien. Pellentesque et accumsan justo. Maecenas elit nisi, tincidunt eget consequat a, laoreet et magna. Pellentesque venenatis felis ut massa ultrices bibendum. Duis vulputate tempor leo at bibendum. Curabitur aliquet, turpis sit amet porta porttitor, nibh mi vehicula dolor, suscipit aliquet mi augue quis magna. Praesent tellus turpis, malesuada at ultricies id, feugiat a urna. Curabitur sed mi magna.
254
+
255
+ Quisque adipiscing dignissim mollis. Aenean blandit, diam porttitor bibendum bibendum, leo neque tempus risus, in rutrum dolor elit a lorem. Aenean sollicitudin scelerisque ullamcorper. Nunc tristique ultricies nunc et imperdiet. Duis vitae egestas mauris. Suspendisse odio nisi, accumsan vel volutpat nec, aliquam vitae odio. Praesent elementum fermentum suscipit. Quisque quis tellus eu tellus bibendum luctus a quis nunc. Praesent dictum velit sed lacus dapibus ut ultricies mauris facilisis. Vivamus bibendum, ipsum sit amet facilisis consequat, leo lectus aliquam augue, eu consectetur magna nunc gravida sapien. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Duis tempor nisl ac odio molestie ut tincidunt purus varius. Nunc quis lorem nibh, vestibulum cursus lorem. Nunc sit amet est ut magna suscipit tempor vitae a augue.}
256
+
257
+ before do
258
+ @job = @backend.enqueue Delayed::PerformableMethod.new(@@text, :length, {})
259
+ end
260
+
261
+ it "should have an id" do
262
+ @job.id.should_not be_nil
263
+ end
264
+ end
246
265
  end
data/spec/sample_jobs.rb CHANGED
@@ -12,10 +12,14 @@ class LongRunningJob
12
12
  def perform; sleep 250; end
13
13
  end
14
14
 
15
+ class OnPermanentFailureJob < SimpleJob
16
+ def on_permanent_failure
17
+ end
18
+ end
19
+
15
20
  module M
16
21
  class ModuleJob
17
22
  cattr_accessor :runs; self.runs = 0
18
23
  def perform; @@runs += 1; end
19
24
  end
20
-
21
- end
25
+ end
@@ -1,4 +1,6 @@
1
1
  require 'dm-core'
2
+ require 'dm-validations'
3
+
2
4
  require 'delayed/backend/data_mapper'
3
5
 
4
6
  DataMapper.logger = Delayed::Worker.logger
data/spec/worker_spec.rb CHANGED
@@ -134,12 +134,52 @@ describe Delayed::Worker do
134
134
  before do
135
135
  @job = Delayed::Job.create :payload_object => SimpleJob.new
136
136
  end
137
-
137
+
138
+ share_examples_for "any failure more than Worker.max_attempts times" do
139
+ context "when the job's payload has an #on_permanent_failure hook" do
140
+ before do
141
+ @job = Delayed::Job.create :payload_object => OnPermanentFailureJob.new
142
+ @job.payload_object.should respond_to :on_permanent_failure
143
+ end
144
+
145
+ it "should run that hook" do
146
+ @job.payload_object.should_receive :on_permanent_failure
147
+ Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
148
+ end
149
+ end
150
+
151
+ context "when the job's payload has no #on_permanent_failure hook" do
152
+ # It's a little tricky to test this in a straightforward way,
153
+ # because putting a should_not_receive expectation on
154
+ # @job.payload_object.on_permanent_failure makes that object
155
+ # incorrectly return true to
156
+ # payload_object.respond_to? :on_permanent_failure, which is what
157
+ # reschedule uses to decide whether to call on_permanent_failure.
158
+ # So instead, we just make sure that the payload_object as it
159
+ # already stands doesn't respond_to? on_permanent_failure, then
160
+ # shove it through the iterated reschedule loop and make sure we
161
+ # don't get a NoMethodError (caused by calling that nonexistent
162
+ # on_permanent_failure method).
163
+
164
+ before do
165
+ @job.payload_object.should_not respond_to(:on_permanent_failure)
166
+ end
167
+
168
+ it "should not try to run that hook" do
169
+ lambda do
170
+ Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
171
+ end.should_not raise_exception(NoMethodError)
172
+ end
173
+ end
174
+ end
175
+
138
176
  context "and we want to destroy jobs" do
139
177
  before do
140
178
  Delayed::Worker.destroy_failed_jobs = true
141
179
  end
142
-
180
+
181
+ it_should_behave_like "any failure more than Worker.max_attempts times"
182
+
143
183
  it "should be destroyed if it failed more than Worker.max_attempts times" do
144
184
  @job.should_receive(:destroy)
145
185
  Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
@@ -156,6 +196,8 @@ describe Delayed::Worker do
156
196
  Delayed::Worker.destroy_failed_jobs = false
157
197
  end
158
198
 
199
+ it_should_behave_like "any failure more than Worker.max_attempts times"
200
+
159
201
  it "should be failed if it failed more than Worker.max_attempts times" do
160
202
  @job.reload.failed_at.should == nil
161
203
  Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
@@ -166,7 +208,6 @@ describe Delayed::Worker do
166
208
  (Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) }
167
209
  @job.reload.failed_at.should == nil
168
210
  end
169
-
170
211
  end
171
212
  end
172
213
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 2
7
7
  - 0
8
- - 1
9
- version: 2.0.1
8
+ - 2
9
+ version: 2.0.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Brandon Keepers
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-04-03 00:00:00 -04:00
18
+ date: 2010-04-08 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -103,7 +103,7 @@ dependencies:
103
103
  type: :development
104
104
  version_requirements: *id007
105
105
  - !ruby/object:Gem::Dependency
106
- name: do_sqlite3
106
+ name: dm-validations
107
107
  prerelease: false
108
108
  requirement: &id008 !ruby/object:Gem::Requirement
109
109
  requirements:
@@ -115,7 +115,7 @@ dependencies:
115
115
  type: :development
116
116
  version_requirements: *id008
117
117
  - !ruby/object:Gem::Dependency
118
- name: database_cleaner
118
+ name: do_sqlite3
119
119
  prerelease: false
120
120
  requirement: &id009 !ruby/object:Gem::Requirement
121
121
  requirements:
@@ -126,6 +126,18 @@ dependencies:
126
126
  version: "0"
127
127
  type: :development
128
128
  version_requirements: *id009
129
+ - !ruby/object:Gem::Dependency
130
+ name: database_cleaner
131
+ prerelease: false
132
+ requirement: &id010 !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ segments:
137
+ - 0
138
+ version: "0"
139
+ type: :development
140
+ version_requirements: *id010
129
141
  description: |-
130
142
  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.
131
143
 
@@ -149,6 +161,7 @@ files:
149
161
  - generators/delayed_job/delayed_job_generator.rb
150
162
  - generators/delayed_job/templates/migration.rb
151
163
  - generators/delayed_job/templates/script
164
+ - init.rb
152
165
  - lib/delayed/backend/active_record.rb
153
166
  - lib/delayed/backend/base.rb
154
167
  - lib/delayed/backend/data_mapper.rb
@@ -156,11 +169,11 @@ files:
156
169
  - lib/delayed/command.rb
157
170
  - lib/delayed/message_sending.rb
158
171
  - lib/delayed/performable_method.rb
172
+ - lib/delayed/railtie.rb
159
173
  - lib/delayed/recipes.rb
160
174
  - lib/delayed/tasks.rb
161
175
  - lib/delayed/worker.rb
162
176
  - lib/delayed_job.rb
163
- - rails/init.rb
164
177
  - recipes/delayed_job.rb
165
178
  - spec/backend/active_record_job_spec.rb
166
179
  - spec/backend/data_mapper_job_spec.rb
data/rails/init.rb DELETED
@@ -1,12 +0,0 @@
1
- require 'delayed_job'
2
-
3
- config.after_initialize do
4
- Delayed::Worker.backend ||= if defined?(ActiveRecord)
5
- :active_record
6
- elsif defined?(MongoMapper)
7
- :mongo_mapper
8
- else
9
- Delayed::Worker.logger.warn "Could not decide on a backend, defaulting to active_record"
10
- :active_record
11
- end
12
- end