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 }