delayed_job 2.0.8 → 2.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.gem
2
+ *.swp
data/README.textile CHANGED
@@ -1,6 +1,6 @@
1
1
  h1. Delayed::Job
2
2
 
3
- Delated_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background.
3
+ Delated_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
 
@@ -9,24 +9,21 @@ It is a direct extraction from Shopify where the job table is responsible for a
9
9
  * http downloads
10
10
  * updating smart collections
11
11
  * updating solr, our search server, after product changes
12
- * batch imports
13
- * spam checks
12
+ * batch imports
13
+ * spam checks
14
14
 
15
15
  h2. Installation
16
16
 
17
- This version is for Rails 2.x only. For rails 3 support, install delayed_job 2.1.
18
-
19
17
  To install as a gem, add the following to @config/environment.rb@:
20
18
 
21
19
  <pre>
22
- config.gem 'delayed_job', :version => '~>2.0.4'
20
+ config.gem 'delayed_job'
23
21
  </pre>
24
22
 
25
23
  Rake tasks are not automatically loaded from gems, so you'll need to add the following to your Rakefile:
26
24
 
27
25
  <pre>
28
26
  begin
29
- gem 'delayed_job', '~>2.0.4'
30
27
  require 'delayed/tasks'
31
28
  rescue LoadError
32
29
  STDERR.puts "Run `rake gems:install` to install delayed_job"
@@ -36,15 +33,11 @@ end
36
33
  To install as a plugin:
37
34
 
38
35
  <pre>
39
- script/plugin install git://github.com/collectiveidea/delayed_job.git -r v2.0
36
+ script/plugin install git://github.com/collectiveidea/delayed_job.git
40
37
  </pre>
41
38
 
42
39
  After delayed_job is installed, you will need to setup the backend.
43
40
 
44
- h3. Dependencies
45
-
46
- delayed_job depends upon version 1.0.10 of the daemons gem. delayed_job is incompatible with the newest (1.1.0) daemons gem.
47
-
48
41
  h2. Backends
49
42
 
50
43
  delayed_job supports multiple backends for storing the job queue. There are currently implementations for Active Record, MongoMapper, and DataMapper.
@@ -60,12 +53,8 @@ $ rake db:migrate
60
53
 
61
54
  h3. MongoMapper
62
55
 
63
- You must use @MongoMapper.setup@ in the initializer:
64
-
65
56
  <pre>
66
- config = YAML::load(File.read(Rails.root.join('config/mongo.yml')))
67
- MongoMapper.setup(config, Rails.env)
68
-
57
+ # config/initializers/delayed_job.rb
69
58
  Delayed::Worker.backend = :mongo_mapper
70
59
  </pre>
71
60
 
@@ -103,39 +92,6 @@ device = Device.new
103
92
  device.deliver
104
93
  </pre>
105
94
 
106
- handle_asynchronously can take as options anything you can pass to delay. In addition the values can be Proc objects allowing call time evaluation of the value. For some examples:
107
-
108
- <pre>
109
- class LongTasks
110
- def send_mailer
111
- # Some other code
112
- end
113
- handle_asynchronously :send_mailer, :priority => 20
114
-
115
- def in_the_future
116
- # Some other code
117
- end
118
- # 5.minutes.from_now will be evaluated when in_the_future is called
119
- handle_asynchronously :in_the_future, :run_at => Proc.new { 5.minutes.from_now }
120
-
121
- def self.when_to_run
122
- 2.hours.from_now
123
- end
124
-
125
- def call_a_class_method
126
- # Some other code
127
- end
128
- handle_asynchronously :call_a_class_method, :run_at => Proc.new { when_to_run }
129
-
130
- attr_reader :how_important
131
-
132
- def call_an_instance_method
133
- # Some other code
134
- end
135
- handle_asynchronously :call_an_instance_method, :priority => Proc.new {|i| i.how_important }
136
- end
137
- </pre>
138
-
139
95
  h2. Running Jobs
140
96
 
141
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`.
@@ -151,19 +107,19 @@ $ RAILS_ENV=production script/delayed_job stop
151
107
 
152
108
  Workers can be running on any computer, as long as they have access to the database and their clock is in sync. Keep in mind that each worker will check the database at least every 5 seconds.
153
109
 
154
- You can also invoke @rake jobs:work@ which will start working off jobs. You can cancel the rake task with @CTRL-C@.
110
+ You can also invoke @rake jobs:work@ which will start working off jobs. You can cancel the rake task with @CTRL-C@.
155
111
 
156
112
  h2. Custom Jobs
157
113
 
158
- Jobs are simple ruby objects with a method called perform. Any object which responds to perform can be stuffed into the jobs table. Job objects are serialized to yaml so that they can later be resurrected by the job runner.
114
+ Jobs are simple ruby objects with a method called perform. Any object which responds to perform can be stuffed into the jobs table. Job objects are serialized to yaml so that they can later be resurrected by the job runner.
159
115
 
160
116
  <pre>
161
117
  class NewsletterJob < Struct.new(:text, :emails)
162
118
  def perform
163
119
  emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
164
- end
165
- end
166
-
120
+ end
121
+ end
122
+
167
123
  Delayed::Job.enqueue NewsletterJob.new('lorem ipsum...', Customers.find(:all).collect(&:email))
168
124
  </pre>
169
125
 
@@ -173,17 +129,17 @@ You can also add an optional on_permanent_failure method which will run if the j
173
129
  class ParanoidNewsletterJob < NewsletterJob
174
130
  def perform
175
131
  emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
176
- end
132
+ end
177
133
 
178
134
  def on_permanent_failure
179
135
  page_sysadmin_in_the_middle_of_the_night
180
136
  end
181
- end
137
+ end
182
138
  </pre>
183
139
 
184
140
  h2. Gory Details
185
141
 
186
- The library evolves around a delayed_jobs table which looks as follows:
142
+ The library evolves around a delayed_jobs table which looks as follows:
187
143
 
188
144
  <pre>
189
145
  create_table :delayed_jobs, :force => true do |table|
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+ begin
3
+ require 'jeweler'
4
+ rescue LoadError
5
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
6
+ exit 1
7
+ end
8
+
9
+ Jeweler::Tasks.new do |s|
10
+ s.name = "delayed_job"
11
+ s.summary = "Database-backed asynchronous priority queue system -- Extracted from Shopify"
12
+ s.email = "tobi@leetsoft.com"
13
+ s.homepage = "http://github.com/collectiveidea/delayed_job"
14
+ 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.\n\nThis gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)."
15
+ s.authors = ["Brandon Keepers", "Tobias Lütke"]
16
+
17
+ s.has_rdoc = true
18
+ s.rdoc_options = ["--main", "README.textile", "--inline-source", "--line-numbers"]
19
+ s.extra_rdoc_files = ["README.textile"]
20
+
21
+ s.test_files = Dir['spec/*_spec.rb']
22
+
23
+ s.add_dependency "daemons"
24
+ s.add_development_dependency "rspec"
25
+ s.add_development_dependency "sqlite3-ruby"
26
+ s.add_development_dependency "activerecord"
27
+ s.add_development_dependency "mongo_mapper"
28
+ s.add_development_dependency "dm-core"
29
+ s.add_development_dependency "dm-observer"
30
+ s.add_development_dependency "dm-aggregates"
31
+ s.add_development_dependency "dm-validations"
32
+ s.add_development_dependency "do_sqlite3"
33
+ s.add_development_dependency "couchrest"
34
+ end
35
+
36
+ require 'spec/rake/spectask'
37
+
38
+
39
+ task :default do
40
+ %w(2.3.5 3.0.0.beta3).each do |version|
41
+ puts "Running specs with Rails #{version}"
42
+ system("RAILS_VERSION=#{version} rake -s spec;")
43
+ end
44
+ end
45
+
46
+ desc 'Run the specs'
47
+ Spec::Rake::SpecTask.new(:spec) do |t|
48
+ t.libs << 'lib'
49
+ t.pattern = 'spec/*_spec.rb'
50
+ t.verbose = true
51
+ end
52
+ task :spec => :check_dependencies
53
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.1.0.pre
data/benchmarks.rb ADDED
@@ -0,0 +1,33 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/lib')
2
+ require 'rubygems'
3
+ require 'logger'
4
+ require 'delayed_job'
5
+ require 'benchmark'
6
+
7
+ RAILS_ENV = 'test'
8
+
9
+ Delayed::Worker.logger = Logger.new('/dev/null')
10
+
11
+ BACKENDS = []
12
+ Dir.glob("#{File.dirname(__FILE__)}/spec/setup/*.rb") do |backend|
13
+ begin
14
+ backend = File.basename(backend, '.rb')
15
+ require "spec/setup/#{backend}"
16
+ BACKENDS << backend.to_sym
17
+ rescue LoadError
18
+ puts "Unable to load #{backend} backend! #{$!}"
19
+ end
20
+ end
21
+
22
+
23
+ Benchmark.bm(10) do |x|
24
+ BACKENDS.each do |backend|
25
+ require "spec/setup/#{backend}"
26
+ Delayed::Worker.backend = backend
27
+
28
+ n = 10000
29
+ n.times { "foo".delay.length }
30
+
31
+ x.report(backend.to_s) { Delayed::Worker.new(:quiet => true).work_off(n) }
32
+ end
33
+ end
@@ -0,0 +1,125 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{delayed_job}
8
+ s.version = "2.1.0.pre"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Brandon Keepers", "Tobias L\303\274tke"]
12
+ s.date = %q{2010-05-21}
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
+
15
+ This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job).}
16
+ s.email = %q{tobi@leetsoft.com}
17
+ s.extra_rdoc_files = [
18
+ "README.textile"
19
+ ]
20
+ s.files = [
21
+ ".gitignore",
22
+ "MIT-LICENSE",
23
+ "README.textile",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "benchmarks.rb",
27
+ "contrib/delayed_job.monitrc",
28
+ "contrib/delayed_job_multiple.monitrc",
29
+ "delayed_job.gemspec",
30
+ "generators/delayed_job/delayed_job_generator.rb",
31
+ "generators/delayed_job/templates/migration.rb",
32
+ "generators/delayed_job/templates/script",
33
+ "init.rb",
34
+ "lib/delayed/backend/active_record.rb",
35
+ "lib/delayed/backend/base.rb",
36
+ "lib/delayed/backend/couch_rest.rb",
37
+ "lib/delayed/backend/data_mapper.rb",
38
+ "lib/delayed/backend/mongo_mapper.rb",
39
+ "lib/delayed/command.rb",
40
+ "lib/delayed/message_sending.rb",
41
+ "lib/delayed/performable_method.rb",
42
+ "lib/delayed/railtie.rb",
43
+ "lib/delayed/recipes.rb",
44
+ "lib/delayed/tasks.rb",
45
+ "lib/delayed/worker.rb",
46
+ "lib/delayed/yaml_ext.rb",
47
+ "lib/delayed_job.rb",
48
+ "lib/generators/delayed_job/delayed_job_generator.rb",
49
+ "lib/generators/delayed_job/templates/migration.rb",
50
+ "lib/generators/delayed_job/templates/script",
51
+ "rails/init.rb",
52
+ "recipes/delayed_job.rb",
53
+ "spec/autoloaded/clazz.rb",
54
+ "spec/autoloaded/struct.rb",
55
+ "spec/backend/active_record_job_spec.rb",
56
+ "spec/backend/couch_rest_job_spec.rb",
57
+ "spec/backend/data_mapper_job_spec.rb",
58
+ "spec/backend/mongo_mapper_job_spec.rb",
59
+ "spec/backend/shared_backend_spec.rb",
60
+ "spec/message_sending_spec.rb",
61
+ "spec/performable_method_spec.rb",
62
+ "spec/sample_jobs.rb",
63
+ "spec/setup/active_record.rb",
64
+ "spec/setup/couch_rest.rb",
65
+ "spec/setup/data_mapper.rb",
66
+ "spec/setup/mongo_mapper.rb",
67
+ "spec/spec_helper.rb",
68
+ "spec/worker_spec.rb",
69
+ "tasks/jobs.rake"
70
+ ]
71
+ s.homepage = %q{http://github.com/collectiveidea/delayed_job}
72
+ s.rdoc_options = ["--main", "README.textile", "--inline-source", "--line-numbers"]
73
+ s.require_paths = ["lib"]
74
+ s.rubygems_version = %q{1.3.6}
75
+ s.summary = %q{Database-backed asynchronous priority queue system -- Extracted from Shopify}
76
+ s.test_files = [
77
+ "spec/message_sending_spec.rb",
78
+ "spec/performable_method_spec.rb",
79
+ "spec/worker_spec.rb"
80
+ ]
81
+
82
+ if s.respond_to? :specification_version then
83
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
84
+ s.specification_version = 3
85
+
86
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
87
+ s.add_runtime_dependency(%q<daemons>, [">= 0"])
88
+ s.add_development_dependency(%q<rspec>, [">= 0"])
89
+ s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
90
+ s.add_development_dependency(%q<activerecord>, [">= 0"])
91
+ s.add_development_dependency(%q<mongo_mapper>, [">= 0"])
92
+ s.add_development_dependency(%q<dm-core>, [">= 0"])
93
+ s.add_development_dependency(%q<dm-observer>, [">= 0"])
94
+ s.add_development_dependency(%q<dm-aggregates>, [">= 0"])
95
+ s.add_development_dependency(%q<dm-validations>, [">= 0"])
96
+ s.add_development_dependency(%q<do_sqlite3>, [">= 0"])
97
+ s.add_development_dependency(%q<couchrest>, [">= 0"])
98
+ else
99
+ s.add_dependency(%q<daemons>, [">= 0"])
100
+ s.add_dependency(%q<rspec>, [">= 0"])
101
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
102
+ s.add_dependency(%q<activerecord>, [">= 0"])
103
+ s.add_dependency(%q<mongo_mapper>, [">= 0"])
104
+ s.add_dependency(%q<dm-core>, [">= 0"])
105
+ s.add_dependency(%q<dm-observer>, [">= 0"])
106
+ s.add_dependency(%q<dm-aggregates>, [">= 0"])
107
+ s.add_dependency(%q<dm-validations>, [">= 0"])
108
+ s.add_dependency(%q<do_sqlite3>, [">= 0"])
109
+ s.add_dependency(%q<couchrest>, [">= 0"])
110
+ end
111
+ else
112
+ s.add_dependency(%q<daemons>, [">= 0"])
113
+ s.add_dependency(%q<rspec>, [">= 0"])
114
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
115
+ s.add_dependency(%q<activerecord>, [">= 0"])
116
+ s.add_dependency(%q<mongo_mapper>, [">= 0"])
117
+ s.add_dependency(%q<dm-core>, [">= 0"])
118
+ s.add_dependency(%q<dm-observer>, [">= 0"])
119
+ s.add_dependency(%q<dm-aggregates>, [">= 0"])
120
+ s.add_dependency(%q<dm-validations>, [">= 0"])
121
+ s.add_dependency(%q<do_sqlite3>, [">= 0"])
122
+ s.add_dependency(%q<couchrest>, [">= 0"])
123
+ end
124
+ end
125
+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'rails', 'init')
@@ -1,16 +1,16 @@
1
1
  require 'active_record'
2
2
 
3
3
  class ActiveRecord::Base
4
- def self.load_for_delayed_job(id)
5
- if id
6
- find(id)
7
- else
8
- super
9
- end
10
- end
4
+ yaml_as "tag:ruby.yaml.org,2002:ActiveRecord"
11
5
 
12
- def dump_for_delayed_job
13
- "#{self.class};#{id}"
6
+ def self.yaml_new(klass, tag, val)
7
+ klass.find(val['attributes']['id'])
8
+ rescue ActiveRecord::RecordNotFound
9
+ nil
10
+ end
11
+
12
+ def to_yaml_properties
13
+ ['@attributes']
14
14
  end
15
15
  end
16
16
 
@@ -63,10 +63,8 @@ module Delayed
63
63
  self.class.update_all(["locked_at = ?", now], ["id = ? and locked_by = ?", id, worker])
64
64
  end
65
65
  if affected_rows == 1
66
- self.locked_at = now
67
- self.locked_by = worker
68
- self.locked_at_will_change!
69
- self.locked_by_will_change!
66
+ self.locked_at = now
67
+ self.locked_by = worker
70
68
  return true
71
69
  else
72
70
  return false
@@ -1,5 +1,8 @@
1
1
  module Delayed
2
2
  module Backend
3
+ class DeserializationError < StandardError
4
+ end
5
+
3
6
  module Base
4
7
  def self.included(base)
5
8
  base.extend ClassMethods
@@ -13,19 +16,11 @@ module Delayed
13
16
  raise ArgumentError, 'Cannot enqueue items which do not respond to perform'
14
17
  end
15
18
 
16
- priority = args.first || Delayed::Worker.default_priority
19
+ priority = args.first || 0
17
20
  run_at = args[1]
18
21
  self.create(:payload_object => object, :priority => priority.to_i, :run_at => run_at)
19
22
  end
20
-
21
- def reserve(worker, max_run_time = Worker.max_run_time)
22
- # We get up to 5 jobs from the db. In case we cannot get exclusive access to a job we try the next.
23
- # this leads to a more even distribution of jobs across the worker processes
24
- find_available(worker.name, 5, max_run_time).detect do |job|
25
- job.lock_exclusively!(max_run_time, worker.name)
26
- end
27
- end
28
-
23
+
29
24
  # Hook method that is called before a new worker is forked
30
25
  def before_fork
31
26
  end
@@ -50,20 +45,19 @@ module Delayed
50
45
  def name
51
46
  @name ||= begin
52
47
  payload = payload_object
53
- if payload.respond_to?(:display_name)
54
- payload.display_name
55
- else
56
- payload.class.name
57
- end
48
+ payload.respond_to?(:display_name) ? payload.display_name : payload.class.name
58
49
  end
59
50
  end
60
51
 
61
52
  def payload_object=(object)
62
- self['handler'] = object.to_yaml
53
+ self.handler = object.to_yaml
63
54
  end
64
55
 
65
56
  def payload_object
66
- @payload_object ||= deserialize(self['handler'])
57
+ @payload_object ||= YAML.load(self.handler)
58
+ rescue TypeError, LoadError, NameError => e
59
+ raise DeserializationError,
60
+ "Job failed to load: #{e.message}. Try to manually require the required file. Handler: #{handler.inspect}"
67
61
  end
68
62
 
69
63
  # Moved into its own method so that new_relic can trace it.
@@ -76,51 +70,13 @@ module Delayed
76
70
  self.locked_at = nil
77
71
  self.locked_by = nil
78
72
  end
79
-
80
- def reschedule_at
81
- payload_object.respond_to?(:reschedule_at) ?
82
- payload_object.reschedule_at(self.class.db_time_now, attempts) :
83
- self.class.db_time_now + (attempts ** 4) + 5
84
- end
85
-
86
- def max_attempts
87
- payload_object.max_attempts if payload_object.respond_to?(:max_attempts)
88
- end
89
-
90
- private
91
-
92
- def deserialize(source)
93
- handler = YAML.load(source) rescue nil
94
-
95
- unless handler.respond_to?(:perform)
96
- if handler.nil? && source =~ ParseObjectFromYaml
97
- handler_class = $1
98
- end
99
- attempt_to_load(handler_class || handler.class)
100
- handler = YAML.load(source)
101
- end
102
-
103
- return handler if handler.respond_to?(:perform)
104
-
105
- raise DeserializationError,
106
- 'Job failed to load: Unknown handler. Try to manually require the appropriate file.'
107
- rescue TypeError, LoadError, NameError, ArgumentError => e
108
- raise DeserializationError,
109
- "Job failed to load: #{e.message}. Try to manually require the required file."
110
- end
111
-
112
- # Constantize the object so that ActiveSupport can attempt
113
- # its auto loading magic. Will raise LoadError if not successful.
114
- def attempt_to_load(klass)
115
- klass.constantize
116
- end
117
73
 
118
74
  protected
119
75
 
120
76
  def set_default_run_at
121
77
  self.run_at ||= self.class.db_time_now
122
- end
123
-
78
+ end
79
+
124
80
  end
125
81
  end
126
- end
82
+ end