drewda_delayed_job 3.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.textile +314 -0
  3. data/contrib/delayed_job.monitrc +14 -0
  4. data/contrib/delayed_job_multiple.monitrc +23 -0
  5. data/lib/delayed/backend/base.rb +184 -0
  6. data/lib/delayed/backend/shared_spec.rb +595 -0
  7. data/lib/delayed/command.rb +108 -0
  8. data/lib/delayed/deserialization_error.rb +4 -0
  9. data/lib/delayed/lifecycle.rb +84 -0
  10. data/lib/delayed/message_sending.rb +54 -0
  11. data/lib/delayed/performable_mailer.rb +21 -0
  12. data/lib/delayed/performable_method.rb +37 -0
  13. data/lib/delayed/plugin.rb +15 -0
  14. data/lib/delayed/plugins/clear_locks.rb +15 -0
  15. data/lib/delayed/psych_ext.rb +132 -0
  16. data/lib/delayed/railtie.rb +16 -0
  17. data/lib/delayed/recipes.rb +50 -0
  18. data/lib/delayed/serialization/active_record.rb +19 -0
  19. data/lib/delayed/syck_ext.rb +34 -0
  20. data/lib/delayed/tasks.rb +11 -0
  21. data/lib/delayed/worker.rb +242 -0
  22. data/lib/delayed/yaml_ext.rb +10 -0
  23. data/lib/delayed_job.rb +21 -0
  24. data/lib/generators/delayed_job/delayed_job_generator.rb +11 -0
  25. data/lib/generators/delayed_job/templates/script +5 -0
  26. data/recipes/delayed_job.rb +1 -0
  27. data/spec/autoloaded/clazz.rb +7 -0
  28. data/spec/autoloaded/instance_clazz.rb +6 -0
  29. data/spec/autoloaded/instance_struct.rb +6 -0
  30. data/spec/autoloaded/struct.rb +7 -0
  31. data/spec/delayed/backend/test.rb +113 -0
  32. data/spec/delayed/serialization/test.rb +0 -0
  33. data/spec/fixtures/bad_alias.yml +1 -0
  34. data/spec/lifecycle_spec.rb +67 -0
  35. data/spec/message_sending_spec.rb +113 -0
  36. data/spec/performable_mailer_spec.rb +44 -0
  37. data/spec/performable_method_spec.rb +89 -0
  38. data/spec/sample_jobs.rb +75 -0
  39. data/spec/spec_helper.rb +53 -0
  40. data/spec/test_backend_spec.rb +13 -0
  41. data/spec/worker_spec.rb +19 -0
  42. data/spec/yaml_ext_spec.rb +41 -0
  43. metadata +214 -0
@@ -0,0 +1,108 @@
1
+ begin
2
+ require 'daemons'
3
+ rescue LoadError
4
+ raise "You need to add gem 'daemons' to your Gemfile if you wish to use it."
5
+ end
6
+ require 'optparse'
7
+
8
+ module Delayed
9
+ class Command
10
+ attr_accessor :worker_count
11
+
12
+ def initialize(args)
13
+ @options = {
14
+ :quiet => true,
15
+ :pid_dir => "#{Rails.root}/tmp/pids"
16
+ }
17
+
18
+ @worker_count = 1
19
+ @monitor = false
20
+
21
+ opts = OptionParser.new do |opts|
22
+ opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run"
23
+
24
+ opts.on('-h', '--help', 'Show this message') do
25
+ puts opts
26
+ exit 1
27
+ end
28
+ opts.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |e|
29
+ 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"
30
+ end
31
+ opts.on('--min-priority N', 'Minimum priority of jobs to run.') do |n|
32
+ @options[:min_priority] = n
33
+ end
34
+ opts.on('--max-priority N', 'Maximum priority of jobs to run.') do |n|
35
+ @options[:max_priority] = n
36
+ end
37
+ opts.on('-n', '--number_of_workers=workers', "Number of unique workers to spawn") do |worker_count|
38
+ @worker_count = worker_count.to_i rescue 1
39
+ end
40
+ opts.on('--pid-dir=DIR', 'Specifies an alternate directory in which to store the process ids.') do |dir|
41
+ @options[:pid_dir] = dir
42
+ end
43
+ opts.on('-i', '--identifier=n', 'A numeric identifier for the worker.') do |n|
44
+ @options[:identifier] = n
45
+ end
46
+ opts.on('-m', '--monitor', 'Start monitor process.') do
47
+ @monitor = true
48
+ end
49
+ opts.on('--sleep-delay N', "Amount of time to sleep when no jobs are found") do |n|
50
+ @options[:sleep_delay] = n.to_i
51
+ end
52
+ opts.on('--read-ahead N', "Number of jobs from the queue to consider") do |n|
53
+ @options[:read_ahead] = n
54
+ end
55
+ opts.on('-p', '--prefix NAME', "String to be prefixed to worker process names") do |prefix|
56
+ @options[:prefix] = prefix
57
+ end
58
+ opts.on('--queues=queues', "Specify which queue DJ must look up for jobs") do |queues|
59
+ @options[:queues] = queues.split(',')
60
+ end
61
+ opts.on('--queue=queue', "Specify which queue DJ must look up for jobs") do |queue|
62
+ @options[:queues] = queue.split(',')
63
+ end
64
+ end
65
+ @args = opts.parse!(args)
66
+ end
67
+
68
+ def daemonize
69
+ dir = @options[:pid_dir]
70
+ Dir.mkdir(dir) unless File.exists?(dir)
71
+
72
+ if @worker_count > 1 && @options[:identifier]
73
+ raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier'
74
+ elsif @worker_count == 1 && @options[:identifier]
75
+ process_name = "delayed_job.#{@options[:identifier]}"
76
+ run_process(process_name, dir)
77
+ else
78
+ worker_count.times do |worker_index|
79
+ process_name = worker_count == 1 ? "delayed_job" : "delayed_job.#{worker_index}"
80
+ run_process(process_name, dir)
81
+ end
82
+ end
83
+ end
84
+
85
+ def run_process(process_name, dir)
86
+ Delayed::Worker.before_fork
87
+ Daemons.run_proc(process_name, :dir => dir, :dir_mode => :normal, :monitor => @monitor, :ARGV => @args) do |*args|
88
+ $0 = File.join(@options[:prefix], process_name) if @options[:prefix]
89
+ run process_name
90
+ end
91
+ end
92
+
93
+ def run(worker_name = nil)
94
+ Dir.chdir(Rails.root)
95
+
96
+ Delayed::Worker.after_fork
97
+ Delayed::Worker.logger ||= Logger.new(File.join(Rails.root, 'log', 'delayed_job.log'))
98
+
99
+ worker = Delayed::Worker.new(@options)
100
+ worker.name_prefix = "#{worker_name} "
101
+ worker.start
102
+ rescue => e
103
+ Rails.logger.fatal e
104
+ STDERR.puts e.message
105
+ exit 1
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,4 @@
1
+ module Delayed
2
+ class DeserializationError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,84 @@
1
+ module Delayed
2
+ class InvalidCallback < Exception; end
3
+
4
+ class Lifecycle
5
+ EVENTS = {
6
+ :enqueue => [:job],
7
+ :execute => [:worker],
8
+ :loop => [:worker],
9
+ :perform => [:worker, :job],
10
+ :error => [:worker, :job],
11
+ :failure => [:worker, :job],
12
+ :invoke_job => [:job]
13
+ }
14
+
15
+ def initialize
16
+ @callbacks = EVENTS.keys.inject({}) { |hash, e| hash[e] = Callback.new; hash }
17
+ end
18
+
19
+ def before(event, &block)
20
+ add(:before, event, &block)
21
+ end
22
+
23
+ def after(event, &block)
24
+ add(:after, event, &block)
25
+ end
26
+
27
+ def around(event, &block)
28
+ add(:around, event, &block)
29
+ end
30
+
31
+ def run_callbacks(event, *args, &block)
32
+ missing_callback(event) unless @callbacks.has_key?(event)
33
+
34
+ unless EVENTS[event].size == args.size
35
+ raise ArgumentError, "Callback #{event} expects #{EVENTS[event].size} parameter(s): #{EVENTS[event].join(', ')}"
36
+ end
37
+
38
+ @callbacks[event].execute(*args, &block)
39
+ end
40
+
41
+ private
42
+
43
+ def add(type, event, &block)
44
+ missing_callback(event) unless @callbacks.has_key?(event)
45
+
46
+ @callbacks[event].add(type, &block)
47
+ end
48
+
49
+ def missing_callback(event)
50
+ raise InvalidCallback, "Unknown callback event: #{event}"
51
+ end
52
+ end
53
+
54
+ class Callback
55
+ def initialize
56
+ @before = []
57
+ @after = []
58
+
59
+ # Identity proc. Avoids special cases when there is no existing around chain.
60
+ @around = lambda { |*args, &block| block.call(*args) }
61
+ end
62
+
63
+ def execute(*args, &block)
64
+ @before.each { |c| c.call(*args) }
65
+ result = @around.call(*args, &block)
66
+ @after.each { |c| c.call(*args) }
67
+ result
68
+ end
69
+
70
+ def add(type, &callback)
71
+ case type
72
+ when :before
73
+ @before << callback
74
+ when :after
75
+ @after << callback
76
+ when :around
77
+ chain = @around # use a local variable so that the current chain is closed over in the following lambda
78
+ @around = lambda { |*a, &block| chain.call(*a) { |*b| callback.call(*b, &block) } }
79
+ else
80
+ raise InvalidCallback, "Invalid callback type: #{type}"
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,54 @@
1
+ require 'active_support/basic_object'
2
+ require 'active_support/core_ext/module/aliasing'
3
+
4
+ module Delayed
5
+ class DelayProxy < ActiveSupport::BasicObject
6
+ def initialize(payload_class, target, options)
7
+ @payload_class = payload_class
8
+ @target = target
9
+ @options = options
10
+ end
11
+
12
+ def method_missing(method, *args)
13
+ Job.enqueue({:payload_object => @payload_class.new(@target, method.to_sym, args)}.merge(@options))
14
+ end
15
+ end
16
+
17
+ module MessageSending
18
+ def delay(options = {})
19
+ DelayProxy.new(PerformableMethod, self, options)
20
+ end
21
+ alias __delay__ delay
22
+
23
+ def send_later(method, *args)
24
+ warn "[DEPRECATION] `object.send_later(:method)` is deprecated. Use `object.delay.method"
25
+ __delay__.__send__(method, *args)
26
+ end
27
+
28
+ def send_at(time, method, *args)
29
+ warn "[DEPRECATION] `object.send_at(time, :method)` is deprecated. Use `object.delay(:run_at => time).method"
30
+ __delay__(:run_at => time).__send__(method, *args)
31
+ end
32
+
33
+ module ClassMethods
34
+ def handle_asynchronously(method, opts = {})
35
+ aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
36
+ with_method, without_method = "#{aliased_method}_with_delay#{punctuation}", "#{aliased_method}_without_delay#{punctuation}"
37
+ define_method(with_method) do |*args|
38
+ curr_opts = opts.clone
39
+ curr_opts.each_key do |key|
40
+ if (val = curr_opts[key]).is_a?(Proc)
41
+ curr_opts[key] = if val.arity == 1
42
+ val.call(self)
43
+ else
44
+ val.call
45
+ end
46
+ end
47
+ end
48
+ delay(curr_opts).__send__(without_method, *args)
49
+ end
50
+ alias_method_chain method, :delay
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ require 'mail'
2
+
3
+ module Delayed
4
+ class PerformableMailer < PerformableMethod
5
+ def perform
6
+ object.send(method_name, *args).deliver
7
+ end
8
+ end
9
+
10
+ module DelayMail
11
+ def delay(options = {})
12
+ DelayProxy.new(PerformableMailer, self, options)
13
+ end
14
+ end
15
+ end
16
+
17
+ Mail::Message.class_eval do
18
+ def delay(*args)
19
+ raise RuntimeError, "Use MyMailer.delay.mailer_action(args) to delay sending of emails."
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Delayed
4
+ class PerformableMethod
5
+ attr_accessor :object, :method_name, :args
6
+
7
+ delegate :method, :to => :object
8
+
9
+ def initialize(object, method_name, args)
10
+ raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true)
11
+
12
+ if defined?(ActiveRecord) && object.kind_of?(ActiveRecord::Base)
13
+ raise(ArgumentError, 'Jobs cannot be created for records before they\'ve been persisted') if object.attributes[object.class.primary_key].nil?
14
+ end
15
+
16
+ self.object = object
17
+ self.args = args
18
+ self.method_name = method_name.to_sym
19
+ end
20
+
21
+ def display_name
22
+ "#{object.class}##{method_name}"
23
+ end
24
+
25
+ def perform
26
+ object.send(method_name, *args) if object
27
+ end
28
+
29
+ def method_missing(symbol, *args)
30
+ object.send(symbol, *args)
31
+ end
32
+
33
+ def respond_to?(symbol, include_private=false)
34
+ super || object.respond_to?(symbol, include_private)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+
3
+ module Delayed
4
+ class Plugin
5
+ class_attribute :callback_block
6
+
7
+ def self.callbacks(&block)
8
+ self.callback_block = block
9
+ end
10
+
11
+ def initialize
12
+ self.class.callback_block.call(Delayed::Worker.lifecycle) if self.class.callback_block
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Delayed
2
+ module Plugins
3
+ class ClearLocks < Plugin
4
+ callbacks do |lifecycle|
5
+ lifecycle.around(:execute) do |worker, &block|
6
+ begin
7
+ block.call(worker)
8
+ ensure
9
+ Delayed::Job.clear_locks!(worker.name)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,132 @@
1
+ if defined?(ActiveRecord)
2
+ class ActiveRecord::Base
3
+ # serialize to YAML
4
+ def encode_with(coder)
5
+ coder["attributes"] = @attributes
6
+ coder.tag = ['!ruby/ActiveRecord', self.class.name].join(':')
7
+ end
8
+ end
9
+ end
10
+
11
+ class Delayed::PerformableMethod
12
+ # serialize to YAML
13
+ def encode_with(coder)
14
+ coder.map = {
15
+ "object" => object,
16
+ "method_name" => method_name,
17
+ "args" => args
18
+ }
19
+ end
20
+ end
21
+
22
+ module Psych
23
+ module Visitors
24
+ class YAMLTree
25
+ def visit_Class(klass)
26
+ @emitter.scalar klass.name, nil, '!ruby/class', false, false, Nodes::Scalar::SINGLE_QUOTED
27
+ end
28
+ end
29
+
30
+ class ToRuby
31
+ def visit_Psych_Nodes_Scalar(o)
32
+ @st[o.anchor] = o.value if o.anchor
33
+
34
+ if klass = Psych.load_tags[o.tag]
35
+ instance = klass.allocate
36
+
37
+ if instance.respond_to?(:init_with)
38
+ coder = Psych::Coder.new(o.tag)
39
+ coder.scalar = o.value
40
+ instance.init_with coder
41
+ end
42
+
43
+ return instance
44
+ end
45
+
46
+ return o.value if o.quoted
47
+ return @ss.tokenize(o.value) unless o.tag
48
+
49
+ case o.tag
50
+ when '!binary', 'tag:yaml.org,2002:binary'
51
+ o.value.unpack('m').first
52
+ when '!str', 'tag:yaml.org,2002:str'
53
+ o.value
54
+ when "!ruby/object:DateTime"
55
+ require 'date'
56
+ @ss.parse_time(o.value).to_datetime
57
+ when "!ruby/object:Complex"
58
+ Complex(o.value)
59
+ when "!ruby/object:Rational"
60
+ Rational(o.value)
61
+ when "!ruby/class", "!ruby/module"
62
+ resolve_class o.value
63
+ when "tag:yaml.org,2002:float", "!float"
64
+ Float(@ss.tokenize(o.value))
65
+ when "!ruby/regexp"
66
+ o.value =~ /^\/(.*)\/([mixn]*)$/
67
+ source = $1
68
+ options = 0
69
+ lang = nil
70
+ ($2 || '').split('').each do |option|
71
+ case option
72
+ when 'x' then options |= Regexp::EXTENDED
73
+ when 'i' then options |= Regexp::IGNORECASE
74
+ when 'm' then options |= Regexp::MULTILINE
75
+ when 'n' then options |= Regexp::NOENCODING
76
+ else lang = option
77
+ end
78
+ end
79
+ Regexp.new(*[source, options, lang].compact)
80
+ when "!ruby/range"
81
+ args = o.value.split(/([.]{2,3})/, 2).map { |s|
82
+ accept Nodes::Scalar.new(s)
83
+ }
84
+ args.push(args.delete_at(1) == '...')
85
+ Range.new(*args)
86
+ when /^!ruby\/sym(bol)?:?(.*)?$/
87
+ o.value.to_sym
88
+ else
89
+ @ss.tokenize o.value
90
+ end
91
+ end
92
+
93
+ def visit_Psych_Nodes_Mapping_with_class(object)
94
+ return revive(Psych.load_tags[object.tag], object) if Psych.load_tags[object.tag]
95
+
96
+ case object.tag
97
+ when /^!ruby\/ActiveRecord:(.+)$/
98
+ klass = resolve_class($1)
99
+ payload = Hash[*object.children.map { |c| accept c }]
100
+ id = payload["attributes"][klass.primary_key]
101
+ begin
102
+ if ActiveRecord::VERSION::MAJOR == 3
103
+ klass.unscoped.find(id)
104
+ else # Rails 2
105
+ klass.with_exclusive_scope { klass.find(id) }
106
+ end
107
+ rescue ActiveRecord::RecordNotFound
108
+ raise Delayed::DeserializationError
109
+ end
110
+ when /^!ruby\/Mongoid:(.+)$/
111
+ klass = resolve_class($1)
112
+ payload = Hash[*object.children.map { |c| accept c }]
113
+ begin
114
+ klass.find(payload["attributes"]["_id"])
115
+ rescue Mongoid::Errors::DocumentNotFound
116
+ raise Delayed::DeserializationError
117
+ end
118
+ else
119
+ visit_Psych_Nodes_Mapping_without_class(object)
120
+ end
121
+ end
122
+ alias_method_chain :visit_Psych_Nodes_Mapping, :class
123
+
124
+ def resolve_class_with_constantize(klass_name)
125
+ klass_name.constantize
126
+ rescue
127
+ resolve_class_without_constantize(klass_name)
128
+ end
129
+ alias_method_chain :resolve_class, :constantize
130
+ end
131
+ end
132
+ end