delayed_job 2.0.8 → 2.1.0.pre

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.
@@ -0,0 +1,40 @@
1
+ # These extensions allow properly serializing and autoloading of
2
+ # Classes, Modules and Structs
3
+
4
+ require 'yaml'
5
+
6
+ class Module
7
+ yaml_as "tag:ruby.yaml.org,2002:module"
8
+
9
+ def self.yaml_new(klass, tag, val)
10
+ val.constantize
11
+ end
12
+
13
+ def to_yaml( opts = {} )
14
+ YAML::quick_emit( nil, opts ) { |out|
15
+ out.scalar(taguri, self.name, :plain)
16
+ }
17
+ end
18
+
19
+ def yaml_tag_read_class(name)
20
+ # Constantize the object so that ActiveSupport can attempt
21
+ # its auto loading magic. Will raise LoadError if not successful.
22
+ name.constantize
23
+ name
24
+ end
25
+
26
+ end
27
+
28
+ class Class
29
+ yaml_as "tag:ruby.yaml.org,2002:class"
30
+ remove_method :to_yaml # use Module's to_yaml
31
+ end
32
+
33
+ class Struct
34
+ def self.yaml_tag_read_class(name)
35
+ # Constantize the object so that ActiveSupport can attempt
36
+ # its auto loading magic. Will raise LoadError if not successful.
37
+ name.constantize
38
+ "Struct::#{ name }"
39
+ end
40
+ end
data/lib/delayed_job.rb CHANGED
@@ -2,9 +2,9 @@ require 'active_support'
2
2
 
3
3
  require File.dirname(__FILE__) + '/delayed/message_sending'
4
4
  require File.dirname(__FILE__) + '/delayed/performable_method'
5
+ require File.dirname(__FILE__) + '/delayed/yaml_ext'
5
6
  require File.dirname(__FILE__) + '/delayed/backend/base'
6
7
  require File.dirname(__FILE__) + '/delayed/worker'
7
- require File.dirname(__FILE__) + '/delayed/deserialization_error'
8
8
  require File.dirname(__FILE__) + '/delayed/railtie' if defined?(::Rails::Railtie)
9
9
 
10
10
  Object.send(:include, Delayed::MessageSending)
@@ -0,0 +1,34 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class DelayedJobGenerator < Rails::Generators::Base
5
+
6
+ include Rails::Generators::Migration
7
+
8
+ def self.source_root
9
+ @source_root ||= File.join(File.dirname(__FILE__), 'templates')
10
+ end
11
+
12
+ # Implement the required interface for Rails::Generators::Migration.
13
+ #
14
+ def self.next_migration_number(dirname) #:nodoc:
15
+ next_migration_number = current_migration_number(dirname) + 1
16
+ if ActiveRecord::Base.timestamped_migrations
17
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
18
+ else
19
+ "%.3d" % next_migration_number
20
+ end
21
+ end
22
+
23
+ def create_script_file
24
+ template 'script', 'script/delayed_job'
25
+ chmod 'script/delayed_job', 0755
26
+ end
27
+
28
+ def create_migration_file
29
+ if defined?(ActiveRecord)
30
+ migration_template 'migration.rb', 'db/migrate/create_delayed_jobs.rb'
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,21 @@
1
+ class CreateDelayedJobs < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :delayed_jobs, :force => true do |table|
4
+ table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
5
+ table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
6
+ table.text :handler # YAML-encoded string of the object that will do work
7
+ table.text :last_error # reason for last failure (See Note below)
8
+ table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
9
+ table.datetime :locked_at # Set when a client is working on this object
10
+ table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
11
+ table.string :locked_by # Who is working on this object (if locked)
12
+ table.timestamps
13
+ end
14
+
15
+ add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
16
+ end
17
+
18
+ def self.down
19
+ drop_table :delayed_jobs
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
4
+ require 'delayed/command'
5
+ Delayed::Command.new(ARGV).daemonize
@@ -0,0 +1,7 @@
1
+ # Make sure this file does not get required manually
2
+ module Autoloaded
3
+ class Clazz
4
+ def perform
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # Make sure this file does not get required manually
2
+ module Autoloaded
3
+ class Struct < ::Struct.new(nil)
4
+ def perform
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+ require 'backend/shared_backend_spec'
3
+ require 'delayed/backend/couch_rest'
4
+
5
+ describe Delayed::Backend::CouchRest::Job do
6
+ before(:all) do
7
+ @backend = Delayed::Backend::CouchRest::Job
8
+ end
9
+
10
+ before(:each) do
11
+ @backend.delete_all
12
+ end
13
+
14
+ it_should_behave_like 'a backend'
15
+ end
@@ -6,11 +6,11 @@ describe Delayed::Backend::MongoMapper::Job do
6
6
  before(:all) do
7
7
  @backend = Delayed::Backend::MongoMapper::Job
8
8
  end
9
-
9
+
10
10
  before(:each) do
11
11
  MongoMapper.database.collections.each(&:remove)
12
12
  end
13
-
13
+
14
14
  it_should_behave_like 'a backend'
15
15
 
16
16
  describe "indexes" do
@@ -22,23 +22,23 @@ describe Delayed::Backend::MongoMapper::Job do
22
22
  @backend.collection.index_information.detect { |index| index[0] == 'locked_by_1' }.should_not be_nil
23
23
  end
24
24
  end
25
-
25
+
26
26
  describe "delayed method" do
27
27
  class MongoStoryReader
28
28
  def read(story)
29
29
  "Epilog: #{story.tell}"
30
30
  end
31
31
  end
32
-
32
+
33
33
  class MongoStory
34
34
  include ::MongoMapper::Document
35
35
  key :text, String
36
-
36
+
37
37
  def tell
38
38
  text
39
39
  end
40
40
  end
41
-
41
+
42
42
  it "should ignore not found errors because they are permanent" do
43
43
  story = MongoStory.create :text => 'Once upon a time...'
44
44
  job = story.delay.tell
@@ -66,12 +66,12 @@ describe Delayed::Backend::MongoMapper::Job do
66
66
  job.payload_object.perform.should == 'Epilog: Once upon a time...'
67
67
  end
68
68
  end
69
-
69
+
70
70
  describe "before_fork" do
71
71
  after do
72
- MongoMapper.connection.connect
72
+ MongoMapper.connection.connect_to_master
73
73
  end
74
-
74
+
75
75
  it "should disconnect" do
76
76
  lambda do
77
77
  Delayed::Backend::MongoMapper::Job.before_fork
@@ -83,12 +83,12 @@ describe Delayed::Backend::MongoMapper::Job do
83
83
  before do
84
84
  MongoMapper.connection.close
85
85
  end
86
-
86
+
87
87
  it "should call reconnect" do
88
88
  lambda do
89
89
  Delayed::Backend::MongoMapper::Job.after_fork
90
90
  end.should change { !!MongoMapper.connection.connected? }.from(false).to(true)
91
91
  end
92
92
  end
93
-
93
+
94
94
  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
  shared_examples_for 'a backend' do
2
8
  def create_job(opts = {})
3
9
  @backend.create(opts.merge(:payload_object => SimpleJob.new))
@@ -6,7 +12,6 @@ shared_examples_for 'a backend' do
6
12
  before do
7
13
  Delayed::Worker.max_priority = nil
8
14
  Delayed::Worker.min_priority = nil
9
- Delayed::Worker.default_priority = 99
10
15
  SimpleJob.runs = 0
11
16
  end
12
17
 
@@ -33,11 +38,6 @@ shared_examples_for 'a backend' do
33
38
  @job.priority.should == 5
34
39
  end
35
40
 
36
- it "should use default priority when it is not set" do
37
- @job = @backend.enqueue SimpleJob.new
38
- @job.priority.should == 99
39
- end
40
-
41
41
  it "should be able to set run_at when enqueuing items" do
42
42
  later = @backend.db_time_now + 5.minutes
43
43
  @job = @backend.enqueue SimpleJob.new, 5, later
@@ -49,39 +49,27 @@ shared_examples_for 'a backend' do
49
49
  job = @backend.enqueue M::ModuleJob.new
50
50
  lambda { job.invoke_job }.should change { M::ModuleJob.runs }.from(0).to(1)
51
51
  end
52
-
53
- it "should raise an DeserializationError when the job class is totally unknown" do
54
- job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
55
- lambda { job.payload_object.perform }.should raise_error(Delayed::DeserializationError)
56
- end
57
-
58
- it "should raise an DeserializationError when the job is badly encoded" do
59
- job = @backend.new :handler => "--- !ruby/object:SimpleJob {"
60
- lambda { job.payload_object.perform }.should raise_error(Delayed::DeserializationError)
61
- end
62
-
63
- it "should try to load the class when it is unknown at the time of the deserialization" do
64
- job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
65
- job.should_receive(:attempt_to_load).with('JobThatDoesNotExist').and_return(true)
66
- lambda { job.payload_object.perform }.should raise_error(Delayed::DeserializationError)
67
- end
68
-
69
- it "should try include the namespace when loading unknown objects" do
70
- job = @backend.new :handler => "--- !ruby/object:Delayed::JobThatDoesNotExist {}"
71
- job.should_receive(:attempt_to_load).with('Delayed::JobThatDoesNotExist').and_return(true)
72
- lambda { job.payload_object.perform }.should raise_error(Delayed::DeserializationError)
73
- end
52
+
53
+ describe "payload_object" do
54
+ it "should raise a DeserializationError when the job class is totally unknown" do
55
+ job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
56
+ lambda { job.payload_object }.should raise_error(Delayed::Backend::DeserializationError)
57
+ end
74
58
 
75
- it "should also try to load structs when they are unknown (raises TypeError)" do
76
- job = @backend.new :handler => "--- !ruby/struct:JobThatDoesNotExist {}"
77
- job.should_receive(:attempt_to_load).with('JobThatDoesNotExist').and_return(true)
78
- lambda { job.payload_object.perform }.should raise_error(Delayed::DeserializationError)
79
- end
59
+ it "should raise a DeserializationError when the job struct is totally unknown" do
60
+ job = @backend.new :handler => "--- !ruby/struct:StructThatDoesNotExist {}"
61
+ lambda { job.payload_object }.should raise_error(Delayed::Backend::DeserializationError)
62
+ end
63
+
64
+ it "should autoload classes that are unknown at runtime" do
65
+ job = @backend.new :handler => "--- !ruby/object:Autoloaded::Clazz {}"
66
+ lambda { job.payload_object }.should_not raise_error(Delayed::Backend::DeserializationError)
67
+ end
80
68
 
81
- it "should try include the namespace when loading unknown structs" do
82
- job = @backend.new :handler => "--- !ruby/struct:Delayed::JobThatDoesNotExist {}"
83
- job.should_receive(:attempt_to_load).with('Delayed::JobThatDoesNotExist').and_return(true)
84
- lambda { job.payload_object.perform }.should raise_error(Delayed::DeserializationError)
69
+ it "should autoload structs that are unknown at runtime" do
70
+ job = @backend.new :handler => "--- !ruby/struct:Autoloaded::Struct {}"
71
+ lambda { job.payload_object }.should_not raise_error(Delayed::Backend::DeserializationError)
72
+ end
85
73
  end
86
74
 
87
75
  describe "find_available" do
@@ -177,55 +165,15 @@ shared_examples_for 'a backend' do
177
165
  @job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
178
166
  end
179
167
  end
180
-
181
- describe "reserve" do
182
- before do
183
- Delayed::Worker.max_run_time = 2.minutes
184
- @worker = Delayed::Worker.new(:quiet => true)
185
- end
186
-
187
- it "should not reserve failed jobs" do
188
- create_job :attempts => 50, :failed_at => described_class.db_time_now
189
- described_class.reserve(@worker).should be_nil
190
- end
191
168
 
192
- it "should not reserve jobs scheduled for the future" do
193
- create_job :run_at => (described_class.db_time_now + 1.minute)
194
- described_class.reserve(@worker).should be_nil
195
- end
196
-
197
- it "should lock the job so other workers can't reserve it" do
198
- job = create_job
199
- described_class.reserve(@worker).should == job
200
- new_worker = Delayed::Worker.new(:quiet => true)
201
- new_worker.name = 'worker2'
202
- described_class.reserve(new_worker).should be_nil
203
- end
204
-
205
- it "should reserve open jobs" do
206
- job = create_job
207
- described_class.reserve(@worker).should == job
208
- end
209
-
210
- it "should reserve expired jobs" do
211
- job = create_job(:locked_by => @worker.name, :locked_at => described_class.db_time_now - 3.minutes)
212
- described_class.reserve(@worker).should == job
213
- end
214
-
215
- it "should reserve own jobs" do
216
- job = create_job(:locked_by => @worker.name, :locked_at => (described_class.db_time_now - 1.minutes))
217
- described_class.reserve(@worker).should == job
218
- end
219
- end
220
-
221
169
  context "#name" do
222
170
  it "should be the class name of the job that was enqueued" do
223
171
  @backend.create(:payload_object => ErrorJob.new ).name.should == 'ErrorJob'
224
172
  end
225
-
173
+
226
174
  it "should be the method that will be called if its a performable method object" do
227
- @job = Story.delay.create
228
- @job.name.should == "Story.create"
175
+ job = @backend.new(:payload_object => NamedJob.new)
176
+ job.name.should == 'named_job'
229
177
  end
230
178
 
231
179
  it "should be the instance method that will be called if its a performable method object" do
@@ -304,38 +252,22 @@ shared_examples_for 'a backend' do
304
252
  @job.id.should_not be_nil
305
253
  end
306
254
  end
307
-
308
- context "max_attempts" do
309
- before(:each) do
310
- @job = described_class.enqueue SimpleJob.new
311
- end
312
-
313
- it 'should not be defined' do
314
- @job.max_attempts.should be_nil
315
- end
316
-
317
- it 'should use the max_retries value on the payload when defined' do
318
- @job.payload_object.stub!(:max_attempts).and_return(99)
319
- @job.max_attempts.should == 99
320
- end
321
- end
322
255
 
323
- describe "worker integration" do
324
- before do
325
- @worker = Delayed::Worker.new(:max_priority => nil, :min_priority => nil, :quiet => true)
256
+ describe "yaml serialization" do
257
+ it "should reload changed attributes" do
258
+ job = @backend.enqueue SimpleJob.new
259
+ yaml = job.to_yaml
260
+ job.priority = 99
261
+ job.save
262
+ YAML.load(yaml).priority.should == 99
326
263
  end
327
264
 
328
- describe "running a job" do
329
-
330
- context "when the job raises a deserialization error" do
331
- it "should mark the job as failed" do
332
- Delayed::Worker.destroy_failed_jobs = false
333
- job = described_class.create! :handler => "--- !ruby/object:JobThatDoesNotExist {}"
334
- @worker.work_off
335
- job.reload
336
- job.failed_at.should_not be_nil
337
- end
338
- end
265
+ it "should ignore destroyed records" do
266
+ job = @backend.enqueue SimpleJob.new
267
+ yaml = job.to_yaml
268
+ job.destroy
269
+ lambda { YAML.load(yaml).should be_nil }.should_not raise_error
339
270
  end
340
271
  end
272
+
341
273
  end
@@ -22,44 +22,6 @@ describe Delayed::MessageSending do
22
22
  job.payload_object.args.should == [1]
23
23
  }.should change { Delayed::Job.count }
24
24
  end
25
-
26
- describe 'with options' do
27
- class Fable
28
- class << self
29
- attr_accessor :importance
30
- end
31
- def tell
32
- end
33
- handle_asynchronously :tell, :priority => Proc.new { self.importance }
34
- end
35
-
36
- it 'should set the priority based on the Fable importance' do
37
- Fable.importance = 10
38
- job = Fable.new.tell
39
- job.priority.should == 10
40
-
41
- Fable.importance = 20
42
- job = Fable.new.tell
43
- job.priority.should == 20
44
- end
45
-
46
- describe 'using a proc with parament' do
47
- class Yarn
48
- attr_accessor :importance
49
- def spin
50
- end
51
- handle_asynchronously :spin, :priority => Proc.new {|y| y.importance }
52
- end
53
-
54
- it 'should set the priority based on the Fable importance' do
55
- job = Yarn.new.tap {|y| y.importance = 10 }.spin
56
- job.priority.should == 10
57
-
58
- job = Yarn.new.tap {|y| y.importance = 20 }.spin
59
- job.priority.should == 20
60
- end
61
- end
62
- end
63
25
  end
64
26
 
65
27
  context "delay" do
@@ -71,14 +33,7 @@ describe Delayed::MessageSending do
71
33
  job.payload_object.args.should == ['l']
72
34
  }.should change { Delayed::Job.count }.by(1)
73
35
  end
74
-
75
- it "should set default priority" do
76
- Delayed::Worker.default_priority = 99
77
- job = Object.delay.to_s
78
- job.priority.should == 99
79
- Delayed::Worker.default_priority = 0
80
- end
81
-
36
+
82
37
  it "should set job options" do
83
38
  run_at = Time.parse('2010-05-03 12:55 AM')
84
39
  job = Object.delay(:priority => 20, :run_at => run_at).to_s