delayed_job 2.1.0.pre → 2.1.0.pre2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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