delayed_job 4.0.6 → 4.1.8
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.
- 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
|