topprospect-delayed_job 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.textile +210 -0
  3. data/contrib/delayed_job.monitrc +14 -0
  4. data/contrib/delayed_job_multiple.monitrc +23 -0
  5. data/lib/delayed/backend/active_record.rb +97 -0
  6. data/lib/delayed/backend/active_record.rb.orig +105 -0
  7. data/lib/delayed/backend/base.rb +85 -0
  8. data/lib/delayed/backend/couch_rest.rb +109 -0
  9. data/lib/delayed/backend/data_mapper.rb +121 -0
  10. data/lib/delayed/backend/mongo_mapper.rb +106 -0
  11. data/lib/delayed/command.rb +107 -0
  12. data/lib/delayed/message_sending.rb +45 -0
  13. data/lib/delayed/performable_method.rb +27 -0
  14. data/lib/delayed/railtie.rb +14 -0
  15. data/lib/delayed/recipes.rb +31 -0
  16. data/lib/delayed/tasks.rb +20 -0
  17. data/lib/delayed/tasks.rb.orig +26 -0
  18. data/lib/delayed/worker.rb +213 -0
  19. data/lib/delayed/worker.rb.orig +202 -0
  20. data/lib/delayed/yaml_ext.rb +40 -0
  21. data/lib/delayed_job.rb +15 -0
  22. data/lib/generators/delayed_job/delayed_job_generator.rb +34 -0
  23. data/lib/generators/delayed_job/templates/migration.rb +21 -0
  24. data/lib/generators/delayed_job/templates/script +5 -0
  25. data/recipes/delayed_job.rb +1 -0
  26. data/spec/autoloaded/clazz.rb +7 -0
  27. data/spec/autoloaded/struct.rb +7 -0
  28. data/spec/backend/active_record_job_spec.rb +54 -0
  29. data/spec/backend/couch_rest_job_spec.rb +15 -0
  30. data/spec/backend/data_mapper_job_spec.rb +16 -0
  31. data/spec/backend/mongo_mapper_job_spec.rb +94 -0
  32. data/spec/backend/shared_backend_spec.rb +280 -0
  33. data/spec/message_sending_spec.rb +51 -0
  34. data/spec/performable_method_spec.rb +48 -0
  35. data/spec/sample_jobs.rb +25 -0
  36. data/spec/setup/active_record.rb +54 -0
  37. data/spec/setup/couch_rest.rb +7 -0
  38. data/spec/setup/data_mapper.rb +8 -0
  39. data/spec/setup/mongo_mapper.rb +17 -0
  40. data/spec/spec_helper.rb +31 -0
  41. data/spec/worker_spec.rb +214 -0
  42. metadata +300 -0
@@ -0,0 +1,121 @@
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
@@ -0,0 +1,106 @@
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
@@ -0,0 +1,107 @@
1
+ require 'rubygems'
2
+ require 'daemons'
3
+ require 'optparse'
4
+
5
+ module Delayed
6
+ class Command
7
+ attr_accessor :worker_count
8
+
9
+ def initialize(args)
10
+ @files_to_reopen = []
11
+ @options = {
12
+ :quiet => true,
13
+ :pid_dir => "#{Rails.root}/tmp/pids"
14
+ }
15
+
16
+ @worker_count = 1
17
+ @monitor = false
18
+
19
+ opts = OptionParser.new do |opts|
20
+ opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run"
21
+
22
+ opts.on('-h', '--help', 'Show this message') do
23
+ puts opts
24
+ exit 1
25
+ end
26
+ opts.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |e|
27
+ STDERR.puts "The -e/--environment option has been deprecated and has no effect. Use RAILS_ENV and see http://github.com/collectiveidea/delayed_job/issues/#issue/7"
28
+ end
29
+ opts.on('--min-priority N', 'Minimum priority of jobs to run.') do |n|
30
+ @options[:min_priority] = n
31
+ end
32
+ opts.on('--max-priority N', 'Maximum priority of jobs to run.') do |n|
33
+ @options[:max_priority] = n
34
+ end
35
+ opts.on('-n', '--number_of_workers=workers', "Number of unique workers to spawn") do |worker_count|
36
+ @worker_count = worker_count.to_i rescue 1
37
+ end
38
+ opts.on('--pid-dir=DIR', 'Specifies an alternate directory in which to store the process ids.') do |dir|
39
+ @options[:pid_dir] = dir
40
+ end
41
+ opts.on('-i', '--identifier=n', 'A numeric identifier for the worker.') do |n|
42
+ @options[:identifier] = n
43
+ end
44
+ opts.on('-m', '--monitor', 'Start monitor process.') do
45
+ @monitor = true
46
+ end
47
+
48
+
49
+ end
50
+ @args = opts.parse!(args)
51
+ end
52
+
53
+ def daemonize
54
+ Delayed::Worker.backend.before_fork
55
+
56
+ ObjectSpace.each_object(File) do |file|
57
+ @files_to_reopen << file unless file.closed?
58
+ end
59
+
60
+ dir = @options[:pid_dir]
61
+ Dir.mkdir(dir) unless File.exists?(dir)
62
+
63
+ if @worker_count > 1 && @options[:identifier]
64
+ raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier'
65
+ elsif @worker_count == 1 && @options[:identifier]
66
+ process_name = "delayed_job.#{@options[:identifier]}"
67
+ run_process(process_name, dir)
68
+ else
69
+ worker_count.times do |worker_index|
70
+ process_name = worker_count == 1 ? "delayed_job" : "delayed_job.#{worker_index}"
71
+ run_process(process_name, dir)
72
+ end
73
+ end
74
+ end
75
+
76
+ def run_process(process_name, dir)
77
+ Daemons.run_proc(process_name, :dir => dir, :dir_mode => :normal, :monitor => @monitor, :ARGV => @args) do |*args|
78
+ run process_name
79
+ end
80
+ end
81
+
82
+ def run(worker_name = nil)
83
+ Dir.chdir(Rails.root)
84
+
85
+ # Re-open file handles
86
+ @files_to_reopen.each do |file|
87
+ begin
88
+ file.reopen file.path, "a+"
89
+ file.sync = true
90
+ rescue ::Exception
91
+ end
92
+ end
93
+
94
+ Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log'))
95
+ Delayed::Worker.backend.after_fork
96
+
97
+ worker = Delayed::Worker.new(@options)
98
+ worker.name_prefix = "#{worker_name} "
99
+ worker.start
100
+ rescue => e
101
+ Rails.logger.fatal e
102
+ STDERR.puts e.message
103
+ exit 1
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,45 @@
1
+ require 'active_support/basic_object'
2
+
3
+ module Delayed
4
+ class DelayProxy < ActiveSupport::BasicObject
5
+ def initialize(target, options)
6
+ @target = target
7
+ @options = options
8
+ end
9
+
10
+ def method_missing(method, *args)
11
+ Job.create({
12
+ :payload_object => PerformableMethod.new(@target, method.to_sym, args),
13
+ :priority => ::Delayed::Worker.default_priority
14
+ }.merge(@options))
15
+ end
16
+ end
17
+
18
+ module MessageSending
19
+ def delay(options = {})
20
+ DelayProxy.new(self, options)
21
+ end
22
+ alias __delay__ delay
23
+
24
+ def send_later(method, *args)
25
+ warn "[DEPRECATION] `object.send_later(:method)` is deprecated. Use `object.delay.method"
26
+ __delay__.__send__(method, *args)
27
+ end
28
+
29
+ def send_at(time, method, *args)
30
+ warn "[DEPRECATION] `object.send_at(time, :method)` is deprecated. Use `object.delay(:run_at => time).method"
31
+ __delay__(:run_at => time).__send__(method, *args)
32
+ end
33
+
34
+ module ClassMethods
35
+ def handle_asynchronously(method, options = {})
36
+ aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
37
+ with_method, without_method = "#{aliased_method}_with_delay#{punctuation}", "#{aliased_method}_without_delay#{punctuation}"
38
+ define_method(with_method) do |*args|
39
+ delay(options).__send__(without_method, *args)
40
+ end
41
+ alias_method_chain method, :delay
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ module Delayed
2
+ class PerformableMethod < Struct.new(:object, :method, :args)
3
+ def initialize(object, method, args)
4
+ raise NoMethodError, "undefined method `#{method}' for #{object.inspect}" unless object.respond_to?(method, true)
5
+
6
+ self.object = object
7
+ self.args = args
8
+ self.method = method.to_sym
9
+ end
10
+
11
+ def display_name
12
+ "#{object.class}##{method}"
13
+ end
14
+
15
+ def perform
16
+ object.send(method, *args) if object
17
+ end
18
+
19
+ def method_missing(symbol, *args)
20
+ object.respond_to?(symbol) ? object.send(symbol, *args) : super
21
+ end
22
+
23
+ def respond_to?(symbol, include_private=false)
24
+ object.respond_to?(symbol, include_private) || super
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ require 'delayed_job'
2
+ require 'rails'
3
+
4
+ module Delayed
5
+ class Railtie < Rails::Railtie
6
+ initializer :after_initialize do
7
+ Delayed::Worker.guess_backend
8
+ end
9
+
10
+ rake_tasks do
11
+ load 'delayed/tasks.rb'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,31 @@
1
+ # Capistrano Recipes for managing delayed_job
2
+ #
3
+ # Add these callbacks to have the delayed_job process restart when the server
4
+ # is restarted:
5
+ #
6
+ # after "deploy:stop", "delayed_job:stop"
7
+ # after "deploy:start", "delayed_job:start"
8
+ # after "deploy:restart", "delayed_job:restart"
9
+
10
+ Capistrano::Configuration.instance.load do
11
+ namespace :delayed_job do
12
+ def rails_env
13
+ fetch(:rails_env, false) ? "RAILS_ENV=#{fetch(:rails_env)}" : ''
14
+ end
15
+
16
+ desc "Stop the delayed_job process"
17
+ task :stop, :roles => :app do
18
+ run "cd #{current_path};#{rails_env} script/delayed_job stop"
19
+ end
20
+
21
+ desc "Start the delayed_job process"
22
+ task :start, :roles => :app do
23
+ run "cd #{current_path};#{rails_env} script/delayed_job start"
24
+ end
25
+
26
+ desc "Restart the delayed_job process"
27
+ task :restart, :roles => :app do
28
+ run "cd #{current_path};#{rails_env} script/delayed_job restart"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ # Re-definitions are appended to existing tasks
2
+ task :environment
3
+ task :merb_env
4
+
5
+ namespace :jobs do
6
+ desc "Clear the delayed_job queue."
7
+ task :clear => [:merb_env, :environment] do
8
+ Delayed::Job.delete_all
9
+ end
10
+
11
+ desc "Start a delayed_job worker with an optional log name"
12
+ task :work, [:logname] => [:merb_env, :environment] do |_, args|
13
+
14
+ Delayed::Worker.new(:min_priority => ENV['MIN_PRIORITY'],
15
+ :max_priority => ENV['MAX_PRIORITY'],
16
+ :queue => ENV['QUEUE'],
17
+ :logname => args.logname
18
+ ).start
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # Re-definitions are appended to existing tasks
2
+ task :environment
3
+ task :merb_env
4
+
5
+ namespace :jobs do
6
+ desc "Clear the delayed_job queue."
7
+ task :clear => [:merb_env, :environment] do
8
+ Delayed::Job.delete_all
9
+ end
10
+
11
+ <<<<<<< HEAD
12
+ desc "Start a delayed_job worker with an optional log name"
13
+ task :work, [:logname] => [:merb_env, :environment] do |_, args|
14
+
15
+ Delayed::Worker.new(:min_priority => ENV['MIN_PRIORITY'],
16
+ :max_priority => ENV['MAX_PRIORITY'],
17
+ :queue => ENV['QUEUE'],
18
+ :logname => args.logname
19
+ ).start
20
+ =======
21
+ desc "Start a delayed_job worker."
22
+ task :work => [:merb_env, :environment] do
23
+ Delayed::Worker.new(:min_priority => ENV['MIN_PRIORITY'], :max_priority => ENV['MAX_PRIORITY'], :queue => ENV['QUEUE']).start
24
+ >>>>>>> 75b6e0c... added queues from the original topprospect patch, added tests for queues
25
+ end
26
+ end