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.
- data/MIT-LICENSE +20 -0
- data/README.textile +314 -0
- data/contrib/delayed_job.monitrc +14 -0
- data/contrib/delayed_job_multiple.monitrc +23 -0
- data/lib/delayed/backend/base.rb +184 -0
- data/lib/delayed/backend/shared_spec.rb +595 -0
- data/lib/delayed/command.rb +108 -0
- data/lib/delayed/deserialization_error.rb +4 -0
- data/lib/delayed/lifecycle.rb +84 -0
- data/lib/delayed/message_sending.rb +54 -0
- data/lib/delayed/performable_mailer.rb +21 -0
- data/lib/delayed/performable_method.rb +37 -0
- data/lib/delayed/plugin.rb +15 -0
- data/lib/delayed/plugins/clear_locks.rb +15 -0
- data/lib/delayed/psych_ext.rb +132 -0
- data/lib/delayed/railtie.rb +16 -0
- data/lib/delayed/recipes.rb +50 -0
- data/lib/delayed/serialization/active_record.rb +19 -0
- data/lib/delayed/syck_ext.rb +34 -0
- data/lib/delayed/tasks.rb +11 -0
- data/lib/delayed/worker.rb +242 -0
- data/lib/delayed/yaml_ext.rb +10 -0
- data/lib/delayed_job.rb +21 -0
- data/lib/generators/delayed_job/delayed_job_generator.rb +11 -0
- data/lib/generators/delayed_job/templates/script +5 -0
- data/recipes/delayed_job.rb +1 -0
- data/spec/autoloaded/clazz.rb +7 -0
- data/spec/autoloaded/instance_clazz.rb +6 -0
- data/spec/autoloaded/instance_struct.rb +6 -0
- data/spec/autoloaded/struct.rb +7 -0
- data/spec/delayed/backend/test.rb +113 -0
- data/spec/delayed/serialization/test.rb +0 -0
- data/spec/fixtures/bad_alias.yml +1 -0
- data/spec/lifecycle_spec.rb +67 -0
- data/spec/message_sending_spec.rb +113 -0
- data/spec/performable_mailer_spec.rb +44 -0
- data/spec/performable_method_spec.rb +89 -0
- data/spec/sample_jobs.rb +75 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/test_backend_spec.rb +13 -0
- data/spec/worker_spec.rb +19 -0
- data/spec/yaml_ext_spec.rb +41 -0
- 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,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
|