delayed_job 2.1.0.pre → 2.1.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,7 +10,7 @@ module Delayed
10
10
  @files_to_reopen = []
11
11
  @options = {
12
12
  :quiet => true,
13
- :pid_dir => "#{RAILS_ROOT}/tmp/pids"
13
+ :pid_dir => "#{Rails.root}/tmp/pids"
14
14
  }
15
15
 
16
16
  @worker_count = 1
@@ -80,18 +80,18 @@ module Delayed
80
80
  end
81
81
 
82
82
  def run(worker_name = nil)
83
- Dir.chdir(RAILS_ROOT)
83
+ Dir.chdir(Rails.root)
84
84
 
85
85
  # Re-open file handles
86
86
  @files_to_reopen.each do |file|
87
87
  begin
88
- file.reopen file.path
88
+ file.reopen file.path, "a+"
89
89
  file.sync = true
90
90
  rescue ::Exception
91
91
  end
92
92
  end
93
93
 
94
- Delayed::Worker.logger = Logger.new(File.join(RAILS_ROOT, 'log', 'delayed_job.log'))
94
+ Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log'))
95
95
  Delayed::Worker.backend.after_fork
96
96
 
97
97
  worker = Delayed::Worker.new(@options)
@@ -1,4 +1,5 @@
1
1
  require 'active_support/basic_object'
2
+ require 'active_support/core_ext/module/aliasing'
2
3
 
3
4
  module Delayed
4
5
  class DelayProxy < ActiveSupport::BasicObject
@@ -6,14 +7,15 @@ module Delayed
6
7
  @target = target
7
8
  @options = options
8
9
  end
9
-
10
+
10
11
  def method_missing(method, *args)
11
- Job.create @options.merge(
12
- :payload_object => PerformableMethod.new(@target, method.to_sym, args)
13
- )
12
+ Job.create({
13
+ :payload_object => PerformableMethod.new(@target, method.to_sym, args),
14
+ :priority => ::Delayed::Worker.default_priority
15
+ }.merge(@options))
14
16
  end
15
17
  end
16
-
18
+
17
19
  module MessageSending
18
20
  def delay(options = {})
19
21
  DelayProxy.new(self, options)
@@ -41,4 +43,4 @@ module Delayed
41
43
  end
42
44
  end
43
45
  end
44
- end
46
+ end
@@ -1,7 +1,7 @@
1
1
  module Delayed
2
2
  class PerformableMethod < Struct.new(:object, :method, :args)
3
3
  def initialize(object, method, args)
4
- raise NoMethodError, "undefined method `#{method}' for #{object.inspect}" unless object.respond_to?(method)
4
+ raise NoMethodError, "undefined method `#{method}' for #{object.inspect}" unless object.respond_to?(method, true)
5
5
 
6
6
  self.object = object
7
7
  self.args = args
@@ -15,5 +15,13 @@ module Delayed
15
15
  def perform
16
16
  object.send(method, *args) if object
17
17
  end
18
+
19
+ def method_missing(symbol, *args)
20
+ object.respond_to?(symbol) ? object.send(symbol, *args) : super
21
+ end
22
+
23
+ def respond_to?(symbol, include_private=false)
24
+ object.respond_to?(symbol, include_private) || super
25
+ end
18
26
  end
19
27
  end
@@ -1,15 +1,11 @@
1
- # Re-definitions are appended to existing tasks
2
- task :environment
3
- task :merb_env
4
-
5
1
  namespace :jobs do
6
2
  desc "Clear the delayed_job queue."
7
- task :clear => [:merb_env, :environment] do
3
+ task :clear => :environment do
8
4
  Delayed::Job.delete_all
9
5
  end
10
6
 
11
7
  desc "Start a delayed_job worker."
12
- task :work => [:merb_env, :environment] do
8
+ task :work => :environment do
13
9
  Delayed::Worker.new(:min_priority => ENV['MIN_PRIORITY'], :max_priority => ENV['MAX_PRIORITY']).start
14
10
  end
15
11
  end
@@ -2,21 +2,23 @@ require 'timeout'
2
2
  require 'active_support/core_ext/numeric/time'
3
3
  require 'active_support/core_ext/class/attribute_accessors'
4
4
  require 'active_support/core_ext/kernel'
5
+ require 'logger'
5
6
 
6
7
  module Delayed
7
8
  class Worker
8
- cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time, :sleep_delay, :logger
9
+ cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time, :default_priority, :sleep_delay, :logger
9
10
  self.sleep_delay = 5
10
11
  self.max_attempts = 25
11
12
  self.max_run_time = 4.hours
13
+ self.default_priority = 0
12
14
 
13
15
  # By default failed jobs are destroyed after too many attempts. If you want to keep them around
14
16
  # (perhaps to inspect the reason for the failure), set this to false.
15
17
  cattr_accessor :destroy_failed_jobs
16
18
  self.destroy_failed_jobs = true
17
19
 
18
- self.logger = if defined?(Merb::Logger)
19
- Merb.logger
20
+ self.logger = if defined?(Rails)
21
+ Rails.logger
20
22
  elsif defined?(RAILS_DEFAULT_LOGGER)
21
23
  RAILS_DEFAULT_LOGGER
22
24
  end
@@ -36,14 +38,7 @@ module Delayed
36
38
  end
37
39
 
38
40
  def self.guess_backend
39
- self.backend ||= if defined?(ActiveRecord)
40
- :active_record
41
- elsif defined?(MongoMapper)
42
- :mongo_mapper
43
- else
44
- logger.warn "Could not decide on a backend, defaulting to active_record"
45
- :active_record
46
- end
41
+ self.backend ||= :active_record if defined?(ActiveRecord)
47
42
  end
48
43
 
49
44
  def initialize(options={})
@@ -142,7 +137,12 @@ module Delayed
142
137
 
143
138
  if job.payload_object.respond_to? :on_permanent_failure
144
139
  say "Running on_permanent_failure hook"
145
- job.payload_object.on_permanent_failure
140
+ failure_method = job.payload_object.method(:on_permanent_failure)
141
+ if failure_method.arity == 1
142
+ failure_method.call(job)
143
+ else
144
+ failure_method.call
145
+ end
146
146
  end
147
147
 
148
148
  self.class.destroy_failed_jobs ? job.destroy : job.update_attributes(:failed_at => Delayed::Job.db_time_now)
@@ -9,7 +9,3 @@ require File.dirname(__FILE__) + '/delayed/railtie' if defined?(::Rails::Railtie
9
9
 
10
10
  Object.send(:include, Delayed::MessageSending)
11
11
  Module.send(:include, Delayed::MessageSending::ClassMethods)
12
-
13
- if defined?(Merb::Plugins)
14
- Merb::Plugins.add_rakefiles File.dirname(__FILE__) / 'delayed' / 'tasks'
15
- end
@@ -1,22 +1,12 @@
1
1
  require 'spec_helper'
2
- require 'backend/shared_backend_spec'
3
2
  require 'delayed/backend/active_record'
4
3
 
5
4
  describe Delayed::Backend::ActiveRecord::Job do
6
- before(:all) do
7
- @backend = Delayed::Backend::ActiveRecord::Job
8
- end
9
-
10
- before(:each) do
11
- Delayed::Backend::ActiveRecord::Job.delete_all
12
- SimpleJob.runs = 0
13
- end
14
-
15
5
  after do
16
6
  Time.zone = nil
17
7
  end
18
8
 
19
- it_should_behave_like 'a backend'
9
+ it_should_behave_like 'a delayed_job backend'
20
10
 
21
11
  context "db_time_now" do
22
12
  it "should return time in current time zone if set" do
@@ -33,7 +33,14 @@ describe Delayed::MessageSending do
33
33
  job.payload_object.args.should == ['l']
34
34
  }.should change { Delayed::Job.count }.by(1)
35
35
  end
36
-
36
+
37
+ it "should set default priority" do
38
+ Delayed::Worker.default_priority = 99
39
+ job = Object.delay.to_s
40
+ job.priority.should == 99
41
+ Delayed::Worker.default_priority = 0
42
+ end
43
+
37
44
  it "should set job options" do
38
45
  run_at = Time.parse('2010-05-03 12:55 AM')
39
46
  job = Object.delay(:priority => 20, :run_at => run_at).to_s
@@ -20,11 +20,29 @@ describe Delayed::PerformableMethod do
20
20
  @method.object.should_receive(:count).with('o')
21
21
  @method.perform
22
22
  end
23
+
24
+ it "should respond to on_permanent_failure when implemented and target object is called via object.delay.do_something" do
25
+ @method = Delayed::PerformableMethod.new(OnPermanentFailureJob.new, :perform, [])
26
+ @method.respond_to?(:on_permanent_failure).should be_true
27
+ @method.object.should_receive(:on_permanent_failure)
28
+ @method.on_permanent_failure
29
+ end
23
30
  end
24
31
 
25
- it "should raise a ArgumentError if target method doesn't exist" do
32
+ it "should raise a NoMethodError if target method doesn't exist" do
26
33
  lambda {
27
34
  Delayed::PerformableMethod.new(Object, :method_that_does_not_exist, [])
28
35
  }.should raise_error(NoMethodError)
29
36
  end
37
+
38
+ it "should not raise NoMethodError if target method is private" do
39
+ clazz = Class.new do
40
+ def private_method
41
+ end
42
+ private :private_method
43
+ end
44
+ lambda {
45
+ Delayed::PerformableMethod.new(clazz.new, :private_method, [])
46
+ }.should_not raise_error(NoMethodError)
47
+ end
30
48
  end
@@ -1,3 +1,9 @@
1
+ class NamedJob < Struct.new(:perform)
2
+ def display_name
3
+ 'named_job'
4
+ end
5
+ end
6
+
1
7
  class SimpleJob
2
8
  cattr_accessor :runs; self.runs = 0
3
9
  def perform; @@runs += 1; end
@@ -23,3 +29,33 @@ module M
23
29
  def perform; @@runs += 1; end
24
30
  end
25
31
  end
32
+
33
+ class SuccessfulCallbackJob
34
+ cattr_accessor :messages
35
+
36
+ def before(job)
37
+ SuccessfulCallbackJob.messages << 'before perform'
38
+ end
39
+
40
+ def perform
41
+ SuccessfulCallbackJob.messages << 'perform'
42
+ end
43
+
44
+ def after(job, error = nil)
45
+ SuccessfulCallbackJob.messages << 'after perform'
46
+ end
47
+
48
+ def success(job)
49
+ SuccessfulCallbackJob.messages << 'success!'
50
+ end
51
+
52
+ def failure(job, error)
53
+ SuccessfulCallbackJob.messages << "error: #{error.class}"
54
+ end
55
+ end
56
+
57
+ class FailureCallbackJob < SuccessfulCallbackJob
58
+ def perform
59
+ raise "failure job"
60
+ end
61
+ end
@@ -1,31 +1,53 @@
1
1
  $:.unshift(File.dirname(__FILE__) + '/../lib')
2
2
 
3
3
  require 'rubygems'
4
+ require 'bundler/setup'
4
5
  require 'spec'
5
6
  require 'logger'
6
7
 
7
8
  gem 'activerecord', ENV['RAILS_VERSION'] if ENV['RAILS_VERSION']
8
9
 
9
10
  require 'delayed_job'
10
- require 'sample_jobs'
11
+ require 'delayed/backend/shared_spec'
11
12
 
12
13
  Delayed::Worker.logger = Logger.new('/tmp/dj.log')
13
14
  RAILS_ENV = 'test'
14
15
 
15
- # determine the available backends
16
- BACKENDS = []
17
- Dir.glob("#{File.dirname(__FILE__)}/setup/*.rb") do |backend|
18
- begin
19
- backend = File.basename(backend, '.rb')
20
- require "setup/#{backend}"
21
- require "backend/#{backend}_job_spec"
22
- BACKENDS << backend.to_sym
23
- rescue Exception
24
- puts "Unable to load #{backend} backend: #{$!}"
16
+ require 'active_record'
17
+
18
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
19
+ ActiveRecord::Base.logger = Delayed::Worker.logger
20
+ ActiveRecord::Migration.verbose = false
21
+
22
+ ActiveRecord::Schema.define do
23
+ create_table :delayed_jobs, :force => true do |table|
24
+ table.integer :priority, :default => 0
25
+ table.integer :attempts, :default => 0
26
+ table.text :handler
27
+ table.text :last_error
28
+ table.datetime :run_at
29
+ table.datetime :locked_at
30
+ table.datetime :failed_at
31
+ table.string :locked_by
32
+ table.timestamps
25
33
  end
34
+
35
+ add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
36
+
37
+ create_table :stories, :force => true do |table|
38
+ table.string :text
39
+ end
40
+ end
41
+
42
+ # Purely useful for test cases...
43
+ class Story < ActiveRecord::Base
44
+ def tell; text; end
45
+ def whatever(n, _); tell*n; end
46
+
47
+ handle_asynchronously :whatever
26
48
  end
27
49
 
28
- Delayed::Worker.backend = BACKENDS.first
50
+ Delayed::Worker.backend = :active_record
29
51
 
30
52
  # Add this directory so the ActiveSupport autoloading works
31
- ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__)
53
+ ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
@@ -1,10 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Delayed::Worker do
4
- def job_create(opts = {})
5
- Delayed::Job.create(opts.merge(:payload_object => SimpleJob.new))
6
- end
7
-
8
4
  describe "backend=" do
9
5
  before do
10
6
  @clazz = Class.new
@@ -14,201 +10,28 @@ describe Delayed::Worker do
14
10
  it "should set the Delayed::Job constant to the backend" do
15
11
  Delayed::Job.should == @clazz
16
12
  end
17
-
13
+
18
14
  it "should set backend with a symbol" do
19
15
  Delayed::Worker.backend = :active_record
20
16
  Delayed::Worker.backend.should == Delayed::Backend::ActiveRecord::Job
21
17
  end
22
18
  end
23
-
24
- BACKENDS.each do |backend|
25
- describe "with the #{backend} backend" do
26
- before do
27
- Delayed::Worker.backend = backend
28
- Delayed::Job.delete_all
29
-
30
- @worker = Delayed::Worker.new(:max_priority => nil, :min_priority => nil, :quiet => true)
31
-
32
- SimpleJob.runs = 0
33
- end
34
-
35
- describe "running a job" do
36
- it "should fail after Worker.max_run_time" do
37
- begin
38
- old_max_run_time = Delayed::Worker.max_run_time
39
- Delayed::Worker.max_run_time = 1.second
40
- @job = Delayed::Job.create :payload_object => LongRunningJob.new
41
- @worker.run(@job)
42
- @job.reload.last_error.should =~ /expired/
43
- @job.attempts.should == 1
44
- ensure
45
- Delayed::Worker.max_run_time = old_max_run_time
46
- end
47
- end
48
- end
49
-
50
- context "worker prioritization" do
51
- before(:each) do
52
- @worker = Delayed::Worker.new(:max_priority => 5, :min_priority => -5, :quiet => true)
53
- end
54
-
55
- it "should only work_off jobs that are >= min_priority" do
56
- job_create(:priority => -10)
57
- job_create(:priority => 0)
58
- @worker.work_off
59
-
60
- SimpleJob.runs.should == 1
61
- end
62
-
63
- it "should only work_off jobs that are <= max_priority" do
64
- job_create(:priority => 10)
65
- job_create(:priority => 0)
66
-
67
- @worker.work_off
68
-
69
- SimpleJob.runs.should == 1
70
- end
71
- end
72
-
73
- context "while running with locked and expired jobs" do
74
- before(:each) do
75
- @worker.name = 'worker1'
76
- end
77
-
78
- it "should not run jobs locked by another worker" do
79
- job_create(:locked_by => 'other_worker', :locked_at => (Delayed::Job.db_time_now - 1.minutes))
80
- lambda { @worker.work_off }.should_not change { SimpleJob.runs }
81
- end
82
-
83
- it "should run open jobs" do
84
- job_create
85
- lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
86
- end
87
-
88
- it "should run expired jobs" do
89
- expired_time = Delayed::Job.db_time_now - (1.minutes + Delayed::Worker.max_run_time)
90
- job_create(:locked_by => 'other_worker', :locked_at => expired_time)
91
- lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
92
- end
93
-
94
- it "should run own jobs" do
95
- job_create(:locked_by => @worker.name, :locked_at => (Delayed::Job.db_time_now - 1.minutes))
96
- lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
97
- end
98
- end
99
-
100
- describe "failed jobs" do
101
- before do
102
- # reset defaults
103
- Delayed::Worker.destroy_failed_jobs = true
104
- Delayed::Worker.max_attempts = 25
105
19
 
106
- @job = Delayed::Job.enqueue ErrorJob.new
107
- end
108
-
109
- it "should record last_error when destroy_failed_jobs = false, max_attempts = 1" do
110
- Delayed::Worker.destroy_failed_jobs = false
111
- Delayed::Worker.max_attempts = 1
112
- @worker.run(@job)
113
- @job.reload
114
- @job.last_error.should =~ /did not work/
115
- @job.last_error.should =~ /worker_spec.rb/
116
- @job.attempts.should == 1
117
- @job.failed_at.should_not be_nil
118
- end
119
-
120
- it "should re-schedule jobs after failing" do
121
- @worker.run(@job)
122
- @job.reload
123
- @job.last_error.should =~ /did not work/
124
- @job.last_error.should =~ /sample_jobs.rb:8:in `perform'/
125
- @job.attempts.should == 1
126
- @job.run_at.should > Delayed::Job.db_time_now - 10.minutes
127
- @job.run_at.should < Delayed::Job.db_time_now + 10.minutes
128
- end
129
- end
130
-
131
- context "reschedule" do
132
- before do
133
- @job = Delayed::Job.create :payload_object => SimpleJob.new
134
- end
135
-
136
- share_examples_for "any failure more than Worker.max_attempts times" do
137
- context "when the job's payload has an #on_permanent_failure hook" do
138
- before do
139
- @job = Delayed::Job.create :payload_object => OnPermanentFailureJob.new
140
- @job.payload_object.should respond_to :on_permanent_failure
141
- end
142
-
143
- it "should run that hook" do
144
- @job.payload_object.should_receive :on_permanent_failure
145
- Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
146
- end
147
- end
148
-
149
- context "when the job's payload has no #on_permanent_failure hook" do
150
- # It's a little tricky to test this in a straightforward way,
151
- # because putting a should_not_receive expectation on
152
- # @job.payload_object.on_permanent_failure makes that object
153
- # incorrectly return true to
154
- # payload_object.respond_to? :on_permanent_failure, which is what
155
- # reschedule uses to decide whether to call on_permanent_failure.
156
- # So instead, we just make sure that the payload_object as it
157
- # already stands doesn't respond_to? on_permanent_failure, then
158
- # shove it through the iterated reschedule loop and make sure we
159
- # don't get a NoMethodError (caused by calling that nonexistent
160
- # on_permanent_failure method).
161
-
162
- before do
163
- @job.payload_object.should_not respond_to(:on_permanent_failure)
164
- end
165
-
166
- it "should not try to run that hook" do
167
- lambda do
168
- Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
169
- end.should_not raise_exception(NoMethodError)
170
- end
171
- end
172
- end
173
-
174
- context "and we want to destroy jobs" do
175
- before do
176
- Delayed::Worker.destroy_failed_jobs = true
177
- end
178
-
179
- it_should_behave_like "any failure more than Worker.max_attempts times"
180
-
181
- it "should be destroyed if it failed more than Worker.max_attempts times" do
182
- @job.should_receive(:destroy)
183
- Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
184
- end
185
-
186
- it "should not be destroyed if failed fewer than Worker.max_attempts times" do
187
- @job.should_not_receive(:destroy)
188
- (Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) }
189
- end
190
- end
191
-
192
- context "and we don't want to destroy jobs" do
193
- before do
194
- Delayed::Worker.destroy_failed_jobs = false
195
- end
196
-
197
- it_should_behave_like "any failure more than Worker.max_attempts times"
20
+ describe "guess_backend" do
21
+ after do
22
+ Delayed::Worker.backend = :active_record
23
+ end
198
24
 
199
- it "should be failed if it failed more than Worker.max_attempts times" do
200
- @job.reload.failed_at.should == nil
201
- Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
202
- @job.reload.failed_at.should_not == nil
203
- end
25
+ it "should set to active_record if nil" do
26
+ Delayed::Worker.backend = nil
27
+ lambda {
28
+ Delayed::Worker.guess_backend
29
+ }.should change { Delayed::Worker.backend }.to(Delayed::Backend::ActiveRecord::Job)
30
+ end
204
31
 
205
- it "should not be failed if it failed fewer than Worker.max_attempts times" do
206
- (Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) }
207
- @job.reload.failed_at.should == nil
208
- end
209
- end
210
- end
32
+ it "should not override the existing backend" do
33
+ Delayed::Worker.backend = Class.new
34
+ lambda { Delayed::Worker.guess_backend }.should_not change { Delayed::Worker.backend }
211
35
  end
212
36
  end
213
-
214
37
  end