delayed_job_hooked 2.1.5
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 +243 -0
- data/contrib/delayed_job.monitrc +14 -0
- data/contrib/delayed_job_multiple.monitrc +23 -0
- data/lib/delayed/backend/active_record.rb +82 -0
- data/lib/delayed/backend/base.rb +128 -0
- data/lib/delayed/backend/shared_spec.rb +485 -0
- data/lib/delayed/command.rb +112 -0
- data/lib/delayed/deserialization_error.rb +4 -0
- data/lib/delayed/message_sending.rb +54 -0
- data/lib/delayed/performable_mailer.rb +21 -0
- data/lib/delayed/performable_method.rb +31 -0
- data/lib/delayed/railtie.rb +18 -0
- data/lib/delayed/recipes.rb +50 -0
- data/lib/delayed/serialization/active_record.rb +13 -0
- data/lib/delayed/tasks.rb +11 -0
- data/lib/delayed/worker.rb +182 -0
- data/lib/delayed/yaml_ext.rb +41 -0
- data/lib/delayed_job.rb +13 -0
- data/lib/generators/delayed_job/delayed_job_generator.rb +34 -0
- data/lib/generators/delayed_job/templates/migration.rb +21 -0
- data/lib/generators/delayed_job/templates/script +5 -0
- data/recipes/delayed_job.rb +1 -0
- data/spec/active_record_job_spec.rb +36 -0
- data/spec/autoloaded/clazz.rb +7 -0
- data/spec/autoloaded/struct.rb +7 -0
- data/spec/database.yml +4 -0
- data/spec/message_sending_spec.rb +116 -0
- data/spec/performable_mailer_spec.rb +46 -0
- data/spec/performable_method_spec.rb +64 -0
- data/spec/sample_jobs.rb +73 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/worker_spec.rb +37 -0
- data/spec/yaml_ext_spec.rb +31 -0
- metadata +221 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'daemons'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module Delayed
|
6
|
+
class Command
|
7
|
+
attr_accessor :worker_count
|
8
|
+
|
9
|
+
def initialize(args)
|
10
|
+
@files_to_reopen = []
|
11
|
+
@options = {
|
12
|
+
:quiet => true,
|
13
|
+
:pid_dir => "#{Rails.root}/tmp/pids"
|
14
|
+
}
|
15
|
+
|
16
|
+
@worker_count = 1
|
17
|
+
@monitor = false
|
18
|
+
|
19
|
+
opts = OptionParser.new do |opts|
|
20
|
+
opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run"
|
21
|
+
|
22
|
+
opts.on('-h', '--help', 'Show this message') do
|
23
|
+
puts opts
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
opts.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |e|
|
27
|
+
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"
|
28
|
+
end
|
29
|
+
opts.on('--min-priority N', 'Minimum priority of jobs to run.') do |n|
|
30
|
+
@options[:min_priority] = n
|
31
|
+
end
|
32
|
+
opts.on('--max-priority N', 'Maximum priority of jobs to run.') do |n|
|
33
|
+
@options[:max_priority] = n
|
34
|
+
end
|
35
|
+
opts.on('-n', '--number_of_workers=workers', "Number of unique workers to spawn") do |worker_count|
|
36
|
+
@worker_count = worker_count.to_i rescue 1
|
37
|
+
end
|
38
|
+
opts.on('--pid-dir=DIR', 'Specifies an alternate directory in which to store the process ids.') do |dir|
|
39
|
+
@options[:pid_dir] = dir
|
40
|
+
end
|
41
|
+
opts.on('-i', '--identifier=n', 'A numeric identifier for the worker.') do |n|
|
42
|
+
@options[:identifier] = n
|
43
|
+
end
|
44
|
+
opts.on('-m', '--monitor', 'Start monitor process.') do
|
45
|
+
@monitor = true
|
46
|
+
end
|
47
|
+
opts.on('--sleep-delay N', "Amount of time to sleep when no jobs are found") do |n|
|
48
|
+
@options[:sleep_delay] = n
|
49
|
+
end
|
50
|
+
opts.on('-p', '--prefix NAME', "String to be prefixed to worker process names") do |prefix|
|
51
|
+
@options[:prefix] = prefix
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@args = opts.parse!(args)
|
55
|
+
end
|
56
|
+
|
57
|
+
def daemonize
|
58
|
+
Delayed::Worker.backend.before_fork
|
59
|
+
|
60
|
+
ObjectSpace.each_object(File) do |file|
|
61
|
+
@files_to_reopen << file unless file.closed?
|
62
|
+
end
|
63
|
+
|
64
|
+
dir = @options[:pid_dir]
|
65
|
+
Dir.mkdir(dir) unless File.exists?(dir)
|
66
|
+
|
67
|
+
if @worker_count > 1 && @options[:identifier]
|
68
|
+
raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier'
|
69
|
+
elsif @worker_count == 1 && @options[:identifier]
|
70
|
+
process_name = "delayed_job.#{@options[:identifier]}"
|
71
|
+
run_process(process_name, dir)
|
72
|
+
else
|
73
|
+
worker_count.times do |worker_index|
|
74
|
+
process_name = worker_count == 1 ? "delayed_job" : "delayed_job.#{worker_index}"
|
75
|
+
run_process(process_name, dir)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def run_process(process_name, dir)
|
81
|
+
Daemons.run_proc(process_name, :dir => dir, :dir_mode => :normal, :monitor => @monitor, :ARGV => @args) do |*args|
|
82
|
+
$0 = File.join(@options[:prefix], process_name) if @options[:prefix]
|
83
|
+
run process_name
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def run(worker_name = nil)
|
88
|
+
Dir.chdir(Rails.root)
|
89
|
+
|
90
|
+
# Re-open file handles
|
91
|
+
@files_to_reopen.each do |file|
|
92
|
+
begin
|
93
|
+
file.reopen file.path, "a+"
|
94
|
+
file.sync = true
|
95
|
+
rescue ::Exception
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log'))
|
100
|
+
Delayed::Worker.backend.after_fork
|
101
|
+
|
102
|
+
worker = Delayed::Worker.new(@options)
|
103
|
+
worker.name_prefix = "#{worker_name} "
|
104
|
+
worker.start
|
105
|
+
rescue => e
|
106
|
+
Rails.logger.fatal e
|
107
|
+
STDERR.puts e.message
|
108
|
+
exit 1
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
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,31 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
|
3
|
+
module Delayed
|
4
|
+
class PerformableMethod < Struct.new(:object, :method_name, :args)
|
5
|
+
delegate :method, :to => :object
|
6
|
+
|
7
|
+
def initialize(object, method_name, args)
|
8
|
+
raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true)
|
9
|
+
|
10
|
+
self.object = object
|
11
|
+
self.args = args
|
12
|
+
self.method_name = method_name.to_sym
|
13
|
+
end
|
14
|
+
|
15
|
+
def display_name
|
16
|
+
"#{object.class}##{method_name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def perform
|
20
|
+
object.send(method_name, *args) if object
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(symbol, *args)
|
24
|
+
object.send(symbol, *args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def respond_to?(symbol, include_private=false)
|
28
|
+
super || object.respond_to?(symbol, include_private)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'delayed_job'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module Delayed
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer :after_initialize do
|
7
|
+
Delayed::Worker.guess_backend
|
8
|
+
|
9
|
+
ActiveSupport.on_load(:action_mailer) do
|
10
|
+
ActionMailer::Base.send(:extend, Delayed::DelayMail)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
rake_tasks do
|
15
|
+
load 'delayed/tasks.rb'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Capistrano Recipes for managing delayed_job
|
2
|
+
#
|
3
|
+
# Add these callbacks to have the delayed_job process restart when the server
|
4
|
+
# is restarted:
|
5
|
+
#
|
6
|
+
# after "deploy:stop", "delayed_job:stop"
|
7
|
+
# after "deploy:start", "delayed_job:start"
|
8
|
+
# after "deploy:restart", "delayed_job:restart"
|
9
|
+
#
|
10
|
+
# If you want to use command line options, for example to start multiple workers,
|
11
|
+
# define a Capistrano variable delayed_job_args:
|
12
|
+
#
|
13
|
+
# set :delayed_job_args, "-n 2"
|
14
|
+
#
|
15
|
+
# If you've got delayed_job workers running on a servers, you can also specify
|
16
|
+
# which servers have delayed_job running and should be restarted after deploy.
|
17
|
+
#
|
18
|
+
# set :delayed_job_server_role, :worker
|
19
|
+
#
|
20
|
+
|
21
|
+
Capistrano::Configuration.instance.load do
|
22
|
+
namespace :delayed_job do
|
23
|
+
def rails_env
|
24
|
+
fetch(:rails_env, false) ? "RAILS_ENV=#{fetch(:rails_env)}" : ''
|
25
|
+
end
|
26
|
+
|
27
|
+
def args
|
28
|
+
fetch(:delayed_job_args, "")
|
29
|
+
end
|
30
|
+
|
31
|
+
def roles
|
32
|
+
fetch(:delayed_job_server_role, :app)
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Stop the delayed_job process"
|
36
|
+
task :stop, :roles => lambda { roles } do
|
37
|
+
run "cd #{current_path};#{rails_env} script/delayed_job stop"
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Start the delayed_job process"
|
41
|
+
task :start, :roles => lambda { roles } do
|
42
|
+
run "cd #{current_path};#{rails_env} script/delayed_job start #{args}"
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Restart the delayed_job process"
|
46
|
+
task :restart, :roles => lambda { roles } do
|
47
|
+
run "cd #{current_path};#{rails_env} script/delayed_job restart #{args}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class ActiveRecord::Base
|
2
|
+
yaml_as "tag:ruby.yaml.org,2002:ActiveRecord"
|
3
|
+
|
4
|
+
def self.yaml_new(klass, tag, val)
|
5
|
+
klass.find(val['attributes']['id'])
|
6
|
+
rescue ActiveRecord::RecordNotFound
|
7
|
+
raise Delayed::DeserializationError
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_yaml_properties
|
11
|
+
['@attributes']
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
namespace :jobs do
|
2
|
+
desc "Clear the delayed_job queue."
|
3
|
+
task :clear => :environment do
|
4
|
+
Delayed::Job.delete_all
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "Start a delayed_job worker."
|
8
|
+
task :work => :environment do
|
9
|
+
Delayed::Worker.new(:min_priority => ENV['MIN_PRIORITY'], :max_priority => ENV['MAX_PRIORITY'], :quiet => false).start
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'active_support/core_ext/numeric/time'
|
3
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
4
|
+
require 'active_support/core_ext/kernel'
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
module Delayed
|
8
|
+
class Worker
|
9
|
+
cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time, :default_priority, :sleep_delay, :logger, :delay_jobs
|
10
|
+
self.sleep_delay = 5
|
11
|
+
self.max_attempts = 25
|
12
|
+
self.max_run_time = 4.hours
|
13
|
+
self.default_priority = 0
|
14
|
+
self.delay_jobs = true
|
15
|
+
|
16
|
+
# By default failed jobs are destroyed after too many attempts. If you want to keep them around
|
17
|
+
# (perhaps to inspect the reason for the failure), set this to false.
|
18
|
+
cattr_accessor :destroy_failed_jobs
|
19
|
+
self.destroy_failed_jobs = true
|
20
|
+
|
21
|
+
self.logger = if defined?(Rails)
|
22
|
+
Rails.logger
|
23
|
+
elsif defined?(RAILS_DEFAULT_LOGGER)
|
24
|
+
RAILS_DEFAULT_LOGGER
|
25
|
+
end
|
26
|
+
|
27
|
+
# name_prefix is ignored if name is set directly
|
28
|
+
attr_accessor :name_prefix
|
29
|
+
|
30
|
+
cattr_reader :backend
|
31
|
+
|
32
|
+
def self.backend=(backend)
|
33
|
+
if backend.is_a? Symbol
|
34
|
+
require "delayed/serialization/#{backend}"
|
35
|
+
require "delayed/backend/#{backend}"
|
36
|
+
backend = "Delayed::Backend::#{backend.to_s.classify}::Job".constantize
|
37
|
+
end
|
38
|
+
@@backend = backend
|
39
|
+
silence_warnings { ::Delayed.const_set(:Job, backend) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.guess_backend
|
43
|
+
self.backend ||= :active_record if defined?(ActiveRecord)
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(options={})
|
47
|
+
@quiet = options.has_key?(:quiet) ? options[:quiet] : true
|
48
|
+
self.class.min_priority = options[:min_priority] if options.has_key?(:min_priority)
|
49
|
+
self.class.max_priority = options[:max_priority] if options.has_key?(:max_priority)
|
50
|
+
self.class.sleep_delay = options[:sleep_delay] if options.has_key?(:sleep_delay)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Every worker has a unique name which by default is the pid of the process. There are some
|
54
|
+
# advantages to overriding this with something which survives worker retarts: Workers can#
|
55
|
+
# safely resume working on tasks which are locked by themselves. The worker will assume that
|
56
|
+
# it crashed before.
|
57
|
+
def name
|
58
|
+
return @name unless @name.nil?
|
59
|
+
"#{@name_prefix}host:#{Socket.gethostname} pid:#{Process.pid}" rescue "#{@name_prefix}pid:#{Process.pid}"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sets the name of the worker.
|
63
|
+
# Setting the name to nil will reset the default worker name
|
64
|
+
def name=(val)
|
65
|
+
@name = val
|
66
|
+
end
|
67
|
+
|
68
|
+
def start
|
69
|
+
say "Starting job worker"
|
70
|
+
|
71
|
+
trap('TERM') { say 'Exiting...'; $exit = true }
|
72
|
+
trap('INT') { say 'Exiting...'; $exit = true }
|
73
|
+
|
74
|
+
loop do
|
75
|
+
result = nil
|
76
|
+
|
77
|
+
realtime = Benchmark.realtime do
|
78
|
+
result = work_off
|
79
|
+
end
|
80
|
+
|
81
|
+
count = result.sum
|
82
|
+
|
83
|
+
break if $exit
|
84
|
+
|
85
|
+
if count.zero?
|
86
|
+
sleep(self.class.sleep_delay)
|
87
|
+
else
|
88
|
+
say "#{count} jobs processed at %.4f j/s, %d failed ..." % [count / realtime, result.last]
|
89
|
+
end
|
90
|
+
|
91
|
+
break if $exit
|
92
|
+
end
|
93
|
+
|
94
|
+
ensure
|
95
|
+
Delayed::Job.clear_locks!(name)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Do num jobs and return stats on success/failure.
|
99
|
+
# Exit early if interrupted.
|
100
|
+
def work_off(num = 100)
|
101
|
+
success, failure = 0, 0
|
102
|
+
|
103
|
+
num.times do
|
104
|
+
case reserve_and_run_one_job
|
105
|
+
when true
|
106
|
+
success += 1
|
107
|
+
when false
|
108
|
+
failure += 1
|
109
|
+
else
|
110
|
+
break # leave if no work could be done
|
111
|
+
end
|
112
|
+
break if $exit # leave if we're exiting
|
113
|
+
end
|
114
|
+
|
115
|
+
return [success, failure]
|
116
|
+
end
|
117
|
+
|
118
|
+
def run(job)
|
119
|
+
runtime = Benchmark.realtime do
|
120
|
+
Timeout.timeout(self.class.max_run_time.to_i) { job.invoke_job }
|
121
|
+
job.destroy
|
122
|
+
end
|
123
|
+
say "#{job.name} completed after %.4f" % runtime
|
124
|
+
job.hook(:completed)
|
125
|
+
return true # did work
|
126
|
+
rescue DeserializationError => error
|
127
|
+
job.last_error = "{#{error.message}\n#{error.backtrace.join('\n')}"
|
128
|
+
failed(job)
|
129
|
+
rescue Exception => error
|
130
|
+
handle_failed_job(job, error)
|
131
|
+
return false # work failed
|
132
|
+
end
|
133
|
+
|
134
|
+
# Reschedule the job in the future (when a job fails).
|
135
|
+
# Uses an exponential scale depending on the number of failed attempts.
|
136
|
+
def reschedule(job, time = nil)
|
137
|
+
if (job.attempts += 1) < max_attempts(job)
|
138
|
+
time ||= job.reschedule_at
|
139
|
+
job.run_at = time
|
140
|
+
job.unlock
|
141
|
+
job.save!
|
142
|
+
else
|
143
|
+
say "PERMANENTLY removing #{job.name} because of #{job.attempts} consecutive failures.", Logger::INFO
|
144
|
+
failed(job)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def failed(job)
|
149
|
+
job.hook(:failure)
|
150
|
+
if job.respond_to?(:on_permanent_failure)
|
151
|
+
warn "[DEPRECATION] The #on_permanent_failure hook has been renamed to #failure."
|
152
|
+
end
|
153
|
+
self.class.destroy_failed_jobs ? job.destroy : job.update_attributes(:failed_at => Delayed::Job.db_time_now)
|
154
|
+
end
|
155
|
+
|
156
|
+
def say(text, level = Logger::INFO)
|
157
|
+
text = "[Worker(#{name})] #{text}"
|
158
|
+
puts text unless @quiet
|
159
|
+
logger.add level, "#{Time.now.strftime('%FT%T%z')}: #{text}" if logger
|
160
|
+
end
|
161
|
+
|
162
|
+
def max_attempts(job)
|
163
|
+
job.max_attempts || self.class.max_attempts
|
164
|
+
end
|
165
|
+
|
166
|
+
protected
|
167
|
+
|
168
|
+
def handle_failed_job(job, error)
|
169
|
+
job.last_error = "{#{error.message}\n#{error.backtrace.join('\n')}"
|
170
|
+
say "#{job.name} failed with #{error.class.name}: #{error.message} - #{job.attempts} failed attempts", Logger::ERROR
|
171
|
+
reschedule(job)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Run the next job we can get an exclusive lock on.
|
175
|
+
# If no jobs are left we return nil
|
176
|
+
def reserve_and_run_one_job
|
177
|
+
job = Delayed::Job.reserve(self)
|
178
|
+
run(job) if job
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|