drewda_delayed_job 3.0.3

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.
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