blaxter-delayed_job 1.7.0 → 1.7.99
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.
- data/.gitignore +1 -0
- data/README.textile +27 -19
- data/Rakefile +38 -0
- data/delayed_job.gemspec +51 -35
- data/generators/delayed_job/delayed_job_generator.rb +11 -0
- data/generators/delayed_job/templates/migration.rb +21 -0
- data/lib/delayed/job.rb +23 -8
- data/lib/delayed/worker.rb +1 -0
- data/spec/database.rb +12 -6
- data/spec/job_spec.rb +107 -4
- metadata +16 -6
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/README.textile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
h1. Delayed::Job
|
2
2
|
|
3
|
-
|
3
|
+
Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background.
|
4
4
|
|
5
5
|
It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks. Amongst those tasks are:
|
6
6
|
|
@@ -15,39 +15,43 @@ It is a direct extraction from Shopify where the job table is responsible for a
|
|
15
15
|
h2. Setup
|
16
16
|
|
17
17
|
The library evolves around a delayed_jobs table which looks as follows:
|
18
|
-
|
19
|
-
create_table :delayed_jobs, :force => true do |table|
|
18
|
+
<pre><code>create_table :delayed_jobs, :force => true do |table|
|
20
19
|
table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
|
21
20
|
table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
|
22
21
|
table.text :handler # YAML-encoded string of the object that will do work
|
22
|
+
table.string :job_type # Class name of the job object, for type-specific workers
|
23
23
|
table.string :last_error # reason for last failure (See Note below)
|
24
24
|
table.datetime :run_at # When to run. Could be Time.now for immediately, or sometime in the future.
|
25
25
|
table.datetime :locked_at # Set when a client is working on this object
|
26
26
|
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
|
27
27
|
table.string :locked_by # Who is working on this object (if locked)
|
28
|
+
table.datetime :finished_at # Used for statiscics / monitoring
|
28
29
|
table.timestamps
|
29
30
|
end
|
30
|
-
|
31
|
+
</code></pre>
|
31
32
|
On failure, the job is scheduled again in 5 seconds + N ** 4, where N is the number of retries.
|
32
33
|
|
33
|
-
The default MAX_ATTEMPTS is 25
|
34
|
-
|
34
|
+
The default MAX_ATTEMPTS is 25- jobs can override this value by responding to :max_attempts. After this, the job
|
35
|
+
either deleted (default), or left in the database with "failed_at" set. With the default of 25 attempts,
|
36
|
+
the last retry will be 20 days later, with the last interval being almost 100 hours.
|
35
37
|
|
36
38
|
The default MAX_RUN_TIME is 4.hours. If your job takes longer than that, another computer could pick it up. It's up to you to
|
37
39
|
make sure your job doesn't exceed this time. You should set this to the longest time you think the job could take.
|
38
40
|
|
39
|
-
By default, it will delete failed jobs
|
40
|
-
Delayed::Job.destroy_failed_jobs = false
|
41
|
+
By default, it will delete failed jobs. If you want to keep failed jobs, set
|
42
|
+
@Delayed::Job.destroy_failed_jobs = false@. The failed jobs will be marked with non-null failed_at.
|
41
43
|
|
42
|
-
|
44
|
+
Same thing for successful jobs. They're deleted by default and, to keep them, set @Delayed::Job.destroy_successful_jobs = false@. They will be marked with finished_at. This is useful for gathering statistics like how long jobs took between entering the queue (created_at) and being finished (finished_at).
|
43
45
|
|
44
|
-
|
46
|
+
Here is an example of changing job parameters in Rails:
|
47
|
+
<pre><code># config/initializers/delayed_job_config.rb
|
45
48
|
Delayed::Job.destroy_failed_jobs = false
|
49
|
+
Delayed::Job.destroy_successful_jobs = false
|
46
50
|
silence_warnings do
|
47
51
|
Delayed::Job.const_set("MAX_ATTEMPTS", 3)
|
48
52
|
Delayed::Job.const_set("MAX_RUN_TIME", 5.minutes)
|
49
53
|
end
|
50
|
-
|
54
|
+
</code></pre>
|
51
55
|
Note: If your error messages are long, consider changing last_error field to a :text instead of a :string (255 character limit).
|
52
56
|
|
53
57
|
|
@@ -55,20 +59,18 @@ h2. Usage
|
|
55
59
|
|
56
60
|
Jobs are simple ruby objects with a method called perform. Any object which responds to perform can be stuffed into the jobs table.
|
57
61
|
Job objects are serialized to yaml so that they can later be resurrected by the job runner.
|
58
|
-
|
59
|
-
class NewsletterJob < Struct.new(:text, :emails)
|
62
|
+
<pre><code>class NewsletterJob < Struct.new(:text, :emails)
|
60
63
|
def perform
|
61
64
|
emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
65
68
|
Delayed::Job.enqueue NewsletterJob.new('lorem ipsum...', Customers.find(:all).collect(&:email))
|
66
|
-
|
69
|
+
</code></pre>
|
67
70
|
There is also a second way to get jobs in the queue: send_later.
|
68
71
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
+
<pre><code>BatchImporter.new(Shop.find(1)).send_later(:import_massive_csv, massive_csv)
|
73
|
+
</code></pre>
|
72
74
|
|
73
75
|
This will simply create a Delayed::PerformableMethod job in the jobs table which serializes all the parameters you pass to it. There are some special smarts for active record objects
|
74
76
|
which are stored as their text representation and loaded from the database fresh when the job is actually run later.
|
@@ -80,8 +82,7 @@ You can invoke @rake jobs:work@ which will start working off jobs. You can cance
|
|
80
82
|
|
81
83
|
You can also run by writing a simple @script/job_runner@, and invoking it externally:
|
82
84
|
|
83
|
-
<pre><code
|
84
|
-
#!/usr/bin/env ruby
|
85
|
+
<pre><code>#!/usr/bin/env ruby
|
85
86
|
require File.dirname(__FILE__) + '/../config/environment'
|
86
87
|
|
87
88
|
Delayed::Worker.new.start
|
@@ -93,6 +94,13 @@ Keep in mind that each worker will check the database at least every 5 seconds.
|
|
93
94
|
|
94
95
|
Note: The rake task will exit if the database has any network connectivity problems.
|
95
96
|
|
97
|
+
If you only want to run specific types of jobs in a given worker, include them when initializing the worker:
|
98
|
+
|
99
|
+
<pre><code>
|
100
|
+
Delayed::Worker.new(:job_types => "SimpleJob").start
|
101
|
+
Delayed::Worker.new(:job_types => ["SimpleJob", "NewsletterJob"]).start
|
102
|
+
</pre></code>
|
103
|
+
|
96
104
|
h3. Cleaning up
|
97
105
|
|
98
106
|
You can invoke @rake jobs:clear@ to delete all jobs in the queue.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
begin
|
3
|
+
require 'jeweler'
|
4
|
+
|
5
|
+
Jeweler::Tasks.new do |s|
|
6
|
+
s.name = "delayed_job"
|
7
|
+
s.summary = "Database-backed asynchronous priority queue system -- Extracted from Shopify"
|
8
|
+
s.email = "tobi@leetsoft.com"
|
9
|
+
s.homepage = "http://github.com/tobi/delayed_job/tree/master"
|
10
|
+
s.description = "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."
|
11
|
+
s.authors = ["Tobias Lütke"]
|
12
|
+
|
13
|
+
s.has_rdoc = true
|
14
|
+
s.rdoc_options = ["--main", "README.textile", "--inline-source", "--line-numbers"]
|
15
|
+
s.extra_rdoc_files = ["README.textile"]
|
16
|
+
|
17
|
+
s.test_files = Dir['spec/**/*']
|
18
|
+
end
|
19
|
+
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'spec/rake/spectask'
|
26
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
27
|
+
spec.libs << 'lib' << 'spec'
|
28
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
29
|
+
end
|
30
|
+
|
31
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
32
|
+
spec.libs << 'lib' << 'spec'
|
33
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
34
|
+
spec.rcov = true
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
task :default => :spec
|
data/delayed_job.gemspec
CHANGED
@@ -1,41 +1,57 @@
|
|
1
|
-
#
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
|
-
s.name
|
5
|
-
s.version
|
6
|
-
s.date = "2008-11-28"
|
7
|
-
s.summary = "Database-backed asynchronous priority queue system -- Extracted from Shopify"
|
8
|
-
s.email = "tobi@leetsoft.com"
|
9
|
-
s.homepage = "http://github.com/tobi/delayed_job/tree/master"
|
10
|
-
s.description = "Delated_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."
|
11
|
-
s.authors = ["Tobias Lütke"]
|
4
|
+
s.name = %q{delayed_job}
|
5
|
+
s.version = "1.7.99"
|
12
6
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
s.
|
18
|
-
s.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Tobias L\303\274tke"]
|
9
|
+
s.date = %q{2009-08-06}
|
10
|
+
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.}
|
11
|
+
s.email = %q{tobi@leetsoft.com}
|
12
|
+
s.extra_rdoc_files = [
|
13
|
+
"README.textile"
|
14
|
+
]
|
15
|
+
s.files = [
|
16
|
+
".gitignore",
|
17
|
+
"MIT-LICENSE",
|
18
|
+
"README.textile",
|
19
|
+
"Rakefile",
|
20
|
+
"delayed_job.gemspec",
|
21
|
+
"generators/delayed_job/delayed_job_generator.rb",
|
22
|
+
"generators/delayed_job/templates/migration.rb",
|
23
|
+
"init.rb",
|
24
|
+
"lib/delayed/job.rb",
|
25
|
+
"lib/delayed/message_sending.rb",
|
26
|
+
"lib/delayed/performable_method.rb",
|
27
|
+
"lib/delayed/worker.rb",
|
28
|
+
"lib/delayed_job.rb",
|
29
|
+
"spec/database.rb",
|
30
|
+
"spec/delayed_method_spec.rb",
|
31
|
+
"spec/job_spec.rb",
|
32
|
+
"spec/story_spec.rb",
|
33
|
+
"tasks/jobs.rake",
|
34
|
+
"tasks/tasks.rb"
|
34
35
|
]
|
35
|
-
s.
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
s.homepage = %q{http://github.com/tobi/delayed_job/tree/master}
|
37
|
+
s.rdoc_options = ["--main", "README.textile", "--inline-source", "--line-numbers"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = %q{1.3.3}
|
40
|
+
s.summary = %q{Database-backed asynchronous priority queue system -- Extracted from Shopify}
|
41
|
+
s.test_files = [
|
42
|
+
"spec/delayed_method_spec.rb",
|
43
|
+
"spec/story_spec.rb",
|
44
|
+
"spec/database.rb",
|
45
|
+
"spec/job_spec.rb"
|
40
46
|
]
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
50
|
+
s.specification_version = 3
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
53
|
+
else
|
54
|
+
end
|
55
|
+
else
|
56
|
+
end
|
41
57
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class DelayedJobGenerator < Rails::Generator::Base
|
2
|
+
def manifest
|
3
|
+
record do |m|
|
4
|
+
m.migration_template 'migration.rb', 'db/migrate',
|
5
|
+
:assigns => { :migration_name => 'CreateDelayedJobs' },
|
6
|
+
:migration_file_name => "create_delayed_jobs"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CreateDelayedJobs < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :delayed_jobs do |t|
|
4
|
+
t.integer :priority, :default => 0
|
5
|
+
t.integer :attempts, :default => 0
|
6
|
+
t.text :handler
|
7
|
+
t.string :job_type
|
8
|
+
t.string :last_error
|
9
|
+
t.datetime :run_at
|
10
|
+
t.datetime :locked_at
|
11
|
+
t.datetime :failed_at
|
12
|
+
t.string :locked_by
|
13
|
+
t.datetime :finished_at
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.down
|
19
|
+
drop_table :delayed_jobs
|
20
|
+
end
|
21
|
+
end
|
data/lib/delayed/job.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
1
3
|
module Delayed
|
2
4
|
|
3
5
|
class DeserializationError < StandardError
|
@@ -16,20 +18,27 @@ module Delayed
|
|
16
18
|
cattr_accessor :destroy_failed_jobs
|
17
19
|
self.destroy_failed_jobs = true
|
18
20
|
|
21
|
+
# By default successful jobs are destroyed after finished.
|
22
|
+
# If you want to keep them around (for statistics/monitoring),
|
23
|
+
# set this to false.
|
24
|
+
cattr_accessor :destroy_successful_jobs
|
25
|
+
self.destroy_successful_jobs = true
|
26
|
+
|
19
27
|
# Every worker has a unique name which by default is the pid of the process.
|
20
28
|
# There are some advantages to overriding this with something which survives worker retarts:
|
21
29
|
# Workers can safely resume working on tasks which are locked by themselves. The worker will assume that it crashed before.
|
22
30
|
cattr_accessor :worker_name
|
23
31
|
self.worker_name = "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
|
24
32
|
|
25
|
-
NextTaskSQL = '(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR (locked_by = ?)) AND failed_at IS NULL'
|
33
|
+
NextTaskSQL = '(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR (locked_by = ?)) AND failed_at IS NULL AND finished_at IS NULL'
|
26
34
|
NextTaskOrder = 'priority DESC, run_at ASC'
|
27
35
|
|
28
36
|
ParseObjectFromYaml = /\!ruby\/\w+\:([^\s]+)/
|
29
37
|
|
30
|
-
cattr_accessor :min_priority, :max_priority
|
38
|
+
cattr_accessor :min_priority, :max_priority, :job_types
|
31
39
|
self.min_priority = nil
|
32
40
|
self.max_priority = nil
|
41
|
+
self.job_types = nil
|
33
42
|
|
34
43
|
# When a worker is exiting, make sure we don't have any locked jobs.
|
35
44
|
def self.clear_locks!
|
@@ -57,13 +66,14 @@ module Delayed
|
|
57
66
|
end
|
58
67
|
|
59
68
|
def payload_object=(object)
|
60
|
-
self['
|
69
|
+
self['job_type'] = object.class.to_s
|
70
|
+
self['handler'] = object.to_yaml
|
61
71
|
end
|
62
72
|
|
63
73
|
# Reschedule the job in the future (when a job fails).
|
64
74
|
# Uses an exponential scale depending on the number of failed attempts.
|
65
75
|
def reschedule(message, backtrace = [], time = nil)
|
66
|
-
if self.attempts < MAX_ATTEMPTS
|
76
|
+
if self.attempts < (payload_object.send(:max_attempts) rescue MAX_ATTEMPTS)
|
67
77
|
time ||= Job.db_time_now + (attempts ** 4) + 5
|
68
78
|
|
69
79
|
self.attempts += 1
|
@@ -89,11 +99,11 @@ module Delayed
|
|
89
99
|
|
90
100
|
begin
|
91
101
|
runtime = Benchmark.realtime do
|
92
|
-
|
93
|
-
destroy
|
102
|
+
Timeout.timeout(max_run_time.to_i) { invoke_job }
|
94
103
|
end
|
95
|
-
|
96
|
-
|
104
|
+
destroy_successful_jobs ? destroy :
|
105
|
+
update_attribute(:finished_at, Time.now)
|
106
|
+
Delayed::Worker.logger.info "* [JOB] #{name} completed after %.4f" % runtime
|
97
107
|
return true # did work
|
98
108
|
rescue Exception => e
|
99
109
|
reschedule e.message, e.backtrace
|
@@ -136,6 +146,11 @@ module Delayed
|
|
136
146
|
conditions << max_priority
|
137
147
|
end
|
138
148
|
|
149
|
+
if self.job_types
|
150
|
+
sql << ' AND (job_type IN (?))'
|
151
|
+
conditions << job_types
|
152
|
+
end
|
153
|
+
|
139
154
|
conditions.unshift(sql)
|
140
155
|
|
141
156
|
records = ActiveRecord::Base.silence do
|
data/lib/delayed/worker.rb
CHANGED
@@ -13,6 +13,7 @@ module Delayed
|
|
13
13
|
@quiet = options[:quiet]
|
14
14
|
Delayed::Job.min_priority = options[:min_priority] if options.has_key?(:min_priority)
|
15
15
|
Delayed::Job.max_priority = options[:max_priority] if options.has_key?(:max_priority)
|
16
|
+
Delayed::Job.job_types = options[:job_types] if options.has_key?(:job_types)
|
16
17
|
end
|
17
18
|
|
18
19
|
def start
|
data/spec/database.rb
CHANGED
@@ -3,13 +3,17 @@ $:.unshift(File.dirname(__FILE__) + '/../../rspec/lib')
|
|
3
3
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'active_record'
|
6
|
-
gem 'sqlite3-ruby'
|
7
6
|
|
8
7
|
require File.dirname(__FILE__) + '/../init'
|
9
8
|
require 'spec'
|
10
|
-
|
11
|
-
|
12
|
-
ActiveRecord::Base.
|
9
|
+
|
10
|
+
logger = Logger.new('/tmp/dj.log')
|
11
|
+
ActiveRecord::Base.logger = logger
|
12
|
+
Delayed::Worker.logger = logger
|
13
|
+
ActiveRecord::Base.establish_connection(
|
14
|
+
:adapter => (PLATFORM =~ /java/ ? 'jdbcsqlite3' : 'sqlite3'),
|
15
|
+
:database => '/tmp/jobs.sqlite'
|
16
|
+
)
|
13
17
|
ActiveRecord::Migration.verbose = false
|
14
18
|
|
15
19
|
ActiveRecord::Schema.define do
|
@@ -18,11 +22,13 @@ ActiveRecord::Schema.define do
|
|
18
22
|
table.integer :priority, :default => 0
|
19
23
|
table.integer :attempts, :default => 0
|
20
24
|
table.text :handler
|
25
|
+
table.string :job_type
|
21
26
|
table.string :last_error
|
22
27
|
table.datetime :run_at
|
23
28
|
table.datetime :locked_at
|
24
29
|
table.string :locked_by
|
25
30
|
table.datetime :failed_at
|
31
|
+
table.datetime :finished_at
|
26
32
|
table.timestamps
|
27
33
|
end
|
28
34
|
|
@@ -35,8 +41,8 @@ end
|
|
35
41
|
|
36
42
|
# Purely useful for test cases...
|
37
43
|
class Story < ActiveRecord::Base
|
38
|
-
def tell; text; end
|
44
|
+
def tell; text; end
|
39
45
|
def whatever(n, _); tell*n; end
|
40
|
-
|
46
|
+
|
41
47
|
handle_asynchronously :whatever
|
42
48
|
end
|
data/spec/job_spec.rb
CHANGED
@@ -5,11 +5,19 @@ class SimpleJob
|
|
5
5
|
def perform; @@runs += 1; end
|
6
6
|
end
|
7
7
|
|
8
|
+
class CustomJob < SimpleJob
|
9
|
+
def max_attempts; 3; end
|
10
|
+
end
|
11
|
+
|
8
12
|
class ErrorJob
|
9
13
|
cattr_accessor :runs; self.runs = 0
|
10
14
|
def perform; raise 'did not work'; end
|
11
15
|
end
|
12
16
|
|
17
|
+
class LongRunningJob
|
18
|
+
def perform; sleep 250; end
|
19
|
+
end
|
20
|
+
|
13
21
|
module M
|
14
22
|
class ModuleJob
|
15
23
|
cattr_accessor :runs; self.runs = 0
|
@@ -69,8 +77,43 @@ describe Delayed::Job do
|
|
69
77
|
|
70
78
|
SimpleJob.runs.should == 1
|
71
79
|
end
|
72
|
-
|
73
|
-
|
80
|
+
|
81
|
+
it "should work on specified job types" do
|
82
|
+
SimpleJob.runs.should == 0
|
83
|
+
|
84
|
+
Delayed::Job.job_types = "SimpleJob"
|
85
|
+
Delayed::Job.enqueue SimpleJob.new
|
86
|
+
Delayed::Job.work_off
|
87
|
+
|
88
|
+
SimpleJob.runs.should == 1
|
89
|
+
|
90
|
+
Delayed::Job.job_types = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should not work on unspecified job types" do
|
94
|
+
SimpleJob.runs.should == 0
|
95
|
+
|
96
|
+
Delayed::Job.job_types = "AnotherJob"
|
97
|
+
Delayed::Job.enqueue SimpleJob.new
|
98
|
+
Delayed::Job.work_off
|
99
|
+
|
100
|
+
SimpleJob.runs.should == 0
|
101
|
+
|
102
|
+
Delayed::Job.job_types = nil
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should work on specified job types even when it's a list" do
|
106
|
+
SimpleJob.runs.should == 0
|
107
|
+
|
108
|
+
Delayed::Job.job_types = %w( Whatever SimpleJob )
|
109
|
+
Delayed::Job.enqueue SimpleJob.new
|
110
|
+
Delayed::Job.work_off
|
111
|
+
|
112
|
+
SimpleJob.runs.should == 1
|
113
|
+
|
114
|
+
Delayed::Job.job_types = nil
|
115
|
+
end
|
116
|
+
|
74
117
|
it "should work with eval jobs" do
|
75
118
|
$eval_job_ran = false
|
76
119
|
|
@@ -92,7 +135,49 @@ describe Delayed::Job do
|
|
92
135
|
|
93
136
|
M::ModuleJob.runs.should == 1
|
94
137
|
end
|
95
|
-
|
138
|
+
|
139
|
+
it "should be destroyed if it succeeded and we want to destroy jobs" do
|
140
|
+
default = Delayed::Job.destroy_successful_jobs
|
141
|
+
Delayed::Job.destroy_successful_jobs = true
|
142
|
+
|
143
|
+
Delayed::Job.enqueue SimpleJob.new
|
144
|
+
Delayed::Job.work_off
|
145
|
+
|
146
|
+
Delayed::Job.count.should == 0
|
147
|
+
|
148
|
+
Delayed::Job.destroy_successful_jobs = default
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should be kept if it succeeded and we don't want to destroy jobs" do
|
152
|
+
default = Delayed::Job.destroy_successful_jobs
|
153
|
+
Delayed::Job.destroy_successful_jobs = false
|
154
|
+
|
155
|
+
Delayed::Job.enqueue SimpleJob.new
|
156
|
+
Delayed::Job.work_off
|
157
|
+
|
158
|
+
Delayed::Job.count.should == 1
|
159
|
+
|
160
|
+
Delayed::Job.destroy_successful_jobs = default
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should be finished if it succeeded and we don't want to destroy jobs" do
|
164
|
+
default = Delayed::Job.destroy_successful_jobs
|
165
|
+
Delayed::Job.destroy_successful_jobs = false
|
166
|
+
@job = Delayed::Job.create :payload_object => SimpleJob.new
|
167
|
+
|
168
|
+
@job.reload.finished_at.should == nil
|
169
|
+
Delayed::Job.work_off
|
170
|
+
@job.reload.finished_at.should_not == nil
|
171
|
+
|
172
|
+
Delayed::Job.destroy_successful_jobs = default
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should never find finished jobs" do
|
176
|
+
@job = Delayed::Job.create :payload_object => SimpleJob.new,
|
177
|
+
:finished_at => Time.now
|
178
|
+
Delayed::Job.find_available(1).length.should == 0
|
179
|
+
end
|
180
|
+
|
96
181
|
it "should re-schedule by about 1 second at first and increment this more and more minutes when it fails to execute properly" do
|
97
182
|
Delayed::Job.enqueue ErrorJob.new
|
98
183
|
Delayed::Job.work_off(1)
|
@@ -100,7 +185,7 @@ describe Delayed::Job do
|
|
100
185
|
job = Delayed::Job.find(:first)
|
101
186
|
|
102
187
|
job.last_error.should =~ /did not work/
|
103
|
-
job.last_error.should =~ /job_spec.rb:
|
188
|
+
job.last_error.should =~ /job_spec.rb:14:in `perform'/
|
104
189
|
job.attempts.should == 1
|
105
190
|
|
106
191
|
job.run_at.should > Delayed::Job.db_time_now - 10.minutes
|
@@ -171,6 +256,24 @@ describe Delayed::Job do
|
|
171
256
|
Delayed::Job.destroy_failed_jobs = default
|
172
257
|
end
|
173
258
|
|
259
|
+
it "should allow jobs to override max_attemps and behave appropriately" do
|
260
|
+
default = Delayed::Job.destroy_failed_jobs
|
261
|
+
Delayed::Job.destroy_failed_jobs = true
|
262
|
+
|
263
|
+
@job = Delayed::Job.create :payload_object => CustomJob.new, :attempts => 5
|
264
|
+
@job.should_receive(:destroy)
|
265
|
+
@job.reschedule 'FAIL'
|
266
|
+
|
267
|
+
Delayed::Job.destroy_failed_jobs = default
|
268
|
+
end
|
269
|
+
|
270
|
+
it "should fail after MAX_RUN_TIME" do
|
271
|
+
@job = Delayed::Job.create :payload_object => LongRunningJob.new
|
272
|
+
Delayed::Job.reserve_and_run_one_job(1.second)
|
273
|
+
@job.reload.last_error.should =~ /expired/
|
274
|
+
@job.attempts.should == 1
|
275
|
+
end
|
276
|
+
|
174
277
|
it "should never find failed jobs" do
|
175
278
|
@job = Delayed::Job.create :payload_object => SimpleJob.new, :attempts => 50, :failed_at => Time.now
|
176
279
|
Delayed::Job.find_available(1).length.should == 0
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blaxter-delayed_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.7.
|
4
|
+
version: 1.7.99
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- "Tobias L\xC3\xBCtke"
|
@@ -9,11 +9,11 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-08-06 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
|
-
description:
|
16
|
+
description: 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.
|
17
17
|
email: tobi@leetsoft.com
|
18
18
|
executables: []
|
19
19
|
|
@@ -22,15 +22,23 @@ extensions: []
|
|
22
22
|
extra_rdoc_files:
|
23
23
|
- README.textile
|
24
24
|
files:
|
25
|
+
- .gitignore
|
25
26
|
- MIT-LICENSE
|
26
27
|
- README.textile
|
28
|
+
- Rakefile
|
27
29
|
- delayed_job.gemspec
|
30
|
+
- generators/delayed_job/delayed_job_generator.rb
|
31
|
+
- generators/delayed_job/templates/migration.rb
|
28
32
|
- init.rb
|
29
33
|
- lib/delayed/job.rb
|
30
34
|
- lib/delayed/message_sending.rb
|
31
35
|
- lib/delayed/performable_method.rb
|
32
36
|
- lib/delayed/worker.rb
|
33
37
|
- lib/delayed_job.rb
|
38
|
+
- spec/database.rb
|
39
|
+
- spec/delayed_method_spec.rb
|
40
|
+
- spec/job_spec.rb
|
41
|
+
- spec/story_spec.rb
|
34
42
|
- tasks/jobs.rake
|
35
43
|
- tasks/tasks.rb
|
36
44
|
has_rdoc: false
|
@@ -40,6 +48,8 @@ post_install_message:
|
|
40
48
|
rdoc_options:
|
41
49
|
- --main
|
42
50
|
- README.textile
|
51
|
+
- --inline-source
|
52
|
+
- --line-numbers
|
43
53
|
require_paths:
|
44
54
|
- lib
|
45
55
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -59,10 +69,10 @@ requirements: []
|
|
59
69
|
rubyforge_project:
|
60
70
|
rubygems_version: 1.3.5
|
61
71
|
signing_key:
|
62
|
-
specification_version:
|
72
|
+
specification_version: 3
|
63
73
|
summary: Database-backed asynchronous priority queue system -- Extracted from Shopify
|
64
74
|
test_files:
|
65
|
-
- spec/database.rb
|
66
75
|
- spec/delayed_method_spec.rb
|
67
|
-
- spec/job_spec.rb
|
68
76
|
- spec/story_spec.rb
|
77
|
+
- spec/database.rb
|
78
|
+
- spec/job_spec.rb
|