delayed_job 4.0.6 → 4.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +54 -0
- data/README.md +89 -15
- data/delayed_job.gemspec +5 -3
- data/lib/delayed/backend/base.rb +26 -32
- data/lib/delayed/backend/job_preparer.rb +53 -0
- data/lib/delayed/backend/shared_spec.rb +92 -10
- data/lib/delayed/command.rb +40 -14
- data/lib/delayed/exceptions.rb +1 -1
- data/lib/delayed/lifecycle.rb +2 -2
- data/lib/delayed/message_sending.rb +29 -17
- data/lib/delayed/performable_method.rb +6 -4
- data/lib/delayed/psych_ext.rb +20 -7
- data/lib/delayed/railtie.rb +0 -4
- data/lib/delayed/recipes.rb +3 -3
- data/lib/delayed/serialization/active_record.rb +1 -1
- data/lib/delayed/syck_ext.rb +3 -3
- data/lib/delayed/tasks.rb +1 -1
- data/lib/delayed/worker.rb +62 -24
- data/lib/delayed_job.rb +11 -7
- data/lib/generators/delayed_job/delayed_job_generator.rb +1 -1
- data/spec/autoloaded/clazz.rb +1 -2
- data/spec/autoloaded/instance_clazz.rb +1 -2
- data/spec/autoloaded/instance_struct.rb +3 -3
- data/spec/autoloaded/struct.rb +3 -3
- data/spec/daemons.rb +2 -0
- data/spec/delayed/backend/test.rb +0 -5
- data/spec/delayed/command_spec.rb +131 -9
- data/spec/helper.rb +0 -4
- data/spec/message_sending_spec.rb +29 -4
- data/spec/performable_mailer_spec.rb +0 -1
- data/spec/performable_method_spec.rb +3 -4
- data/spec/psych_ext_spec.rb +23 -1
- data/spec/sample_jobs.rb +5 -3
- data/spec/worker_spec.rb +27 -1
- metadata +17 -15
data/lib/delayed/command.rb
CHANGED
@@ -5,16 +5,21 @@ unless ENV['RAILS_ENV'] == 'test'
|
|
5
5
|
raise "You need to add gem 'daemons' to your Gemfile if you wish to use it."
|
6
6
|
end
|
7
7
|
end
|
8
|
+
require 'fileutils'
|
8
9
|
require 'optparse'
|
10
|
+
require 'pathname'
|
9
11
|
|
10
12
|
module Delayed
|
11
13
|
class Command # rubocop:disable ClassLength
|
12
14
|
attr_accessor :worker_count, :worker_pools
|
13
15
|
|
16
|
+
DIR_PWD = Pathname.new Dir.pwd
|
17
|
+
|
14
18
|
def initialize(args) # rubocop:disable MethodLength
|
15
19
|
@options = {
|
16
20
|
:quiet => true,
|
17
|
-
:pid_dir => "#{
|
21
|
+
:pid_dir => "#{root}/tmp/pids",
|
22
|
+
:log_dir => "#{root}/log"
|
18
23
|
}
|
19
24
|
|
20
25
|
@worker_count = 1
|
@@ -28,7 +33,7 @@ module Delayed
|
|
28
33
|
exit 1
|
29
34
|
end
|
30
35
|
opt.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |_e|
|
31
|
-
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
|
36
|
+
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/7'
|
32
37
|
end
|
33
38
|
opt.on('--min-priority N', 'Minimum priority of jobs to run.') do |n|
|
34
39
|
@options[:min_priority] = n
|
@@ -37,11 +42,14 @@ module Delayed
|
|
37
42
|
@options[:max_priority] = n
|
38
43
|
end
|
39
44
|
opt.on('-n', '--number_of_workers=workers', 'Number of unique workers to spawn') do |worker_count|
|
40
|
-
@worker_count = worker_count.to_i rescue 1
|
45
|
+
@worker_count = worker_count.to_i rescue 1
|
41
46
|
end
|
42
47
|
opt.on('--pid-dir=DIR', 'Specifies an alternate directory in which to store the process ids.') do |dir|
|
43
48
|
@options[:pid_dir] = dir
|
44
49
|
end
|
50
|
+
opt.on('--log-dir=DIR', 'Specifies an alternate directory in which to store the delayed_job log.') do |dir|
|
51
|
+
@options[:log_dir] = dir
|
52
|
+
end
|
45
53
|
opt.on('-i', '--identifier=n', 'A numeric identifier for the worker.') do |n|
|
46
54
|
@options[:identifier] = n
|
47
55
|
end
|
@@ -69,22 +77,27 @@ module Delayed
|
|
69
77
|
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
|
70
78
|
@options[:exit_on_complete] = true
|
71
79
|
end
|
80
|
+
opt.on('--daemon-options a, b, c', Array, 'options to be passed through to daemons gem') do |daemon_options|
|
81
|
+
@daemon_options = daemon_options
|
82
|
+
end
|
72
83
|
end
|
73
|
-
@args = opts.parse!(args)
|
84
|
+
@args = opts.parse!(args) + (@daemon_options || [])
|
74
85
|
end
|
75
86
|
|
76
87
|
def daemonize # rubocop:disable PerceivedComplexity
|
77
88
|
dir = @options[:pid_dir]
|
78
|
-
|
89
|
+
FileUtils.mkdir_p(dir) unless File.exist?(dir)
|
79
90
|
|
80
91
|
if worker_pools
|
81
92
|
setup_pools
|
82
93
|
elsif @options[:identifier]
|
94
|
+
# rubocop:disable GuardClause
|
83
95
|
if worker_count > 1
|
84
96
|
raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier'
|
85
97
|
else
|
86
98
|
run_process("delayed_job.#{@options[:identifier]}", @options)
|
87
99
|
end
|
100
|
+
# rubocop:enable GuardClause
|
88
101
|
else
|
89
102
|
worker_count.times do |worker_index|
|
90
103
|
process_name = worker_count == 1 ? 'delayed_job' : "delayed_job.#{worker_index}"
|
@@ -114,18 +127,19 @@ module Delayed
|
|
114
127
|
end
|
115
128
|
|
116
129
|
def run(worker_name = nil, options = {})
|
117
|
-
Dir.chdir(
|
130
|
+
Dir.chdir(root)
|
118
131
|
|
119
132
|
Delayed::Worker.after_fork
|
120
|
-
Delayed::Worker.logger ||= Logger.new(File.join(
|
133
|
+
Delayed::Worker.logger ||= Logger.new(File.join(@options[:log_dir], 'delayed_job.log'))
|
121
134
|
|
122
135
|
worker = Delayed::Worker.new(options)
|
123
136
|
worker.name_prefix = "#{worker_name} "
|
124
137
|
worker.start
|
125
138
|
rescue => e
|
126
|
-
Rails.logger.fatal e
|
127
139
|
STDERR.puts e.message
|
128
|
-
|
140
|
+
STDERR.puts e.backtrace
|
141
|
+
::Rails.logger.fatal(e) if rails_logger_defined?
|
142
|
+
exit_with_error_status
|
129
143
|
end
|
130
144
|
|
131
145
|
private
|
@@ -134,13 +148,25 @@ module Delayed
|
|
134
148
|
@worker_pools ||= []
|
135
149
|
|
136
150
|
queues, worker_count = pool.split(':')
|
137
|
-
|
138
|
-
queues = []
|
139
|
-
else
|
140
|
-
queues = queues.split(',')
|
141
|
-
end
|
151
|
+
queues = ['*', '', nil].include?(queues) ? [] : queues.split(',')
|
142
152
|
worker_count = (worker_count || 1).to_i rescue 1
|
143
153
|
@worker_pools << [queues, worker_count]
|
144
154
|
end
|
155
|
+
|
156
|
+
def root
|
157
|
+
@root ||= rails_root_defined? ? ::Rails.root : DIR_PWD
|
158
|
+
end
|
159
|
+
|
160
|
+
def rails_root_defined?
|
161
|
+
defined?(::Rails.root)
|
162
|
+
end
|
163
|
+
|
164
|
+
def rails_logger_defined?
|
165
|
+
defined?(::Rails.logger)
|
166
|
+
end
|
167
|
+
|
168
|
+
def exit_with_error_status
|
169
|
+
exit 1
|
170
|
+
end
|
145
171
|
end
|
146
172
|
end
|
data/lib/delayed/exceptions.rb
CHANGED
data/lib/delayed/lifecycle.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Delayed
|
2
|
-
class InvalidCallback <
|
2
|
+
class InvalidCallback < RuntimeError; end
|
3
3
|
|
4
4
|
class Lifecycle
|
5
5
|
EVENTS = {
|
@@ -10,7 +10,7 @@ module Delayed
|
|
10
10
|
:error => [:worker, :job],
|
11
11
|
:failure => [:worker, :job],
|
12
12
|
:invoke_job => [:job]
|
13
|
-
}
|
13
|
+
}.freeze
|
14
14
|
|
15
15
|
def initialize
|
16
16
|
@callbacks = EVENTS.keys.each_with_object({}) do |e, hash|
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'active_support/core_ext/module/aliasing'
|
2
|
-
|
3
1
|
module Delayed
|
4
2
|
class DelayProxy < Delayed::Compatibility.proxy_object_class
|
5
3
|
def initialize(payload_class, target, options)
|
@@ -8,9 +6,11 @@ module Delayed
|
|
8
6
|
@options = options
|
9
7
|
end
|
10
8
|
|
9
|
+
# rubocop:disable MethodMissing
|
11
10
|
def method_missing(method, *args)
|
12
11
|
Job.enqueue({:payload_object => @payload_class.new(@target, method.to_sym, args)}.merge(@options))
|
13
12
|
end
|
13
|
+
# rubocop:enable MethodMissing
|
14
14
|
end
|
15
15
|
|
16
16
|
module MessageSending
|
@@ -28,24 +28,36 @@ module Delayed
|
|
28
28
|
warn '[DEPRECATION] `object.send_at(time, :method)` is deprecated. Use `object.delay(:run_at => time).method'
|
29
29
|
__delay__(:run_at => time).__send__(method, *args)
|
30
30
|
end
|
31
|
+
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
33
|
+
module MessageSendingClassMethods
|
34
|
+
def handle_asynchronously(method, opts = {}) # rubocop:disable PerceivedComplexity
|
35
|
+
aliased_method = method.to_s.sub(/([?!=])$/, '')
|
36
|
+
punctuation = $1 # rubocop:disable PerlBackrefs
|
37
|
+
with_method = "#{aliased_method}_with_delay#{punctuation}"
|
38
|
+
without_method = "#{aliased_method}_without_delay#{punctuation}"
|
39
|
+
define_method(with_method) do |*args|
|
40
|
+
curr_opts = opts.clone
|
41
|
+
curr_opts.each_key do |key|
|
42
|
+
next unless (val = curr_opts[key]).is_a?(Proc)
|
43
|
+
curr_opts[key] = if val.arity == 1
|
44
|
+
val.call(self)
|
45
|
+
else
|
46
|
+
val.call
|
45
47
|
end
|
46
|
-
delay(curr_opts).__send__(without_method, *args)
|
47
48
|
end
|
48
|
-
|
49
|
+
delay(curr_opts).__send__(without_method, *args)
|
50
|
+
end
|
51
|
+
|
52
|
+
alias_method without_method, method
|
53
|
+
alias_method method, with_method
|
54
|
+
|
55
|
+
if public_method_defined?(without_method)
|
56
|
+
public method
|
57
|
+
elsif protected_method_defined?(without_method)
|
58
|
+
protected method
|
59
|
+
elsif private_method_defined?(without_method)
|
60
|
+
private method
|
49
61
|
end
|
50
62
|
end
|
51
63
|
end
|
@@ -1,11 +1,7 @@
|
|
1
|
-
require 'active_support/core_ext/module/delegation'
|
2
|
-
|
3
1
|
module Delayed
|
4
2
|
class PerformableMethod
|
5
3
|
attr_accessor :object, :method_name, :args
|
6
4
|
|
7
|
-
delegate :method, :to => :object
|
8
|
-
|
9
5
|
def initialize(object, method_name, args)
|
10
6
|
raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true)
|
11
7
|
|
@@ -30,9 +26,15 @@ module Delayed
|
|
30
26
|
object.send(method_name, *args) if object
|
31
27
|
end
|
32
28
|
|
29
|
+
def method(sym)
|
30
|
+
object.method(sym)
|
31
|
+
end
|
32
|
+
|
33
|
+
# rubocop:disable MethodMissing
|
33
34
|
def method_missing(symbol, *args)
|
34
35
|
object.send(symbol, *args)
|
35
36
|
end
|
37
|
+
# rubocop:enable MethodMissing
|
36
38
|
|
37
39
|
def respond_to?(symbol, include_private = false)
|
38
40
|
super || object.respond_to?(symbol, include_private)
|
data/lib/delayed/psych_ext.rb
CHANGED
@@ -28,23 +28,29 @@ module Delayed
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def visit_Psych_Nodes_Mapping(object) # rubocop:disable CyclomaticComplexity, MethodName, PerceivedComplexity
|
31
|
-
|
31
|
+
klass = Psych.load_tags[object.tag]
|
32
|
+
if klass
|
33
|
+
# Implementation changed here https://github.com/ruby/psych/commit/2c644e184192975b261a81f486a04defa3172b3f
|
34
|
+
# load_tags used to have class values, now the values are strings
|
35
|
+
klass = resolve_class(klass) if klass.is_a?(String)
|
36
|
+
return revive(klass, object)
|
37
|
+
end
|
32
38
|
|
33
39
|
case object.tag
|
34
|
-
when
|
40
|
+
when %r{^!ruby/object}
|
35
41
|
result = super
|
36
|
-
if
|
42
|
+
if jruby_is_seriously_borked && result.is_a?(ActiveRecord::Base)
|
37
43
|
klass = result.class
|
38
44
|
id = result[klass.primary_key]
|
39
45
|
begin
|
40
|
-
klass.find(id)
|
46
|
+
klass.unscoped.find(id)
|
41
47
|
rescue ActiveRecord::RecordNotFound => error # rubocop:disable BlockNesting
|
42
48
|
raise Delayed::DeserializationError, "ActiveRecord::RecordNotFound, class: #{klass}, primary key: #{id} (#{error.message})"
|
43
49
|
end
|
44
50
|
else
|
45
51
|
result
|
46
52
|
end
|
47
|
-
when
|
53
|
+
when %r{^!ruby/ActiveRecord:(.+)$}
|
48
54
|
klass = resolve_class(Regexp.last_match[1])
|
49
55
|
payload = Hash[*object.children.map { |c| accept c }]
|
50
56
|
id = payload['attributes'][klass.primary_key]
|
@@ -54,7 +60,7 @@ module Delayed
|
|
54
60
|
rescue ActiveRecord::RecordNotFound => error
|
55
61
|
raise Delayed::DeserializationError, "ActiveRecord::RecordNotFound, class: #{klass}, primary key: #{id} (#{error.message})"
|
56
62
|
end
|
57
|
-
when
|
63
|
+
when %r{^!ruby/Mongoid:(.+)$}
|
58
64
|
klass = resolve_class(Regexp.last_match[1])
|
59
65
|
payload = Hash[*object.children.map { |c| accept c }]
|
60
66
|
id = payload['attributes']['_id']
|
@@ -63,7 +69,7 @@ module Delayed
|
|
63
69
|
rescue Mongoid::Errors::DocumentNotFound => error
|
64
70
|
raise Delayed::DeserializationError, "Mongoid::Errors::DocumentNotFound, class: #{klass}, primary key: #{id} (#{error.message})"
|
65
71
|
end
|
66
|
-
when
|
72
|
+
when %r{^!ruby/DataMapper:(.+)$}
|
67
73
|
klass = resolve_class(Regexp.last_match[1])
|
68
74
|
payload = Hash[*object.children.map { |c| accept c }]
|
69
75
|
begin
|
@@ -78,6 +84,13 @@ module Delayed
|
|
78
84
|
end
|
79
85
|
end
|
80
86
|
|
87
|
+
# defined? is triggering something really messed up in
|
88
|
+
# jruby causing both the if AND else clauses to execute,
|
89
|
+
# however if the check is run here, everything is fine
|
90
|
+
def jruby_is_seriously_borked
|
91
|
+
defined?(ActiveRecord::Base)
|
92
|
+
end
|
93
|
+
|
81
94
|
def resolve_class(klass_name)
|
82
95
|
return nil if !klass_name || klass_name.empty?
|
83
96
|
klass_name.constantize
|
data/lib/delayed/railtie.rb
CHANGED
@@ -4,10 +4,6 @@ require 'rails'
|
|
4
4
|
module Delayed
|
5
5
|
class Railtie < Rails::Railtie
|
6
6
|
initializer :after_initialize do
|
7
|
-
ActiveSupport.on_load(:action_mailer) do
|
8
|
-
ActionMailer::Base.extend(Delayed::DelayMail)
|
9
|
-
end
|
10
|
-
|
11
7
|
Delayed::Worker.logger ||= if defined?(Rails)
|
12
8
|
Rails.logger
|
13
9
|
elsif defined?(RAILS_DEFAULT_LOGGER)
|
data/lib/delayed/recipes.rb
CHANGED
@@ -38,17 +38,17 @@ Capistrano::Configuration.instance.load do
|
|
38
38
|
|
39
39
|
desc 'Stop the delayed_job process'
|
40
40
|
task :stop, :roles => lambda { roles } do
|
41
|
-
run "cd #{current_path}
|
41
|
+
run "cd #{current_path} && #{rails_env} #{delayed_job_command} stop #{args}"
|
42
42
|
end
|
43
43
|
|
44
44
|
desc 'Start the delayed_job process'
|
45
45
|
task :start, :roles => lambda { roles } do
|
46
|
-
run "cd #{current_path}
|
46
|
+
run "cd #{current_path} && #{rails_env} #{delayed_job_command} start #{args}"
|
47
47
|
end
|
48
48
|
|
49
49
|
desc 'Restart the delayed_job process'
|
50
50
|
task :restart, :roles => lambda { roles } do
|
51
|
-
run "cd #{current_path}
|
51
|
+
run "cd #{current_path} && #{rails_env} #{delayed_job_command} restart #{args}"
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
data/lib/delayed/syck_ext.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
class Module
|
2
|
-
|
2
|
+
yaml_tag 'tag:ruby.yaml.org,2002:module'
|
3
3
|
|
4
4
|
def self.yaml_new(_klass, _tag, val)
|
5
5
|
val.constantize
|
@@ -20,7 +20,7 @@ class Module
|
|
20
20
|
end
|
21
21
|
|
22
22
|
class Class
|
23
|
-
|
23
|
+
yaml_tag 'tag:ruby.yaml.org,2002:class'
|
24
24
|
remove_method :to_yaml if respond_to?(:to_yaml) && method(:to_yaml).owner == Class # use Module's to_yaml
|
25
25
|
end
|
26
26
|
|
@@ -29,7 +29,7 @@ class Struct
|
|
29
29
|
# Constantize the object so that ActiveSupport can attempt
|
30
30
|
# its auto loading magic. Will raise LoadError if not successful.
|
31
31
|
name.constantize
|
32
|
-
"Struct::#{
|
32
|
+
"Struct::#{name}"
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
data/lib/delayed/tasks.rb
CHANGED
@@ -19,7 +19,7 @@ namespace :jobs do
|
|
19
19
|
:min_priority => ENV['MIN_PRIORITY'],
|
20
20
|
:max_priority => ENV['MAX_PRIORITY'],
|
21
21
|
:queues => (ENV['QUEUES'] || ENV['QUEUE'] || '').split(','),
|
22
|
-
:quiet =>
|
22
|
+
:quiet => ENV['QUIET']
|
23
23
|
}
|
24
24
|
|
25
25
|
@worker_options[:sleep_delay] = ENV['SLEEP_DELAY'].to_i if ENV['SLEEP_DELAY']
|
data/lib/delayed/worker.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
require 'timeout'
|
2
|
+
require 'active_support/dependencies'
|
2
3
|
require 'active_support/core_ext/numeric/time'
|
3
4
|
require 'active_support/core_ext/class/attribute_accessors'
|
4
|
-
require 'active_support/
|
5
|
-
require 'active_support/core_ext/
|
5
|
+
require 'active_support/hash_with_indifferent_access'
|
6
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
6
7
|
require 'logger'
|
7
8
|
require 'benchmark'
|
8
9
|
|
9
10
|
module Delayed
|
10
11
|
class Worker # rubocop:disable ClassLength
|
11
|
-
DEFAULT_LOG_LEVEL = 'info'
|
12
|
+
DEFAULT_LOG_LEVEL = 'info'.freeze
|
12
13
|
DEFAULT_SLEEP_DELAY = 5
|
13
14
|
DEFAULT_MAX_ATTEMPTS = 25
|
14
15
|
DEFAULT_MAX_RUN_TIME = 4.hours
|
15
16
|
DEFAULT_DEFAULT_PRIORITY = 0
|
16
17
|
DEFAULT_DELAY_JOBS = true
|
17
|
-
DEFAULT_QUEUES = []
|
18
|
+
DEFAULT_QUEUES = [].freeze
|
19
|
+
DEFAULT_QUEUE_ATTRIBUTES = HashWithIndifferentAccess.new.freeze
|
18
20
|
DEFAULT_READ_AHEAD = 5
|
19
21
|
|
20
22
|
cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time,
|
@@ -25,7 +27,7 @@ module Delayed
|
|
25
27
|
# Named queue into which jobs are enqueued by default
|
26
28
|
cattr_accessor :default_queue_name
|
27
29
|
|
28
|
-
cattr_reader :backend
|
30
|
+
cattr_reader :backend, :queue_attributes
|
29
31
|
|
30
32
|
# name_prefix is ignored if name is set directly
|
31
33
|
attr_accessor :name_prefix
|
@@ -38,11 +40,11 @@ module Delayed
|
|
38
40
|
self.default_priority = DEFAULT_DEFAULT_PRIORITY
|
39
41
|
self.delay_jobs = DEFAULT_DELAY_JOBS
|
40
42
|
self.queues = DEFAULT_QUEUES
|
43
|
+
self.queue_attributes = DEFAULT_QUEUE_ATTRIBUTES
|
41
44
|
self.read_ahead = DEFAULT_READ_AHEAD
|
45
|
+
@lifecycle = nil
|
42
46
|
end
|
43
47
|
|
44
|
-
reset
|
45
|
-
|
46
48
|
# Add or remove plugins in this list before the worker is instantiated
|
47
49
|
self.plugins = [Delayed::Plugins::ClearLocks]
|
48
50
|
|
@@ -69,6 +71,11 @@ module Delayed
|
|
69
71
|
silence_warnings { ::Delayed.const_set(:Job, backend) }
|
70
72
|
end
|
71
73
|
|
74
|
+
# rubocop:disable ClassVars
|
75
|
+
def self.queue_attributes=(val)
|
76
|
+
@@queue_attributes = val.with_indifferent_access
|
77
|
+
end
|
78
|
+
|
72
79
|
def self.guess_backend
|
73
80
|
warn '[DEPRECATION] guess_backend is deprecated. Please remove it from your code.'
|
74
81
|
end
|
@@ -97,13 +104,29 @@ module Delayed
|
|
97
104
|
end
|
98
105
|
|
99
106
|
def self.lifecycle
|
100
|
-
|
107
|
+
# In case a worker has not been set up, job enqueueing needs a lifecycle.
|
108
|
+
setup_lifecycle unless @lifecycle
|
109
|
+
|
110
|
+
@lifecycle
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.setup_lifecycle
|
114
|
+
@lifecycle = Delayed::Lifecycle.new
|
115
|
+
plugins.each { |klass| klass.new }
|
101
116
|
end
|
102
117
|
|
103
118
|
def self.reload_app?
|
104
119
|
defined?(ActionDispatch::Reloader) && Rails.application.config.cache_classes == false
|
105
120
|
end
|
106
121
|
|
122
|
+
def self.delay_job?(job)
|
123
|
+
if delay_jobs.is_a?(Proc)
|
124
|
+
delay_jobs.arity == 1 ? delay_jobs.call(job) : delay_jobs.call
|
125
|
+
else
|
126
|
+
delay_jobs
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
107
130
|
def initialize(options = {})
|
108
131
|
@quiet = options.key?(:quiet) ? options[:quiet] : true
|
109
132
|
@failed_reserve_count = 0
|
@@ -112,7 +135,9 @@ module Delayed
|
|
112
135
|
self.class.send("#{option}=", options[option]) if options.key?(option)
|
113
136
|
end
|
114
137
|
|
115
|
-
|
138
|
+
# Reset lifecycle on the offhand chance that something lazily
|
139
|
+
# triggered its creation before all plugins had been registered.
|
140
|
+
self.class.setup_lifecycle
|
116
141
|
end
|
117
142
|
|
118
143
|
# Every worker has a unique name which by default is the pid of the process. There are some
|
@@ -121,7 +146,7 @@ module Delayed
|
|
121
146
|
# it crashed before.
|
122
147
|
def name
|
123
148
|
return @name unless @name.nil?
|
124
|
-
"#{@name_prefix}host:#{Socket.gethostname} pid:#{Process.pid}" rescue "#{@name_prefix}pid:#{Process.pid}"
|
149
|
+
"#{@name_prefix}host:#{Socket.gethostname} pid:#{Process.pid}" rescue "#{@name_prefix}pid:#{Process.pid}"
|
125
150
|
end
|
126
151
|
|
127
152
|
# Sets the name of the worker.
|
@@ -151,7 +176,7 @@ module Delayed
|
|
151
176
|
end
|
152
177
|
end
|
153
178
|
|
154
|
-
count = @result
|
179
|
+
count = @result[0] + @result[1]
|
155
180
|
|
156
181
|
if count.zero?
|
157
182
|
if self.class.exit_on_complete
|
@@ -181,7 +206,8 @@ module Delayed
|
|
181
206
|
# Do num jobs and return stats on success/failure.
|
182
207
|
# Exit early if interrupted.
|
183
208
|
def work_off(num = 100)
|
184
|
-
success
|
209
|
+
success = 0
|
210
|
+
failure = 0
|
185
211
|
|
186
212
|
num.times do
|
187
213
|
case reserve_and_run_one_job
|
@@ -190,7 +216,7 @@ module Delayed
|
|
190
216
|
when false
|
191
217
|
failure += 1
|
192
218
|
else
|
193
|
-
break
|
219
|
+
break # leave if no work could be done
|
194
220
|
end
|
195
221
|
break if stop? # leave if we're exiting
|
196
222
|
end
|
@@ -200,18 +226,20 @@ module Delayed
|
|
200
226
|
|
201
227
|
def run(job)
|
202
228
|
job_say job, 'RUNNING'
|
203
|
-
runtime =
|
229
|
+
runtime = Benchmark.realtime do
|
204
230
|
Timeout.timeout(max_run_time(job).to_i, WorkerTimeout) { job.invoke_job }
|
205
231
|
job.destroy
|
206
232
|
end
|
207
233
|
job_say job, format('COMPLETED after %.4f', runtime)
|
208
|
-
return true
|
234
|
+
return true # did work
|
209
235
|
rescue DeserializationError => error
|
210
|
-
job
|
236
|
+
job_say job, "FAILED permanently with #{error.class.name}: #{error.message}", 'error'
|
237
|
+
|
238
|
+
job.error = error
|
211
239
|
failed(job)
|
212
|
-
rescue => error
|
240
|
+
rescue Exception => error # rubocop:disable RescueException
|
213
241
|
self.class.lifecycle.run_callbacks(:error, self, job) { handle_failed_job(job, error) }
|
214
|
-
return false
|
242
|
+
return false # work failed
|
215
243
|
end
|
216
244
|
|
217
245
|
# Reschedule the job in the future (when a job fails).
|
@@ -223,7 +251,7 @@ module Delayed
|
|
223
251
|
job.unlock
|
224
252
|
job.save!
|
225
253
|
else
|
226
|
-
job_say job, "
|
254
|
+
job_say job, "FAILED permanently because of #{job.attempts} consecutive failures", 'error'
|
227
255
|
failed(job)
|
228
256
|
end
|
229
257
|
end
|
@@ -236,13 +264,13 @@ module Delayed
|
|
236
264
|
say "Error when running failure callback: #{error}", 'error'
|
237
265
|
say error.backtrace.join("\n"), 'error'
|
238
266
|
ensure
|
239
|
-
|
267
|
+
job.destroy_failed_jobs? ? job.destroy : job.fail!
|
240
268
|
end
|
241
269
|
end
|
242
270
|
end
|
243
271
|
|
244
272
|
def job_say(job, text, level = default_log_level)
|
245
|
-
text = "Job #{job.name} (id=#{job.id}) #{text}"
|
273
|
+
text = "Job #{job.name} (id=#{job.id})#{say_queue(job.queue)} #{text}"
|
246
274
|
say text, level
|
247
275
|
end
|
248
276
|
|
@@ -267,8 +295,12 @@ module Delayed
|
|
267
295
|
|
268
296
|
protected
|
269
297
|
|
298
|
+
def say_queue(queue)
|
299
|
+
" (queue=#{queue})" if queue
|
300
|
+
end
|
301
|
+
|
270
302
|
def handle_failed_job(job, error)
|
271
|
-
job.
|
303
|
+
job.error = error
|
272
304
|
job_say job, "FAILED (#{job.attempts} prior attempts) with #{error.class.name}: #{error.message}", 'error'
|
273
305
|
reschedule(job)
|
274
306
|
end
|
@@ -294,8 +326,14 @@ module Delayed
|
|
294
326
|
|
295
327
|
def reload!
|
296
328
|
return unless self.class.reload_app?
|
297
|
-
|
298
|
-
|
329
|
+
if defined?(ActiveSupport::Reloader)
|
330
|
+
Rails.application.reloader.reload!
|
331
|
+
else
|
332
|
+
ActionDispatch::Reloader.cleanup!
|
333
|
+
ActionDispatch::Reloader.prepare!
|
334
|
+
end
|
299
335
|
end
|
300
336
|
end
|
301
337
|
end
|
338
|
+
|
339
|
+
Delayed::Worker.reset
|