delayed_job 1.9.0pre → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +13 -11
- data/Rakefile +7 -1
- data/VERSION +1 -1
- data/benchmarks.rb +19 -0
- data/delayed_job.gemspec +26 -13
- data/lib/delayed/backend/base.rb +5 -0
- data/lib/delayed/backend/data_mapper.rb +124 -0
- data/spec/backend/active_record_job_spec.rb +7 -9
- data/spec/backend/data_mapper_job_spec.rb +16 -0
- data/spec/backend/mongo_mapper_job_spec.rb +1 -1
- data/spec/backend/shared_backend_spec.rb +2 -0
- data/spec/setup/active_record.rb +5 -5
- data/spec/setup/data_mapper.rb +6 -0
- data/spec/setup/mongo_mapper.rb +4 -2
- data/spec/spec_helper.rb +14 -24
- data/spec/worker_spec.rb +124 -127
- metadata +105 -23
data/README.textile
CHANGED
@@ -40,30 +40,32 @@ After delayed_job is installed, you will need to setup the backend.
|
|
40
40
|
|
41
41
|
h2. Backends
|
42
42
|
|
43
|
-
delayed_job supports multiple backends for storing the job queue.
|
43
|
+
delayed_job supports multiple backends for storing the job queue. There are currently implementations for Active Record, MongoMapper, and DataMapper.
|
44
|
+
|
45
|
+
h3. Active Record
|
46
|
+
|
47
|
+
The default is Active Record, which requires a jobs table.
|
44
48
|
|
45
49
|
<pre>
|
46
|
-
script/generate delayed_job
|
47
|
-
rake db:migrate
|
50
|
+
$ script/generate delayed_job
|
51
|
+
$ rake db:migrate
|
48
52
|
</pre>
|
49
53
|
|
50
|
-
|
54
|
+
h3. MongoMapper
|
51
55
|
|
52
56
|
<pre>
|
53
57
|
# config/initializers/delayed_job.rb
|
54
58
|
Delayed::Worker.backend = :mongo_mapper
|
55
59
|
</pre>
|
56
60
|
|
57
|
-
|
58
|
-
|
59
|
-
If you are upgrading from a previous release, you will need to generate the new @script/delayed_job@:
|
61
|
+
h3. DataMapper
|
60
62
|
|
61
63
|
<pre>
|
62
|
-
|
64
|
+
# config/initializers/delayed_job.rb
|
65
|
+
Delayed::Worker.backend = :data_mapper
|
66
|
+
Delayed::Worker.backend.auto_upgrade!
|
63
67
|
</pre>
|
64
68
|
|
65
|
-
Known Issues: script/delayed_job does not work properly with anything besides the Active Record backend. That will be resolved before the next gem release.
|
66
|
-
|
67
69
|
h2. Queuing Jobs
|
68
70
|
|
69
71
|
Call @#send_later(method, params)@ on any object and it will be processed in the background.
|
@@ -92,7 +94,7 @@ device.deliver
|
|
92
94
|
|
93
95
|
h2. Running Jobs
|
94
96
|
|
95
|
-
@script/delayed_job@ can be used to manage a background process which will start working off jobs.
|
97
|
+
@script/delayed_job@ can be used to manage a background process which will start working off jobs. Make sure you've run `script/generate delayed_job`.
|
96
98
|
|
97
99
|
<pre>
|
98
100
|
$ RAILS_ENV=production script/delayed_job start
|
data/Rakefile
CHANGED
@@ -18,11 +18,17 @@ Jeweler::Tasks.new do |s|
|
|
18
18
|
s.rdoc_options = ["--main", "README.textile", "--inline-source", "--line-numbers"]
|
19
19
|
s.extra_rdoc_files = ["README.textile"]
|
20
20
|
|
21
|
-
s.test_files = Dir['spec
|
21
|
+
s.test_files = Dir['spec/*_spec.rb']
|
22
22
|
|
23
23
|
s.add_dependency "daemons"
|
24
24
|
s.add_development_dependency "rspec"
|
25
25
|
s.add_development_dependency "sqlite3-ruby"
|
26
|
+
s.add_development_dependency "mongo_mapper"
|
27
|
+
s.add_development_dependency "dm-core"
|
28
|
+
s.add_development_dependency "dm-observer"
|
29
|
+
s.add_development_dependency "dm-aggregates"
|
30
|
+
s.add_development_dependency "do_sqlite3"
|
31
|
+
s.add_development_dependency "database_cleaner"
|
26
32
|
end
|
27
33
|
|
28
34
|
require 'spec/rake/spectask'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0
|
data/benchmarks.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/lib')
|
2
|
+
require 'rubygems'
|
3
|
+
require 'logger'
|
4
|
+
require 'delayed_job'
|
5
|
+
require 'benchmark'
|
6
|
+
|
7
|
+
Delayed::Worker.logger = Logger.new('/dev/null')
|
8
|
+
|
9
|
+
Benchmark.bm(10) do |x|
|
10
|
+
[:active_record, :mongo_mapper, :data_mapper].each do |backend|
|
11
|
+
require "spec/setup/#{backend}"
|
12
|
+
Delayed::Worker.backend = backend
|
13
|
+
|
14
|
+
n = 10000
|
15
|
+
n.times { "foo".send_later :length }
|
16
|
+
|
17
|
+
x.report(backend.to_s) { Delayed::Worker.new(:quiet => true).work_off(n) }
|
18
|
+
end
|
19
|
+
end
|
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 = "
|
8
|
+
s.version = "2.0.0"
|
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-03
|
12
|
+
s.date = %q{2010-04-03}
|
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).}
|
@@ -23,6 +23,7 @@ This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)
|
|
23
23
|
"README.textile",
|
24
24
|
"Rakefile",
|
25
25
|
"VERSION",
|
26
|
+
"benchmarks.rb",
|
26
27
|
"contrib/delayed_job.monitrc",
|
27
28
|
"delayed_job.gemspec",
|
28
29
|
"generators/delayed_job/delayed_job_generator.rb",
|
@@ -30,6 +31,7 @@ This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)
|
|
30
31
|
"generators/delayed_job/templates/script",
|
31
32
|
"lib/delayed/backend/active_record.rb",
|
32
33
|
"lib/delayed/backend/base.rb",
|
34
|
+
"lib/delayed/backend/data_mapper.rb",
|
33
35
|
"lib/delayed/backend/mongo_mapper.rb",
|
34
36
|
"lib/delayed/command.rb",
|
35
37
|
"lib/delayed/message_sending.rb",
|
@@ -41,12 +43,14 @@ This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)
|
|
41
43
|
"rails/init.rb",
|
42
44
|
"recipes/delayed_job.rb",
|
43
45
|
"spec/backend/active_record_job_spec.rb",
|
46
|
+
"spec/backend/data_mapper_job_spec.rb",
|
44
47
|
"spec/backend/mongo_mapper_job_spec.rb",
|
45
48
|
"spec/backend/shared_backend_spec.rb",
|
46
49
|
"spec/delayed_method_spec.rb",
|
47
50
|
"spec/performable_method_spec.rb",
|
48
51
|
"spec/sample_jobs.rb",
|
49
52
|
"spec/setup/active_record.rb",
|
53
|
+
"spec/setup/data_mapper.rb",
|
50
54
|
"spec/setup/mongo_mapper.rb",
|
51
55
|
"spec/spec_helper.rb",
|
52
56
|
"spec/story_spec.rb",
|
@@ -56,20 +60,11 @@ This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)
|
|
56
60
|
s.homepage = %q{http://github.com/collectiveidea/delayed_job}
|
57
61
|
s.rdoc_options = ["--main", "README.textile", "--inline-source", "--line-numbers"]
|
58
62
|
s.require_paths = ["lib"]
|
59
|
-
s.rubygems_version = %q{1.3.
|
63
|
+
s.rubygems_version = %q{1.3.6}
|
60
64
|
s.summary = %q{Database-backed asynchronous priority queue system -- Extracted from Shopify}
|
61
65
|
s.test_files = [
|
62
|
-
"spec/
|
63
|
-
"spec/backend/active_record_job_spec.rb",
|
64
|
-
"spec/backend/mongo_mapper_job_spec.rb",
|
65
|
-
"spec/backend/shared_backend_spec.rb",
|
66
|
-
"spec/delayed_method_spec.rb",
|
66
|
+
"spec/delayed_method_spec.rb",
|
67
67
|
"spec/performable_method_spec.rb",
|
68
|
-
"spec/sample_jobs.rb",
|
69
|
-
"spec/setup",
|
70
|
-
"spec/setup/active_record.rb",
|
71
|
-
"spec/setup/mongo_mapper.rb",
|
72
|
-
"spec/spec_helper.rb",
|
73
68
|
"spec/story_spec.rb",
|
74
69
|
"spec/worker_spec.rb"
|
75
70
|
]
|
@@ -82,15 +77,33 @@ This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)
|
|
82
77
|
s.add_runtime_dependency(%q<daemons>, [">= 0"])
|
83
78
|
s.add_development_dependency(%q<rspec>, [">= 0"])
|
84
79
|
s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
|
80
|
+
s.add_development_dependency(%q<mongo_mapper>, [">= 0"])
|
81
|
+
s.add_development_dependency(%q<dm-core>, [">= 0"])
|
82
|
+
s.add_development_dependency(%q<dm-observer>, [">= 0"])
|
83
|
+
s.add_development_dependency(%q<dm-aggregates>, [">= 0"])
|
84
|
+
s.add_development_dependency(%q<do_sqlite3>, [">= 0"])
|
85
|
+
s.add_development_dependency(%q<database_cleaner>, [">= 0"])
|
85
86
|
else
|
86
87
|
s.add_dependency(%q<daemons>, [">= 0"])
|
87
88
|
s.add_dependency(%q<rspec>, [">= 0"])
|
88
89
|
s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
|
90
|
+
s.add_dependency(%q<mongo_mapper>, [">= 0"])
|
91
|
+
s.add_dependency(%q<dm-core>, [">= 0"])
|
92
|
+
s.add_dependency(%q<dm-observer>, [">= 0"])
|
93
|
+
s.add_dependency(%q<dm-aggregates>, [">= 0"])
|
94
|
+
s.add_dependency(%q<do_sqlite3>, [">= 0"])
|
95
|
+
s.add_dependency(%q<database_cleaner>, [">= 0"])
|
89
96
|
end
|
90
97
|
else
|
91
98
|
s.add_dependency(%q<daemons>, [">= 0"])
|
92
99
|
s.add_dependency(%q<rspec>, [">= 0"])
|
93
100
|
s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
|
101
|
+
s.add_dependency(%q<mongo_mapper>, [">= 0"])
|
102
|
+
s.add_dependency(%q<dm-core>, [">= 0"])
|
103
|
+
s.add_dependency(%q<dm-observer>, [">= 0"])
|
104
|
+
s.add_dependency(%q<dm-aggregates>, [">= 0"])
|
105
|
+
s.add_dependency(%q<do_sqlite3>, [">= 0"])
|
106
|
+
s.add_dependency(%q<database_cleaner>, [">= 0"])
|
94
107
|
end
|
95
108
|
end
|
96
109
|
|
data/lib/delayed/backend/base.rb
CHANGED
@@ -28,6 +28,11 @@ module Delayed
|
|
28
28
|
# Hook method that is called after a new worker is forked
|
29
29
|
def after_fork
|
30
30
|
end
|
31
|
+
|
32
|
+
def work_off(num = 100)
|
33
|
+
warn "[DEPRECATION] `Delayed::Job.work_off` is deprecated. Use `Delayed::Worker.new.work_off instead."
|
34
|
+
Delayed::Worker.new.work_off(num)
|
35
|
+
end
|
31
36
|
end
|
32
37
|
|
33
38
|
ParseObjectFromYaml = /\!ruby\/\w+\:([^\s]+)/
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'dm-observer'
|
3
|
+
require 'dm-aggregates'
|
4
|
+
|
5
|
+
module DataMapper
|
6
|
+
module Resource
|
7
|
+
module ClassMethods
|
8
|
+
def load_for_delayed_job(id)
|
9
|
+
find!(id)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module InstanceMethods
|
14
|
+
def dump_for_delayed_job
|
15
|
+
"#{self.class};#{id}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Delayed
|
22
|
+
module Backend
|
23
|
+
module DataMapper
|
24
|
+
class Job
|
25
|
+
include ::DataMapper::Resource
|
26
|
+
include Delayed::Backend::Base
|
27
|
+
|
28
|
+
storage_names[:default] = 'delayed_jobs'
|
29
|
+
|
30
|
+
property :id, Serial
|
31
|
+
property :priority, Integer, :default => 0
|
32
|
+
property :attempts, Integer, :default => 0
|
33
|
+
property :handler, String
|
34
|
+
property :run_at, Time
|
35
|
+
property :locked_at, Time
|
36
|
+
property :locked_by, String
|
37
|
+
property :failed_at, Time
|
38
|
+
property :last_error, String
|
39
|
+
|
40
|
+
def self.db_time_now
|
41
|
+
Time.now
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
|
45
|
+
|
46
|
+
simple_conditions = { :run_at.lte => db_time_now, :limit => limit, :failed_at => nil, :order => [:priority.asc, :run_at.asc] }
|
47
|
+
|
48
|
+
# respect priorities
|
49
|
+
simple_conditions[:priority.gte] = Worker.min_priority if Worker.min_priority
|
50
|
+
simple_conditions[:priority.lte] = Worker.max_priority if Worker.max_priority
|
51
|
+
|
52
|
+
# lockable
|
53
|
+
lockable = (
|
54
|
+
# not locked or past the max time
|
55
|
+
( all(:locked_at => nil ) | all(:locked_at.lt => db_time_now - max_run_time)) |
|
56
|
+
|
57
|
+
# OR locked by our worker
|
58
|
+
all(:locked_by => worker_name))
|
59
|
+
|
60
|
+
# plus some other boring junk
|
61
|
+
(lockable).all( simple_conditions )
|
62
|
+
end
|
63
|
+
|
64
|
+
# When a worker is exiting, make sure we don't have any locked jobs.
|
65
|
+
def self.clear_locks!(worker_name)
|
66
|
+
all(:locked_by => worker_name).update(:locked_at => nil, :locked_by => nil)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Lock this job for this worker.
|
70
|
+
# Returns true if we have the lock, false otherwise.
|
71
|
+
def lock_exclusively!(max_run_time, worker = worker_name)
|
72
|
+
now = self.class.db_time_now
|
73
|
+
overtime = now - max_run_time
|
74
|
+
|
75
|
+
# FIXME - this is a bit gross
|
76
|
+
# DM doesn't give us the number of rows affected by a collection update
|
77
|
+
# so we have to circumvent some niceness in DM::Collection here
|
78
|
+
collection = locked_by != worker ?
|
79
|
+
(self.class.all(:id => id, :run_at.lte => now) & ( self.class.all(:locked_at => nil) | self.class.all(:locked_at.lt => overtime) ) ) :
|
80
|
+
self.class.all(:id => id, :locked_by => worker)
|
81
|
+
|
82
|
+
attributes = collection.model.new(:locked_at => now, :locked_by => worker).dirty_attributes
|
83
|
+
affected_rows = self.repository.update(attributes, collection)
|
84
|
+
|
85
|
+
if affected_rows == 1
|
86
|
+
self.locked_at = now
|
87
|
+
self.locked_by = worker
|
88
|
+
return true
|
89
|
+
else
|
90
|
+
return false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# these are common to the other backends, so we provide an implementation
|
95
|
+
def self.delete_all
|
96
|
+
Delayed::Job.auto_migrate!
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.find id
|
100
|
+
get id
|
101
|
+
end
|
102
|
+
|
103
|
+
def update_attributes(attributes)
|
104
|
+
attributes.each do |k,v|
|
105
|
+
self[k] = v
|
106
|
+
end
|
107
|
+
self.save
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
class JobObserver
|
114
|
+
include ::DataMapper::Observer
|
115
|
+
|
116
|
+
observe Job
|
117
|
+
|
118
|
+
before :save do
|
119
|
+
self.run_at ||= self.class.db_time_now
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -1,15 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'backend/shared_backend_spec'
|
2
3
|
require 'delayed/backend/active_record'
|
3
4
|
|
4
|
-
describe Delayed::Job do
|
5
|
+
describe Delayed::Backend::ActiveRecord::Job do
|
5
6
|
before(:all) do
|
6
|
-
@backend = Delayed::Job
|
7
|
+
@backend = Delayed::Backend::ActiveRecord::Job
|
7
8
|
end
|
8
9
|
|
9
10
|
before(:each) do
|
10
|
-
Delayed::
|
11
|
-
Delayed::Worker.min_priority = nil
|
12
|
-
Delayed::Job.delete_all
|
11
|
+
Delayed::Backend::ActiveRecord::Job.delete_all
|
13
12
|
SimpleJob.runs = 0
|
14
13
|
end
|
15
14
|
|
@@ -28,14 +27,13 @@ describe Delayed::Job do
|
|
28
27
|
it "should return UTC time if that is the AR default" do
|
29
28
|
Time.zone = nil
|
30
29
|
ActiveRecord::Base.default_timezone = :utc
|
31
|
-
Delayed::Job.db_time_now.zone.should == 'UTC'
|
30
|
+
Delayed::Backend::ActiveRecord::Job.db_time_now.zone.should == 'UTC'
|
32
31
|
end
|
33
32
|
|
34
33
|
it "should return local time if that is the AR default" do
|
35
34
|
Time.zone = 'Central Time (US & Canada)'
|
36
35
|
ActiveRecord::Base.default_timezone = :local
|
37
|
-
%w(CST CDT).should include(Delayed::Job.db_time_now.zone)
|
36
|
+
%w(CST CDT).should include(Delayed::Backend::ActiveRecord::Job.db_time_now.zone)
|
38
37
|
end
|
39
|
-
end
|
40
|
-
|
38
|
+
end
|
41
39
|
end
|
@@ -0,0 +1,16 @@
|
|
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
|
data/spec/setup/active_record.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
|
3
3
|
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
4
|
-
ActiveRecord::Base.logger =
|
4
|
+
ActiveRecord::Base.logger = Delayed::Worker.logger
|
5
5
|
ActiveRecord::Migration.verbose = false
|
6
6
|
|
7
7
|
ActiveRecord::Schema.define do
|
8
|
-
|
9
8
|
create_table :delayed_jobs, :force => true do |table|
|
10
9
|
table.integer :priority, :default => 0
|
11
10
|
table.integer :attempts, :default => 0
|
12
11
|
table.text :handler
|
13
|
-
table.
|
12
|
+
table.text :last_error
|
14
13
|
table.datetime :run_at
|
15
14
|
table.datetime :locked_at
|
16
|
-
table.string :locked_by
|
17
15
|
table.datetime :failed_at
|
16
|
+
table.string :locked_by
|
18
17
|
table.timestamps
|
19
18
|
end
|
20
19
|
|
20
|
+
add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
|
21
|
+
|
21
22
|
create_table :stories, :force => true do |table|
|
22
23
|
table.string :text
|
23
24
|
end
|
24
|
-
|
25
25
|
end
|
26
26
|
|
27
27
|
# Purely useful for test cases...
|
data/spec/setup/mongo_mapper.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
require 'mongo_mapper'
|
2
|
+
|
3
|
+
MongoMapper.connection = Mongo::Connection.new nil, nil, :logger => Delayed::Worker.logger
|
2
4
|
MongoMapper.database = 'delayed_job'
|
3
5
|
|
4
6
|
unless defined?(Story)
|
@@ -7,7 +9,7 @@ unless defined?(Story)
|
|
7
9
|
def tell; text; end
|
8
10
|
def whatever(n, _); tell*n; end
|
9
11
|
def self.count; end
|
10
|
-
|
12
|
+
|
11
13
|
handle_asynchronously :whatever
|
12
14
|
end
|
13
15
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,32 +4,22 @@ require 'rubygems'
|
|
4
4
|
require 'spec'
|
5
5
|
require 'logger'
|
6
6
|
|
7
|
-
backends_available = []
|
8
|
-
%w(active_record mongo_mapper).each do |backend|
|
9
|
-
begin
|
10
|
-
require backend
|
11
|
-
backends_available << backend
|
12
|
-
rescue LoadError => e
|
13
|
-
$stderr.puts "The backend '#{backend}' is not available. Skipping tests"
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
if backends_available.empty?
|
18
|
-
raise LoadError, "Cannot run delayed_job specs. No backends available"
|
19
|
-
end
|
20
|
-
|
21
7
|
require 'delayed_job'
|
22
8
|
require 'sample_jobs'
|
23
|
-
require 'backend/shared_backend_spec'
|
24
9
|
|
25
|
-
|
26
|
-
Delayed::Worker.logger = DELAYED_JOB_LOGGER
|
10
|
+
Delayed::Worker.logger = Logger.new('/tmp/dj.log')
|
27
11
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
12
|
+
# determine the available backends
|
13
|
+
BACKENDS = []
|
14
|
+
Dir.glob("#{File.dirname(__FILE__)}/setup/*.rb") do |backend|
|
15
|
+
begin
|
16
|
+
backend = File.basename(backend, '.rb')
|
17
|
+
require "setup/#{backend}"
|
18
|
+
require "backend/#{backend}_job_spec"
|
19
|
+
BACKENDS << backend.to_sym
|
20
|
+
rescue LoadError
|
21
|
+
puts "Unable to load #{backend} backend! #{$!}"
|
22
|
+
end
|
35
23
|
end
|
24
|
+
|
25
|
+
Delayed::Worker.backend = BACKENDS.first
|
data/spec/worker_spec.rb
CHANGED
@@ -5,21 +5,6 @@ describe Delayed::Worker do
|
|
5
5
|
Delayed::Job.create(opts.merge(:payload_object => SimpleJob.new))
|
6
6
|
end
|
7
7
|
|
8
|
-
before(:all) do
|
9
|
-
Delayed::Worker.send :public, :work_off
|
10
|
-
end
|
11
|
-
|
12
|
-
before(:each) do
|
13
|
-
# Make sure backend is set to active record
|
14
|
-
Delayed::Worker.backend = DEFAULT_BACKEND
|
15
|
-
|
16
|
-
@worker = Delayed::Worker.new(:max_priority => nil, :min_priority => nil, :quiet => true)
|
17
|
-
|
18
|
-
Delayed::Job.delete_all
|
19
|
-
|
20
|
-
SimpleJob.runs = 0
|
21
|
-
end
|
22
|
-
|
23
8
|
describe "backend=" do
|
24
9
|
it "should set the Delayed::Job constant to the backend" do
|
25
10
|
@clazz = Class.new
|
@@ -34,145 +19,157 @@ describe Delayed::Worker do
|
|
34
19
|
end
|
35
20
|
end
|
36
21
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
Delayed::
|
42
|
-
|
43
|
-
@worker.
|
44
|
-
|
45
|
-
|
46
|
-
ensure
|
47
|
-
Delayed::Worker.max_run_time = old_max_run_time
|
22
|
+
BACKENDS.each do |backend|
|
23
|
+
describe "with the #{backend} backend" do
|
24
|
+
before do
|
25
|
+
Delayed::Worker.backend = backend
|
26
|
+
Delayed::Job.delete_all
|
27
|
+
|
28
|
+
@worker = Delayed::Worker.new(:max_priority => nil, :min_priority => nil, :quiet => true)
|
29
|
+
|
30
|
+
SimpleJob.runs = 0
|
48
31
|
end
|
49
|
-
end
|
50
|
-
end
|
51
32
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
33
|
+
describe "running a job" do
|
34
|
+
it "should fail after Worker.max_run_time" do
|
35
|
+
begin
|
36
|
+
old_max_run_time = Delayed::Worker.max_run_time
|
37
|
+
Delayed::Worker.max_run_time = 1.second
|
38
|
+
@job = Delayed::Job.create :payload_object => LongRunningJob.new
|
39
|
+
@worker.run(@job)
|
40
|
+
@job.reload.last_error.should =~ /expired/
|
41
|
+
@job.attempts.should == 1
|
42
|
+
ensure
|
43
|
+
Delayed::Worker.max_run_time = old_max_run_time
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "worker prioritization" do
|
49
|
+
before(:each) do
|
50
|
+
@worker = Delayed::Worker.new(:max_priority => 5, :min_priority => -5, :quiet => true)
|
51
|
+
end
|
56
52
|
|
57
|
-
|
58
|
-
|
53
|
+
it "should only work_off jobs that are >= min_priority" do
|
54
|
+
SimpleJob.runs.should == 0
|
59
55
|
|
60
|
-
|
61
|
-
|
62
|
-
|
56
|
+
job_create(:priority => -10)
|
57
|
+
job_create(:priority => 0)
|
58
|
+
@worker.work_off
|
63
59
|
|
64
|
-
|
65
|
-
|
60
|
+
SimpleJob.runs.should == 1
|
61
|
+
end
|
66
62
|
|
67
|
-
|
68
|
-
|
63
|
+
it "should only work_off jobs that are <= max_priority" do
|
64
|
+
SimpleJob.runs.should == 0
|
69
65
|
|
70
|
-
|
71
|
-
|
66
|
+
job_create(:priority => 10)
|
67
|
+
job_create(:priority => 0)
|
72
68
|
|
73
|
-
|
69
|
+
@worker.work_off
|
74
70
|
|
75
|
-
|
76
|
-
|
77
|
-
|
71
|
+
SimpleJob.runs.should == 1
|
72
|
+
end
|
73
|
+
end
|
78
74
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
75
|
+
context "while running with locked and expired jobs" do
|
76
|
+
before(:each) do
|
77
|
+
@worker.name = 'worker1'
|
78
|
+
end
|
83
79
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
80
|
+
it "should not run jobs locked by another worker" do
|
81
|
+
job_create(:locked_by => 'other_worker', :locked_at => (Delayed::Job.db_time_now - 1.minutes))
|
82
|
+
lambda { @worker.work_off }.should_not change { SimpleJob.runs }
|
83
|
+
end
|
88
84
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
85
|
+
it "should run open jobs" do
|
86
|
+
job_create
|
87
|
+
lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
|
88
|
+
end
|
93
89
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
90
|
+
it "should run expired jobs" do
|
91
|
+
expired_time = Delayed::Job.db_time_now - (1.minutes + Delayed::Worker.max_run_time)
|
92
|
+
job_create(:locked_by => 'other_worker', :locked_at => expired_time)
|
93
|
+
lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
|
94
|
+
end
|
99
95
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
96
|
+
it "should run own jobs" do
|
97
|
+
job_create(:locked_by => @worker.name, :locked_at => (Delayed::Job.db_time_now - 1.minutes))
|
98
|
+
lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1)
|
99
|
+
end
|
100
|
+
end
|
105
101
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
102
|
+
describe "failed jobs" do
|
103
|
+
before do
|
104
|
+
# reset defaults
|
105
|
+
Delayed::Worker.destroy_failed_jobs = true
|
106
|
+
Delayed::Worker.max_attempts = 25
|
111
107
|
|
112
|
-
|
113
|
-
|
108
|
+
@job = Delayed::Job.enqueue ErrorJob.new
|
109
|
+
end
|
114
110
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
111
|
+
it "should record last_error when destroy_failed_jobs = false, max_attempts = 1" do
|
112
|
+
Delayed::Worker.destroy_failed_jobs = false
|
113
|
+
Delayed::Worker.max_attempts = 1
|
114
|
+
@worker.run(@job)
|
115
|
+
@job.reload
|
116
|
+
@job.last_error.should =~ /did not work/
|
117
|
+
@job.last_error.should =~ /worker_spec.rb/
|
118
|
+
@job.attempts.should == 1
|
119
|
+
@job.failed_at.should_not be_nil
|
120
|
+
end
|
125
121
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
122
|
+
it "should re-schedule jobs after failing" do
|
123
|
+
@worker.run(@job)
|
124
|
+
@job.reload
|
125
|
+
@job.last_error.should =~ /did not work/
|
126
|
+
@job.last_error.should =~ /sample_jobs.rb:8:in `perform'/
|
127
|
+
@job.attempts.should == 1
|
128
|
+
@job.run_at.should > Delayed::Job.db_time_now - 10.minutes
|
129
|
+
@job.run_at.should < Delayed::Job.db_time_now + 10.minutes
|
130
|
+
end
|
131
|
+
end
|
136
132
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
133
|
+
context "reschedule" do
|
134
|
+
before do
|
135
|
+
@job = Delayed::Job.create :payload_object => SimpleJob.new
|
136
|
+
end
|
141
137
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
138
|
+
context "and we want to destroy jobs" do
|
139
|
+
before do
|
140
|
+
Delayed::Worker.destroy_failed_jobs = true
|
141
|
+
end
|
146
142
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
143
|
+
it "should be destroyed if it failed more than Worker.max_attempts times" do
|
144
|
+
@job.should_receive(:destroy)
|
145
|
+
Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
|
146
|
+
end
|
151
147
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
148
|
+
it "should not be destroyed if failed fewer than Worker.max_attempts times" do
|
149
|
+
@job.should_not_receive(:destroy)
|
150
|
+
(Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) }
|
151
|
+
end
|
152
|
+
end
|
157
153
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
154
|
+
context "and we don't want to destroy jobs" do
|
155
|
+
before do
|
156
|
+
Delayed::Worker.destroy_failed_jobs = false
|
157
|
+
end
|
162
158
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
159
|
+
it "should be failed if it failed more than Worker.max_attempts times" do
|
160
|
+
@job.reload.failed_at.should == nil
|
161
|
+
Delayed::Worker.max_attempts.times { @worker.reschedule(@job) }
|
162
|
+
@job.reload.failed_at.should_not == nil
|
163
|
+
end
|
168
164
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
165
|
+
it "should not be failed if it failed fewer than Worker.max_attempts times" do
|
166
|
+
(Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) }
|
167
|
+
@job.reload.failed_at.should == nil
|
168
|
+
end
|
173
169
|
|
170
|
+
end
|
171
|
+
end
|
174
172
|
end
|
175
173
|
end
|
176
174
|
|
177
|
-
|
178
175
|
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: delayed_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 2
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 2.0.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Brandon Keepers
|
@@ -10,39 +15,117 @@ autorequire:
|
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
17
|
|
13
|
-
date: 2010-03
|
18
|
+
date: 2010-04-03 00:00:00 -04:00
|
14
19
|
default_executable:
|
15
20
|
dependencies:
|
16
21
|
- !ruby/object:Gem::Dependency
|
17
22
|
name: daemons
|
18
|
-
|
19
|
-
|
20
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
21
25
|
requirements:
|
22
26
|
- - ">="
|
23
27
|
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
24
30
|
version: "0"
|
25
|
-
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
26
33
|
- !ruby/object:Gem::Dependency
|
27
34
|
name: rspec
|
28
|
-
|
29
|
-
|
30
|
-
version_requirements: !ruby/object:Gem::Requirement
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
37
|
requirements:
|
32
38
|
- - ">="
|
33
39
|
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
34
42
|
version: "0"
|
35
|
-
|
43
|
+
type: :development
|
44
|
+
version_requirements: *id002
|
36
45
|
- !ruby/object:Gem::Dependency
|
37
46
|
name: sqlite3-ruby
|
47
|
+
prerelease: false
|
48
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
version: "0"
|
55
|
+
type: :development
|
56
|
+
version_requirements: *id003
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: mongo_mapper
|
59
|
+
prerelease: false
|
60
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
type: :development
|
68
|
+
version_requirements: *id004
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: dm-core
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
38
79
|
type: :development
|
39
|
-
|
40
|
-
|
80
|
+
version_requirements: *id005
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: dm-observer
|
83
|
+
prerelease: false
|
84
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
41
85
|
requirements:
|
42
86
|
- - ">="
|
43
87
|
- !ruby/object:Gem::Version
|
88
|
+
segments:
|
89
|
+
- 0
|
44
90
|
version: "0"
|
45
|
-
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id006
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: dm-aggregates
|
95
|
+
prerelease: false
|
96
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
version: "0"
|
103
|
+
type: :development
|
104
|
+
version_requirements: *id007
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: do_sqlite3
|
107
|
+
prerelease: false
|
108
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
segments:
|
113
|
+
- 0
|
114
|
+
version: "0"
|
115
|
+
type: :development
|
116
|
+
version_requirements: *id008
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: database_cleaner
|
119
|
+
prerelease: false
|
120
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
segments:
|
125
|
+
- 0
|
126
|
+
version: "0"
|
127
|
+
type: :development
|
128
|
+
version_requirements: *id009
|
46
129
|
description: |-
|
47
130
|
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.
|
48
131
|
|
@@ -60,6 +143,7 @@ files:
|
|
60
143
|
- README.textile
|
61
144
|
- Rakefile
|
62
145
|
- VERSION
|
146
|
+
- benchmarks.rb
|
63
147
|
- contrib/delayed_job.monitrc
|
64
148
|
- delayed_job.gemspec
|
65
149
|
- generators/delayed_job/delayed_job_generator.rb
|
@@ -67,6 +151,7 @@ files:
|
|
67
151
|
- generators/delayed_job/templates/script
|
68
152
|
- lib/delayed/backend/active_record.rb
|
69
153
|
- lib/delayed/backend/base.rb
|
154
|
+
- lib/delayed/backend/data_mapper.rb
|
70
155
|
- lib/delayed/backend/mongo_mapper.rb
|
71
156
|
- lib/delayed/command.rb
|
72
157
|
- lib/delayed/message_sending.rb
|
@@ -78,12 +163,14 @@ files:
|
|
78
163
|
- rails/init.rb
|
79
164
|
- recipes/delayed_job.rb
|
80
165
|
- spec/backend/active_record_job_spec.rb
|
166
|
+
- spec/backend/data_mapper_job_spec.rb
|
81
167
|
- spec/backend/mongo_mapper_job_spec.rb
|
82
168
|
- spec/backend/shared_backend_spec.rb
|
83
169
|
- spec/delayed_method_spec.rb
|
84
170
|
- spec/performable_method_spec.rb
|
85
171
|
- spec/sample_jobs.rb
|
86
172
|
- spec/setup/active_record.rb
|
173
|
+
- spec/setup/data_mapper.rb
|
87
174
|
- spec/setup/mongo_mapper.rb
|
88
175
|
- spec/spec_helper.rb
|
89
176
|
- spec/story_spec.rb
|
@@ -105,30 +192,25 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
105
192
|
requirements:
|
106
193
|
- - ">="
|
107
194
|
- !ruby/object:Gem::Version
|
195
|
+
segments:
|
196
|
+
- 0
|
108
197
|
version: "0"
|
109
|
-
version:
|
110
198
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
199
|
requirements:
|
112
200
|
- - ">="
|
113
201
|
- !ruby/object:Gem::Version
|
202
|
+
segments:
|
203
|
+
- 0
|
114
204
|
version: "0"
|
115
|
-
version:
|
116
205
|
requirements: []
|
117
206
|
|
118
207
|
rubyforge_project:
|
119
|
-
rubygems_version: 1.3.
|
208
|
+
rubygems_version: 1.3.6
|
120
209
|
signing_key:
|
121
210
|
specification_version: 3
|
122
211
|
summary: Database-backed asynchronous priority queue system -- Extracted from Shopify
|
123
212
|
test_files:
|
124
|
-
- spec/backend/active_record_job_spec.rb
|
125
|
-
- spec/backend/mongo_mapper_job_spec.rb
|
126
|
-
- spec/backend/shared_backend_spec.rb
|
127
213
|
- spec/delayed_method_spec.rb
|
128
214
|
- spec/performable_method_spec.rb
|
129
|
-
- spec/sample_jobs.rb
|
130
|
-
- spec/setup/active_record.rb
|
131
|
-
- spec/setup/mongo_mapper.rb
|
132
|
-
- spec/spec_helper.rb
|
133
215
|
- spec/story_spec.rb
|
134
216
|
- spec/worker_spec.rb
|