lib/zk/locker/locker_base.rb in zk-1.1.1 vs lib/zk/locker/locker_base.rb in zk-1.2.0

- old
+ new

@@ -7,10 +7,11 @@ # exceptions when they fail. This was an oversight on the part of the # author, and it may be corrected sometime in the future. # class LockerBase include ZK::Logging + include ZK::Exceptions # @private attr_accessor :zk # our absolute lock node path @@ -40,30 +41,31 @@ # @param [String] name Unique name that will be used to generate a key. # All instances created with the same `root_lock_node` and `name` will be # holding the same lock. # # @param [String] root_lock_node the root path on the server under which all - # locks will be generated + # locks will be generated, the default is Locker.default_root_lock_node # - def initialize(client, name, root_lock_node = "/_zklocking") + def initialize(client, name, root_lock_node=nil) @zk = client - @root_lock_node = root_lock_node + @root_lock_node = root_lock_node || Locker.default_root_lock_node @path = name @locked = false @waiting = false + @lock_path = nil @root_lock_path = "#{@root_lock_node}/#{@path.gsub("/", "__")}" end # block caller until lock is aquired, then yield # # there is no non-blocking version of this method # def with_lock - lock!(true) + lock(true) yield ensure - unlock! + unlock end # the basename of our lock path # # @example @@ -77,85 +79,186 @@ # @return [String] last path component of our lock path def lock_basename lock_path and File.basename(lock_path) end - # @return [true,false] true if we hold the lock - def locked? - false|@locked + # returns our current idea of whether or not we hold the lock, which does + # not actually check the state on the server. + # + # The reason for the equivocation around _thinking_ we hold the lock is + # to contrast our current state and the actual state on the server. If you + # want to make double-triple certain of the state of the lock, use {#assert!} + # + # @return [true] if we hold the lock + # @return [false] if we don't hold the lock + # + def locked?(check_if_any=false) + false|@locked end + + # * If this instance holds the lock {#locked? is true} we return true (as + # we have already succeeded in acquiring the lock) + # * If this instance doesn't hold the lock, we'll do a check on the server + # to see if there are any participants _who hold the lock and would + # prevent us from acquiring the lock_. + # * If this instance could acquire the lock we will return true. + # * If another client would prevent us from acquiring the lock, we return false. + # + # @note It should be obvious, but there is no way to guarantee that + # between the time this method checks the server and taking any action to + # acquire the lock, another client may grab the lock before us (or + # converseley, another client may release the lock). This is simply meant + # as an advisory, and may be useful in some cases. + # + def acquirable? + raise NotImplementedError + end # @return [true] if we held the lock and this method has # unlocked it successfully # # @return [false] we did not own the lock # - def unlock! + def unlock if @locked cleanup_lock_path! @locked = false true else false # i know, i know, but be explicit end end + # (see #unlock) + # @deprecated the use of unlock! is deprecated and may be removed or have + # its semantics changed in a future release + def unlock! + unlock + end + + # @param blocking [true,false] if true we block the caller until we can obtain + # a lock on the resource + # + # @return [true] if we're already obtained a shared lock, or if we were able to + # obtain the lock in non-blocking mode. + # + # @return [false] if we did not obtain the lock in non-blocking mode + # + # @return [void] if we obtained the lock in blocking mode. + # + # @raise [InterruptedSession] raised when blocked waiting for a lock and + # the underlying client's session is interrupted. + # + # @see ZK::Client::Unixisms#block_until_node_deleted more about possible execptions + def lock(blocking=false) + raise NotImplementedError + end + + # (see #lock) + # @deprecated the use of lock! is deprecated and may be removed or have + # its semantics changed in a future release + def lock!(blocking=false) + lock(blocking) + end + # returns true if this locker is waiting to acquire lock # # @private def waiting? false|@waiting end + # This is for users who wish to check that the assumption is correct + # that they actually still hold the lock. (check for session interruption, + # perhaps a lock is obtained in one method and handed to another) + # + # This, unlike {#locked?} will actually go and check the conditions + # that constitute "holding the lock" with the server. + # + # @raise [InterruptedSession] raised when the zk session has either + # closed or is in an invalid state. + # + # @raise [LockAssertionFailedError] raised if the lock is not held + # + # @example + # + # def process_jobs + # @lock.with_lock do + # @jobs.each do |j| + # @lock.assert! + # perform_job(j) + # end + # end + # end + # + # def perform_job(j) + # puts "hah! he thinks we're workin!" + # sleep(60) + # end + # + def assert! + raise LockAssertionFailedError, "have not obtained the lock yet" unless locked? + raise LockAssertionFailedError, "not connected" unless zk.connected? + raise LockAssertionFailedError, "lock_path was #{lock_path.inspect}" unless lock_path + raise LockAssertionFailedError, "the lock path #{lock_path} did not exist!" unless zk.exists?(lock_path) + raise LockAssertionFailedError, "we do not actually hold the lock" unless got_lock? + end + protected - # @private def in_waiting_status w, @waiting = @waiting, true yield ensure @waiting = w end - # @private def digit_from(path) self.class.digit_from_lock_path(path) end - # @private + # possibly lighter weight check to see if the lock path has any children + # (using stat, rather than getting the list of children). + def any_lock_children? + end + def lock_children(watch=false) - @zk.children(root_lock_path, :watch => watch) + zk.children(root_lock_path, :watch => watch) end - # @private def ordered_lock_children(watch=false) lock_children(watch).tap do |ary| ary.sort! { |a,b| digit_from(a) <=> digit_from(b) } end end - # @private def create_root_path! - @zk.mkdir_p(@root_lock_path) + zk.mkdir_p(@root_lock_path) end + # performs the checks that (according to the recipe) mean that we hold + # the lock. used by (#assert!) + # + def got_lock? + raise NotImplementedError + end + # prefix is the string that will appear in front of the sequence num, # defaults to 'lock' # - # @private def create_lock_path!(prefix='lock') @lock_path = @zk.create("#{root_lock_path}/#{prefix}", "", :mode => :ephemeral_sequential) logger.debug { "got lock path #{@lock_path}" } @lock_path - rescue Exceptions::NoNode + rescue NoNode create_root_path! retry end - # @private def cleanup_lock_path! logger.debug { "removing lock path #{@lock_path}" } - @zk.delete(@lock_path) - @zk.delete(root_lock_path) rescue Exceptions::NotEmpty + zk.delete(@lock_path) + zk.delete(root_lock_path) rescue NotEmpty + @lock_path = nil end end # LockerBase end # Locker end # ZK