lib/rufus/scheduler.rb in rufus-scheduler-3.0.8 vs lib/rufus/scheduler.rb in rufus-scheduler-3.0.9
- old
+ new
@@ -24,23 +24,23 @@
require 'date' if RUBY_VERSION < '1.9.0'
require 'time'
require 'thread'
require 'tzinfo'
-require 'fileutils'
module Rufus
class Scheduler
require 'rufus/scheduler/util'
require 'rufus/scheduler/jobs'
require 'rufus/scheduler/cronline'
require 'rufus/scheduler/job_array'
+ require 'rufus/scheduler/locks'
- VERSION = '3.0.8'
+ VERSION = '3.0.9'
#
# A common error class for rufus-scheduler
#
class Error < StandardError; end
@@ -91,12 +91,22 @@
@stderr = $stderr
@thread_key = "rufus_scheduler_#{self.object_id}"
- lock || return
+ @scheduler_lock =
+ if lockfile = opts[:lockfile]
+ Rufus::Scheduler::FileLock.new(lockfile)
+ else
+ opts[:scheduler_lock] || Rufus::Scheduler::NullLock.new
+ end
+ @trigger_lock = opts[:trigger_lock] || Rufus::Scheduler::NullLock.new
+
+ # If we can't grab the @scheduler_lock, don't run.
+ @scheduler_lock.lock || return
+
start
end
# Returns a singleton Rufus::Scheduler instance
#
@@ -322,10 +332,53 @@
def job(job_id)
@jobs[job_id]
end
+ # Returns true if the scheduler has acquired the [exclusive] lock and
+ # thus may run.
+ #
+ # Most of the time, a scheduler is run alone and this method should
+ # return true. It is useful in cases where among a group of applications
+ # only one of them should run the scheduler. For schedulers that should
+ # not run, the method should return false.
+ #
+ # Out of the box, rufus-scheduler proposes the
+ # :lockfile => 'path/to/lock/file' scheduler start option. It makes
+ # it easy for schedulers on the same machine to determine which should
+ # run (the first to write the lockfile and lock it). It uses "man 2 flock"
+ # so it probably won't work reliably on distributed file systems.
+ #
+ # If one needs to use a special/different locking mechanism, the scheduler
+ # accepts :scheduler_lock => lock_object. lock_object only needs to respond
+ # to #lock
+ # and #unlock, and both of these methods should be idempotent.
+ #
+ # Look at rufus/scheduler/locks.rb for an example.
+ #
+ def lock
+
+ @scheduler_lock.lock
+ end
+
+ # Sister method to #lock, is called when the scheduler shuts down.
+ #
+ def unlock
+
+ @trigger_lock.unlock
+ @scheduler_lock.unlock
+ end
+
+ # Callback called when a job is triggered. If the lock cannot be acquired,
+ # the job won't run (though it'll still be scheduled to run again if
+ # necessary).
+ #
+ def confirm_lock
+
+ @trigger_lock.lock
+ end
+
# Returns true if this job is currently scheduled.
#
# Takes extra care to answer true if the job is a repeat job
# currently firing.
#
@@ -421,11 +474,12 @@
stderr.puts(" #{pre} scheduler:")
stderr.puts(" #{pre} object_id: #{object_id}")
stderr.puts(" #{pre} opts:")
stderr.puts(" #{pre} #{@opts.inspect}")
stderr.puts(" #{pre} frequency: #{self.frequency}")
- stderr.puts(" #{pre} lockfile: #{@lockfile.inspect}")
+ stderr.puts(" #{pre} scheduler_lock: #{@scheduler_lock.inspect}")
+ stderr.puts(" #{pre} trigger_lock: #{@trigger_lock.inspect}")
stderr.puts(" #{pre} uptime: #{uptime} (#{uptime_s})")
stderr.puts(" #{pre} down?: #{down?}")
stderr.puts(" #{pre} threads: #{self.threads.size}")
stderr.puts(" #{pre} thread: #{self.thread}")
stderr.puts(" #{pre} thread_key: #{self.thread_key}")
@@ -464,64 +518,9 @@
if job_or_job_id.respond_to?(:job_id)
[ job_or_job_id, job_or_job_id.job_id ]
else
[ job(job_or_job_id), job_or_job_id ]
end
- end
-
- # Returns true if the scheduler has acquired the [exclusive] lock and
- # thus may run.
- #
- # Most of the time, a scheduler is run alone and this method should
- # return true. It is useful in cases where among a group of applications
- # only one of them should run the scheduler. For schedulers that should
- # not run, the method should return false.
- #
- # Out of the box, rufus-scheduler proposes the
- # :lockfile => 'path/to/lock/file' scheduler start option. It makes
- # it easy for schedulers on the same machine to determine which should
- # run (to first to write the lockfile and lock it). It uses "man 2 flock"
- # so it probably won't work reliably on distributed file systems.
- #
- # If one needs to use a special/different locking mechanism, providing
- # overriding implementation for this #lock and the #unlock complement is
- # easy.
- #
- def lock
-
- @lockfile = nil
-
- return true unless f = @opts[:lockfile]
-
- raise ArgumentError.new(
- ":lockfile argument must be a string, not a #{f.class}"
- ) unless f.is_a?(String)
-
- FileUtils.mkdir_p(File.dirname(f))
-
- f = File.new(f, File::RDWR | File::CREAT)
- locked = f.flock(File::LOCK_NB | File::LOCK_EX)
-
- return false unless locked
-
- now = Time.now
-
- f.print("pid: #{$$}, ")
- f.print("scheduler.object_id: #{self.object_id}, ")
- f.print("time: #{now}, ")
- f.print("timestamp: #{now.to_f}")
- f.flush
-
- @lockfile = f
-
- true
- end
-
- # Sister method to #lock, is called when the scheduler shuts down.
- #
- def unlock
-
- @lockfile.flock(File::LOCK_UN) if @lockfile
end
def terminate_all_jobs
jobs.each { |j| j.unschedule }