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.
@@ -1,109 +0,0 @@
1
- require 'couchrest'
2
-
3
- #extent couchrest to handle delayed_job serialization.
4
- class CouchRest::ExtendedDocument
5
- yaml_as "tag:ruby.yaml.org,2002:CouchRest"
6
-
7
- def reload
8
- job = self.class.get self['_id']
9
- job.each {|k,v| self[k] = v}
10
- end
11
- def self.find(id)
12
- get id
13
- end
14
- def self.yaml_new(klass, tag, val)
15
- klass.get(val['_id'])
16
- end
17
- def ==(other)
18
- if other.is_a? ::CouchRest::ExtendedDocument
19
- self['_id'] == other['_id']
20
- else
21
- super
22
- end
23
- end
24
- end
25
-
26
- #couchrest adapter
27
- module Delayed
28
- module Backend
29
- module CouchRest
30
- class Job < ::CouchRest::ExtendedDocument
31
- include Delayed::Backend::Base
32
- use_database ::CouchRest::Server.new.database('delayed_job')
33
-
34
- property :handler
35
- property :last_error
36
- property :locked_by
37
- property :priority, :default => 0
38
- property :attempts, :default => 0
39
- property :run_at, :cast_as => 'Time'
40
- property :locked_at, :cast_as => 'Time'
41
- property :failed_at, :cast_as => 'Time'
42
- timestamps!
43
-
44
- set_callback :save, :before, :set_default_run_at
45
-
46
- view_by(:failed_at, :locked_by, :run_at,
47
- :map => "function(doc){" +
48
- " if(doc['couchrest-type'] == 'Delayed::Backend::CouchRest::Job') {" +
49
- " emit([doc.failed_at || null, doc.locked_by || null, doc.run_at || null], null);}" +
50
- " }")
51
- view_by(:failed_at, :locked_at, :run_at,
52
- :map => "function(doc){" +
53
- " if(doc['couchrest-type'] == 'Delayed::Backend::CouchRest::Job') {" +
54
- " emit([doc.failed_at || null, doc.locked_at || null, doc.run_at || null], null);}" +
55
- " }")
56
-
57
- def self.db_time_now; Time.now; end
58
- def self.find_available(worker_name, limit = 5, max_run_time = ::Delayed::Worker.max_run_time)
59
- ready = ready_jobs
60
- mine = my_jobs worker_name
61
- expire = expired_jobs max_run_time
62
- jobs = (ready + mine + expire)[0..limit-1].sort_by { |j| j.priority }
63
- jobs = jobs.find_all { |j| j.priority >= Worker.min_priority } if Worker.min_priority
64
- jobs = jobs.find_all { |j| j.priority <= Worker.max_priority } if Worker.max_priority
65
- jobs
66
- end
67
- def self.clear_locks!(worker_name)
68
- jobs = my_jobs worker_name
69
- jobs.each { |j| j.locked_by, j.locked_at = nil, nil; }
70
- database.bulk_save jobs
71
- end
72
- def self.delete_all
73
- database.bulk_save all.each { |doc| doc['_deleted'] = true }
74
- end
75
-
76
- def lock_exclusively!(max_run_time, worker = worker_name)
77
- return false if locked_by_other?(worker) and not expired?(max_run_time)
78
- case
79
- when locked_by_me?(worker)
80
- self.locked_at = self.class.db_time_now
81
- when (unlocked? or (locked_by_other?(worker) and expired?(max_run_time)))
82
- self.locked_at, self.locked_by = self.class.db_time_now, worker
83
- end
84
- save
85
- rescue RestClient::Conflict
86
- false
87
- end
88
-
89
- private
90
- def self.ready_jobs
91
- options = {:startkey => [nil, nil], :endkey => [nil, nil, db_time_now]}
92
- by_failed_at_and_locked_by_and_run_at options
93
- end
94
- def self.my_jobs(worker_name)
95
- options = {:startkey => [nil, worker_name], :endkey => [nil, worker_name, {}]}
96
- by_failed_at_and_locked_by_and_run_at options
97
- end
98
- def self.expired_jobs(max_run_time)
99
- options = {:startkey => [nil,'0'], :endkey => [nil, db_time_now - max_run_time, db_time_now]}
100
- by_failed_at_and_locked_at_and_run_at options
101
- end
102
- def unlocked?; locked_by.nil?; end
103
- def expired?(time); locked_at < self.class.db_time_now - time; end
104
- def locked_by_me?(worker); not locked_by.nil? and locked_by == worker; end
105
- def locked_by_other?(worker); not locked_by.nil? and locked_by != worker; end
106
- end
107
- end
108
- end
109
- end
@@ -1,121 +0,0 @@
1
- require 'dm-core'
2
- require 'dm-observer'
3
- require 'dm-aggregates'
4
-
5
- DataMapper::Resource.class_eval do
6
- yaml_as "tag:ruby.yaml.org,2002:DataMapper"
7
-
8
- def self.yaml_new(klass, tag, val)
9
- klass.find(val['id'])
10
- end
11
-
12
- def to_yaml_properties
13
- ['@id']
14
- end
15
- end
16
-
17
- module Delayed
18
- module Backend
19
- module DataMapper
20
- class Job
21
- include ::DataMapper::Resource
22
- include Delayed::Backend::Base
23
-
24
- storage_names[:default] = 'delayed_jobs'
25
-
26
- property :id, Serial
27
- property :priority, Integer, :default => 0, :index => :run_at_priority
28
- property :attempts, Integer, :default => 0
29
- property :handler, Text, :lazy => false
30
- property :run_at, Time, :index => :run_at_priority
31
- property :locked_at, Time, :index => true
32
- property :locked_by, String
33
- property :failed_at, Time
34
- property :last_error, Text
35
-
36
- def self.db_time_now
37
- Time.now
38
- end
39
-
40
- def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
41
-
42
- simple_conditions = { :run_at.lte => db_time_now, :limit => limit, :failed_at => nil, :order => [:priority.asc, :run_at.asc] }
43
-
44
- # respect priorities
45
- simple_conditions[:priority.gte] = Worker.min_priority if Worker.min_priority
46
- simple_conditions[:priority.lte] = Worker.max_priority if Worker.max_priority
47
-
48
- # lockable
49
- lockable = (
50
- # not locked or past the max time
51
- ( all(:locked_at => nil ) | all(:locked_at.lt => db_time_now - max_run_time)) |
52
-
53
- # OR locked by our worker
54
- all(:locked_by => worker_name))
55
-
56
- # plus some other boring junk
57
- (lockable).all( simple_conditions )
58
- end
59
-
60
- # When a worker is exiting, make sure we don't have any locked jobs.
61
- def self.clear_locks!(worker_name)
62
- all(:locked_by => worker_name).update(:locked_at => nil, :locked_by => nil)
63
- end
64
-
65
- # Lock this job for this worker.
66
- # Returns true if we have the lock, false otherwise.
67
- def lock_exclusively!(max_run_time, worker = worker_name)
68
-
69
- now = self.class.db_time_now
70
- overtime = now - max_run_time
71
-
72
- # FIXME - this is a bit gross
73
- # DM doesn't give us the number of rows affected by a collection update
74
- # so we have to circumvent some niceness in DM::Collection here
75
- collection = locked_by != worker ?
76
- (self.class.all(:id => id, :run_at.lte => now) & ( self.class.all(:locked_at => nil) | self.class.all(:locked_at.lt => overtime) ) ) :
77
- self.class.all(:id => id, :locked_by => worker)
78
-
79
- attributes = collection.model.new(:locked_at => now, :locked_by => worker).dirty_attributes
80
- affected_rows = self.repository.update(attributes, collection)
81
-
82
- if affected_rows == 1
83
- self.locked_at = now
84
- self.locked_by = worker
85
- return true
86
- else
87
- return false
88
- end
89
- end
90
-
91
- # these are common to the other backends, so we provide an implementation
92
- def self.delete_all
93
- Delayed::Job.auto_migrate!
94
- end
95
-
96
- def self.find id
97
- get id
98
- end
99
-
100
- def update_attributes(attributes)
101
- attributes.each do |k,v|
102
- self[k] = v
103
- end
104
- self.save
105
- end
106
-
107
-
108
- end
109
-
110
- class JobObserver
111
- include ::DataMapper::Observer
112
-
113
- observe Job
114
-
115
- before :save do
116
- self.run_at ||= self.class.db_time_now
117
- end
118
- end
119
- end
120
- end
121
- end
@@ -1,106 +0,0 @@
1
- require 'mongo_mapper'
2
-
3
- MongoMapper::Document.class_eval do
4
- yaml_as "tag:ruby.yaml.org,2002:MongoMapper"
5
-
6
- def self.yaml_new(klass, tag, val)
7
- klass.find(val['_id'])
8
- end
9
-
10
- def to_yaml_properties
11
- ['@_id']
12
- end
13
- end
14
-
15
- module Delayed
16
- module Backend
17
- module MongoMapper
18
- class Job
19
- include ::MongoMapper::Document
20
- include Delayed::Backend::Base
21
- set_collection_name 'delayed_jobs'
22
-
23
- key :priority, Integer, :default => 0
24
- key :attempts, Integer, :default => 0
25
- key :handler, String
26
- key :run_at, Time
27
- key :locked_at, Time
28
- key :locked_by, String, :index => true
29
- key :failed_at, Time
30
- key :last_error, String
31
- timestamps!
32
-
33
- before_save :set_default_run_at
34
-
35
- ensure_index [[:priority, 1], [:run_at, 1]]
36
-
37
- def self.before_fork
38
- ::MongoMapper.connection.close
39
- end
40
-
41
- def self.after_fork
42
- ::MongoMapper.connect(RAILS_ENV)
43
- end
44
-
45
- def self.db_time_now
46
- Time.now.utc
47
- end
48
-
49
- def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
50
- right_now = db_time_now
51
-
52
- conditions = {
53
- :run_at => {"$lte" => right_now},
54
- :limit => -limit, # In mongo, positive limits are 'soft' and negative are 'hard'
55
- :failed_at => nil,
56
- :sort => [['priority', 1], ['run_at', 1]]
57
- }
58
-
59
- where = "this.locked_at == null || this.locked_at < #{make_date(right_now - max_run_time)}"
60
-
61
- (conditions[:priority] ||= {})['$gte'] = Worker.min_priority.to_i if Worker.min_priority
62
- (conditions[:priority] ||= {})['$lte'] = Worker.max_priority.to_i if Worker.max_priority
63
-
64
- results = all(conditions.merge(:locked_by => worker_name))
65
- results += all(conditions.merge('$where' => where)) if results.size < limit
66
- results
67
- end
68
-
69
- # When a worker is exiting, make sure we don't have any locked jobs.
70
- def self.clear_locks!(worker_name)
71
- collection.update({:locked_by => worker_name}, {"$set" => {:locked_at => nil, :locked_by => nil}}, :multi => true)
72
- end
73
-
74
- # Lock this job for this worker.
75
- # Returns true if we have the lock, false otherwise.
76
- def lock_exclusively!(max_run_time, worker = worker_name)
77
- right_now = self.class.db_time_now
78
- overtime = right_now - max_run_time.to_i
79
-
80
- query = "this.locked_at == null || this.locked_at < #{make_date(overtime)} || this.locked_by == #{worker.to_json}"
81
- conditions = {:_id => id, :run_at => {"$lte" => right_now}, "$where" => query}
82
-
83
- collection.update(conditions, {"$set" => {:locked_at => right_now, :locked_by => worker}})
84
- affected_rows = collection.find({:_id => id, :locked_by => worker}).count
85
- if affected_rows == 1
86
- self.locked_at = right_now
87
- self.locked_by = worker
88
- return true
89
- else
90
- return false
91
- end
92
- end
93
-
94
- private
95
-
96
- def self.make_date(date_or_seconds)
97
- "new Date(#{date_or_seconds.to_f * 1000})"
98
- end
99
-
100
- def make_date(date)
101
- self.class.make_date(date)
102
- end
103
- end
104
- end
105
- end
106
- end
@@ -1,15 +0,0 @@
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
@@ -1,16 +0,0 @@
1
- require 'spec_helper'
2
- require 'backend/shared_backend_spec'
3
- require 'delayed/backend/data_mapper'
4
-
5
- describe Delayed::Backend::DataMapper::Job do
6
- before(:all) do
7
- @backend = Delayed::Backend::DataMapper::Job
8
- end
9
-
10
- before(:each) do
11
- # reset database before each example is run
12
- DataMapper.auto_migrate!
13
- end
14
-
15
- it_should_behave_like 'a backend'
16
- end
@@ -1,94 +0,0 @@
1
- require 'spec_helper'
2
- require 'backend/shared_backend_spec'
3
- require 'delayed/backend/mongo_mapper'
4
-
5
- describe Delayed::Backend::MongoMapper::Job do
6
- before(:all) do
7
- @backend = Delayed::Backend::MongoMapper::Job
8
- end
9
-
10
- before(:each) do
11
- MongoMapper.database.collections.each(&:remove)
12
- end
13
-
14
- it_should_behave_like 'a backend'
15
-
16
- describe "indexes" do
17
- it "should have combo index on priority and run_at" do
18
- @backend.collection.index_information.detect { |index| index[0] == 'priority_1_run_at_1' }.should_not be_nil
19
- end
20
-
21
- it "should have index on locked_by" do
22
- @backend.collection.index_information.detect { |index| index[0] == 'locked_by_1' }.should_not be_nil
23
- end
24
- end
25
-
26
- describe "delayed method" do
27
- class MongoStoryReader
28
- def read(story)
29
- "Epilog: #{story.tell}"
30
- end
31
- end
32
-
33
- class MongoStory
34
- include ::MongoMapper::Document
35
- key :text, String
36
-
37
- def tell
38
- text
39
- end
40
- end
41
-
42
- it "should ignore not found errors because they are permanent" do
43
- story = MongoStory.create :text => 'Once upon a time...'
44
- job = story.delay.tell
45
- story.destroy
46
- lambda { job.invoke_job }.should_not raise_error
47
- end
48
-
49
- it "should store the object as string" do
50
- story = MongoStory.create :text => 'Once upon a time...'
51
- job = story.delay.tell
52
-
53
- job.payload_object.class.should == Delayed::PerformableMethod
54
- job.payload_object.object.should == story
55
- job.payload_object.method.should == :tell
56
- job.payload_object.args.should == []
57
- job.payload_object.perform.should == 'Once upon a time...'
58
- end
59
-
60
- it "should store arguments as string" do
61
- story = MongoStory.create :text => 'Once upon a time...'
62
- job = MongoStoryReader.new.delay.read(story)
63
- job.payload_object.class.should == Delayed::PerformableMethod
64
- job.payload_object.method.should == :read
65
- job.payload_object.args.should == [story]
66
- job.payload_object.perform.should == 'Epilog: Once upon a time...'
67
- end
68
- end
69
-
70
- describe "before_fork" do
71
- after do
72
- MongoMapper.connection.connect_to_master
73
- end
74
-
75
- it "should disconnect" do
76
- lambda do
77
- Delayed::Backend::MongoMapper::Job.before_fork
78
- end.should change { !!MongoMapper.connection.connected? }.from(true).to(false)
79
- end
80
- end
81
-
82
- describe "after_fork" do
83
- before do
84
- MongoMapper.connection.close
85
- end
86
-
87
- it "should call reconnect" do
88
- lambda do
89
- Delayed::Backend::MongoMapper::Job.after_fork
90
- end.should change { !!MongoMapper.connection.connected? }.from(false).to(true)
91
- end
92
- end
93
-
94
- end