delayed_job_tgmerritt 4.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +208 -0
- data/CONTRIBUTING.md +27 -0
- data/LICENSE.md +20 -0
- data/README.md +424 -0
- data/Rakefile +15 -0
- data/contrib/delayed_job.monitrc +14 -0
- data/contrib/delayed_job_multiple.monitrc +34 -0
- data/contrib/delayed_job_rails_4.monitrc +14 -0
- data/contrib/delayed_job_rails_4_multiple.monitrc +34 -0
- data/delayed_job.gemspec +15 -0
- data/lib/delayed/backend/base.rb +159 -0
- data/lib/delayed/backend/shared_spec.rb +657 -0
- data/lib/delayed/command.rb +170 -0
- data/lib/delayed/compatibility.rb +27 -0
- data/lib/delayed/deserialization_error.rb +4 -0
- data/lib/delayed/exceptions.rb +12 -0
- data/lib/delayed/lifecycle.rb +85 -0
- data/lib/delayed/message_sending.rb +52 -0
- data/lib/delayed/performable_mailer.rb +22 -0
- data/lib/delayed/performable_method.rb +41 -0
- data/lib/delayed/plugin.rb +15 -0
- data/lib/delayed/plugins/clear_locks.rb +15 -0
- data/lib/delayed/psych_ext.rb +89 -0
- data/lib/delayed/railtie.rb +22 -0
- data/lib/delayed/recipes.rb +54 -0
- data/lib/delayed/serialization/active_record.rb +17 -0
- data/lib/delayed/syck_ext.rb +42 -0
- data/lib/delayed/tasks.rb +39 -0
- data/lib/delayed/worker.rb +312 -0
- data/lib/delayed/yaml_ext.rb +10 -0
- data/lib/delayed_job.rb +22 -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 +7 -0
- data/spec/autoloaded/struct.rb +8 -0
- data/spec/daemons.rb +2 -0
- data/spec/delayed/backend/test.rb +117 -0
- data/spec/delayed/command_spec.rb +180 -0
- data/spec/delayed/serialization/test.rb +0 -0
- data/spec/helper.rb +85 -0
- data/spec/lifecycle_spec.rb +75 -0
- data/spec/message_sending_spec.rb +122 -0
- data/spec/performable_mailer_spec.rb +43 -0
- data/spec/performable_method_spec.rb +111 -0
- data/spec/psych_ext_spec.rb +12 -0
- data/spec/sample_jobs.rb +111 -0
- data/spec/test_backend_spec.rb +13 -0
- data/spec/worker_spec.rb +175 -0
- data/spec/yaml_ext_spec.rb +48 -0
- metadata +144 -0
@@ -0,0 +1,170 @@
|
|
1
|
+
unless ENV['RAILS_ENV'] == 'test'
|
2
|
+
begin
|
3
|
+
require 'daemons'
|
4
|
+
rescue LoadError
|
5
|
+
raise "You need to add gem 'daemons' to your Gemfile if you wish to use it."
|
6
|
+
end
|
7
|
+
end
|
8
|
+
require 'optparse'
|
9
|
+
require 'pathname'
|
10
|
+
|
11
|
+
module Delayed
|
12
|
+
class Command # rubocop:disable ClassLength
|
13
|
+
attr_accessor :worker_count, :worker_pools
|
14
|
+
|
15
|
+
DIR_PWD = Pathname.new Dir.pwd
|
16
|
+
|
17
|
+
def initialize(args) # rubocop:disable MethodLength
|
18
|
+
@options = {
|
19
|
+
:quiet => true,
|
20
|
+
:pid_dir => "#{root}/tmp/pids",
|
21
|
+
:log_dir => "#{root}/log"
|
22
|
+
}
|
23
|
+
|
24
|
+
@worker_count = 1
|
25
|
+
@monitor = false
|
26
|
+
|
27
|
+
opts = OptionParser.new do |opt|
|
28
|
+
opt.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options] start|stop|restart|run"
|
29
|
+
|
30
|
+
opt.on('-h', '--help', 'Show this message') do
|
31
|
+
puts opt
|
32
|
+
exit 1
|
33
|
+
end
|
34
|
+
opt.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |_e|
|
35
|
+
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'
|
36
|
+
end
|
37
|
+
opt.on('--min-priority N', 'Minimum priority of jobs to run.') do |n|
|
38
|
+
@options[:min_priority] = n
|
39
|
+
end
|
40
|
+
opt.on('--max-priority N', 'Maximum priority of jobs to run.') do |n|
|
41
|
+
@options[:max_priority] = n
|
42
|
+
end
|
43
|
+
opt.on('-n', '--number_of_workers=workers', 'Number of unique workers to spawn') do |worker_count|
|
44
|
+
@worker_count = worker_count.to_i rescue 1 # rubocop:disable RescueModifier
|
45
|
+
end
|
46
|
+
opt.on('--pid-dir=DIR', 'Specifies an alternate directory in which to store the process ids.') do |dir|
|
47
|
+
@options[:pid_dir] = dir
|
48
|
+
end
|
49
|
+
opt.on('--log-dir=DIR', 'Specifies an alternate directory in which to store the delayed_job log.') do |dir|
|
50
|
+
@options[:log_dir] = dir
|
51
|
+
end
|
52
|
+
opt.on('-i', '--identifier=n', 'A numeric identifier for the worker.') do |n|
|
53
|
+
@options[:identifier] = n
|
54
|
+
end
|
55
|
+
opt.on('-m', '--monitor', 'Start monitor process.') do
|
56
|
+
@monitor = true
|
57
|
+
end
|
58
|
+
opt.on('--sleep-delay N', 'Amount of time to sleep when no jobs are found') do |n|
|
59
|
+
@options[:sleep_delay] = n.to_i
|
60
|
+
end
|
61
|
+
opt.on('--read-ahead N', 'Number of jobs from the queue to consider') do |n|
|
62
|
+
@options[:read_ahead] = n
|
63
|
+
end
|
64
|
+
opt.on('-p', '--prefix NAME', 'String to be prefixed to worker process names') do |prefix|
|
65
|
+
@options[:prefix] = prefix
|
66
|
+
end
|
67
|
+
opt.on('--queues=queues', 'Specify which queue DJ must look up for jobs') do |queues|
|
68
|
+
@options[:queues] = queues.split(',')
|
69
|
+
end
|
70
|
+
opt.on('--queue=queue', 'Specify which queue DJ must look up for jobs') do |queue|
|
71
|
+
@options[:queues] = queue.split(',')
|
72
|
+
end
|
73
|
+
opt.on('--pool=queue1[,queue2][:worker_count]', 'Specify queues and number of workers for a worker pool') do |pool|
|
74
|
+
parse_worker_pool(pool)
|
75
|
+
end
|
76
|
+
opt.on('--exit-on-complete', 'Exit when no more jobs are available to run. This will exit if all jobs are scheduled to run in the future.') do
|
77
|
+
@options[:exit_on_complete] = true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
@args = opts.parse!(args)
|
81
|
+
end
|
82
|
+
|
83
|
+
def daemonize # rubocop:disable PerceivedComplexity
|
84
|
+
dir = @options[:pid_dir]
|
85
|
+
Dir.mkdir(dir) unless File.exist?(dir)
|
86
|
+
|
87
|
+
if worker_pools
|
88
|
+
setup_pools
|
89
|
+
elsif @options[:identifier]
|
90
|
+
if worker_count > 1
|
91
|
+
raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier'
|
92
|
+
else
|
93
|
+
run_process("delayed_job.#{@options[:identifier]}", @options)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
worker_count.times do |worker_index|
|
97
|
+
process_name = worker_count == 1 ? 'delayed_job' : "delayed_job.#{worker_index}"
|
98
|
+
run_process(process_name, @options)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def setup_pools
|
104
|
+
worker_index = 0
|
105
|
+
@worker_pools.each do |queues, worker_count|
|
106
|
+
options = @options.merge(:queues => queues)
|
107
|
+
worker_count.times do
|
108
|
+
process_name = "delayed_job.#{worker_index}"
|
109
|
+
run_process(process_name, options)
|
110
|
+
worker_index += 1
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def run_process(process_name, options = {})
|
116
|
+
Delayed::Worker.before_fork
|
117
|
+
Daemons.run_proc(process_name, :dir => options[:pid_dir], :dir_mode => :normal, :monitor => @monitor, :ARGV => @args) do |*_args|
|
118
|
+
$0 = File.join(options[:prefix], process_name) if @options[:prefix]
|
119
|
+
run process_name, options
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def run(worker_name = nil, options = {})
|
124
|
+
Dir.chdir(root)
|
125
|
+
|
126
|
+
Delayed::Worker.after_fork
|
127
|
+
Delayed::Worker.logger ||= Logger.new(File.join(@options[:log_dir], 'delayed_job.log'))
|
128
|
+
|
129
|
+
worker = Delayed::Worker.new(options)
|
130
|
+
worker.name_prefix = "#{worker_name} "
|
131
|
+
worker.start
|
132
|
+
rescue => e
|
133
|
+
STDERR.puts e.message
|
134
|
+
STDERR.puts e.backtrace
|
135
|
+
::Rails.logger.fatal(e) if rails_logger_defined?
|
136
|
+
exit_with_error_status
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def parse_worker_pool(pool)
|
142
|
+
@worker_pools ||= []
|
143
|
+
|
144
|
+
queues, worker_count = pool.split(':')
|
145
|
+
if ['*', '', nil].include?(queues)
|
146
|
+
queues = []
|
147
|
+
else
|
148
|
+
queues = queues.split(',')
|
149
|
+
end
|
150
|
+
worker_count = (worker_count || 1).to_i rescue 1
|
151
|
+
@worker_pools << [queues, worker_count]
|
152
|
+
end
|
153
|
+
|
154
|
+
def root
|
155
|
+
@root ||= rails_root_defined? ? ::Rails.root : DIR_PWD
|
156
|
+
end
|
157
|
+
|
158
|
+
def rails_root_defined?
|
159
|
+
defined?(::Rails.root)
|
160
|
+
end
|
161
|
+
|
162
|
+
def rails_logger_defined?
|
163
|
+
defined?(::Rails.logger)
|
164
|
+
end
|
165
|
+
|
166
|
+
def exit_with_error_status
|
167
|
+
exit 1
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'active_support/version'
|
2
|
+
|
3
|
+
module Delayed
|
4
|
+
module Compatibility
|
5
|
+
if ActiveSupport::VERSION::MAJOR >= 4
|
6
|
+
require 'active_support/proxy_object'
|
7
|
+
|
8
|
+
def self.executable_prefix
|
9
|
+
'bin'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.proxy_object_class
|
13
|
+
ActiveSupport::ProxyObject
|
14
|
+
end
|
15
|
+
else
|
16
|
+
require 'active_support/basic_object'
|
17
|
+
|
18
|
+
def self.executable_prefix
|
19
|
+
'script'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.proxy_object_class
|
23
|
+
ActiveSupport::BasicObject
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Delayed
|
4
|
+
class WorkerTimeout < Timeout::Error
|
5
|
+
def message
|
6
|
+
seconds = Delayed::Worker.max_run_time.to_i
|
7
|
+
"#{super} (Delayed::Worker.max_run_time is only #{seconds} second#{seconds == 1 ? '' : 's'})"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class FatalBackendError < Exception; end
|
12
|
+
end
|
@@ -0,0 +1,85 @@
|
|
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.each_with_object({}) do |e, hash|
|
17
|
+
hash[e] = Callback.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def before(event, &block)
|
22
|
+
add(:before, event, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def after(event, &block)
|
26
|
+
add(:after, event, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def around(event, &block)
|
30
|
+
add(:around, event, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def run_callbacks(event, *args, &block)
|
34
|
+
missing_callback(event) unless @callbacks.key?(event)
|
35
|
+
|
36
|
+
unless EVENTS[event].size == args.size
|
37
|
+
raise ArgumentError, "Callback #{event} expects #{EVENTS[event].size} parameter(s): #{EVENTS[event].join(', ')}"
|
38
|
+
end
|
39
|
+
|
40
|
+
@callbacks[event].execute(*args, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def add(type, event, &block)
|
46
|
+
missing_callback(event) unless @callbacks.key?(event)
|
47
|
+
@callbacks[event].add(type, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def missing_callback(event)
|
51
|
+
raise InvalidCallback, "Unknown callback event: #{event}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Callback
|
56
|
+
def initialize
|
57
|
+
@before = []
|
58
|
+
@after = []
|
59
|
+
|
60
|
+
# Identity proc. Avoids special cases when there is no existing around chain.
|
61
|
+
@around = lambda { |*args, &block| block.call(*args) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def execute(*args, &block)
|
65
|
+
@before.each { |c| c.call(*args) }
|
66
|
+
result = @around.call(*args, &block)
|
67
|
+
@after.each { |c| c.call(*args) }
|
68
|
+
result
|
69
|
+
end
|
70
|
+
|
71
|
+
def add(type, &callback)
|
72
|
+
case type
|
73
|
+
when :before
|
74
|
+
@before << callback
|
75
|
+
when :after
|
76
|
+
@after << callback
|
77
|
+
when :around
|
78
|
+
chain = @around # use a local variable so that the current chain is closed over in the following lambda
|
79
|
+
@around = lambda { |*a, &block| chain.call(*a) { |*b| callback.call(*b, &block) } }
|
80
|
+
else
|
81
|
+
raise InvalidCallback, "Invalid callback type: #{type}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'active_support/core_ext/module/aliasing'
|
2
|
+
|
3
|
+
module Delayed
|
4
|
+
class DelayProxy < Delayed::Compatibility.proxy_object_class
|
5
|
+
def initialize(payload_class, target, options)
|
6
|
+
@payload_class = payload_class
|
7
|
+
@target = target
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(method, *args)
|
12
|
+
Job.enqueue({:payload_object => @payload_class.new(@target, method.to_sym, args)}.merge(@options))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module MessageSending
|
17
|
+
def delay(options = {})
|
18
|
+
DelayProxy.new(PerformableMethod, self, options)
|
19
|
+
end
|
20
|
+
alias_method :__delay__, :delay
|
21
|
+
|
22
|
+
def send_later(method, *args)
|
23
|
+
warn '[DEPRECATION] `object.send_later(:method)` is deprecated. Use `object.delay.method'
|
24
|
+
__delay__.__send__(method, *args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_at(time, method, *args)
|
28
|
+
warn '[DEPRECATION] `object.send_at(time, :method)` is deprecated. Use `object.delay(:run_at => time).method'
|
29
|
+
__delay__(:run_at => time).__send__(method, *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
def handle_asynchronously(method, opts = {})
|
34
|
+
aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1 # rubocop:disable PerlBackrefs
|
35
|
+
with_method, without_method = "#{aliased_method}_with_delay#{punctuation}", "#{aliased_method}_without_delay#{punctuation}"
|
36
|
+
define_method(with_method) do |*args|
|
37
|
+
curr_opts = opts.clone
|
38
|
+
curr_opts.each_key do |key|
|
39
|
+
next unless (val = curr_opts[key]).is_a?(Proc)
|
40
|
+
curr_opts[key] = if val.arity == 1
|
41
|
+
val.call(self)
|
42
|
+
else
|
43
|
+
val.call
|
44
|
+
end
|
45
|
+
end
|
46
|
+
delay(curr_opts).__send__(without_method, *args)
|
47
|
+
end
|
48
|
+
alias_method_chain method, :delay
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'mail'
|
2
|
+
|
3
|
+
module Delayed
|
4
|
+
class PerformableMailer < PerformableMethod
|
5
|
+
def perform
|
6
|
+
mailer = object.send(method_name, *args)
|
7
|
+
mailer.respond_to?(:deliver_now) ? mailer.deliver_now : mailer.deliver
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module DelayMail
|
12
|
+
def delay(options = {})
|
13
|
+
DelayProxy.new(PerformableMailer, self, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Mail::Message.class_eval do
|
19
|
+
def delay(*_args)
|
20
|
+
raise 'Use MyMailer.delay.mailer_action(args) to delay sending of emails.'
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,41 @@
|
|
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 object.respond_to?(:persisted?) && !object.persisted?
|
13
|
+
raise(ArgumentError, "job cannot be created for non-persisted record: #{object.inspect}")
|
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
|
+
if object.is_a?(Class)
|
23
|
+
"#{object}.#{method_name}"
|
24
|
+
else
|
25
|
+
"#{object.class}##{method_name}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def perform
|
30
|
+
object.send(method_name, *args) if object
|
31
|
+
end
|
32
|
+
|
33
|
+
def method_missing(symbol, *args)
|
34
|
+
object.send(symbol, *args)
|
35
|
+
end
|
36
|
+
|
37
|
+
def respond_to?(symbol, include_private = false)
|
38
|
+
super || object.respond_to?(symbol, include_private)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
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
|