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