lib/zk/locker/locker_base.rb in zk-1.5.1 vs lib/zk/locker/locker_base.rb in zk-1.5.2
- old
+ new
@@ -46,15 +46,18 @@
# locks will be generated, the default is Locker.default_root_lock_node
#
def initialize(client, name, root_lock_node=nil)
@zk = client
@root_lock_node = root_lock_node || Locker.default_root_lock_node
- @path = name
- @locked = false
- @waiting = false
- @lock_path = nil
+
+ @path = name
+ @locked = false
+ @waiting = false
+ @lock_path = nil
+ @parent_stat = nil
@root_lock_path = "#{@root_lock_node}/#{@path.gsub("/", "__")}"
+
@mutex = Monitor.new
@cond = @mutex.new_cond
@node_deletion_watcher = nil
end
@@ -117,23 +120,25 @@
end
# @return [true] if we held the lock and this method has
# unlocked it successfully
#
- # @return [false] we did not own the lock
+ # @return [false] if we did not own the lock.
#
+ # @note There is more than one way you might not "own the lock"
+ # see [issue #34](https://github.com/slyphon/zk/issues/34)
+ #
def unlock
+ rval = false
synchronize do
if @locked
- cleanup_lock_path!
+ rval = cleanup_lock_path!
@locked = false
@node_deletion_watcher = nil
- true
- else
- false # i know, i know, but be explicit
end
end
+ rval
end
# (see #unlock)
# @deprecated the use of unlock! is deprecated and may be removed or have
# its semantics changed in a future release
@@ -218,10 +223,11 @@
synchronize do
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, "the parent node was replaced!" unless root_lock_path_same?
raise LockAssertionFailedError, "we do not actually hold the lock" unless got_lock?
end
end
protected
@@ -246,10 +252,12 @@
lock_children(watch).tap do |ary|
ary.sort! { |a,b| digit_from(a) <=> digit_from(b) }
end
end
+ # root_lock_path is /_zklocking/foobar
+ #
def create_root_path!
zk.mkdir_p(@root_lock_path)
end
# performs the checks that (according to the recipe) mean that we hold
@@ -260,27 +268,63 @@
end
# prefix is the string that will appear in front of the sequence num,
# defaults to 'lock'
#
+ # this method also saves the stat of root_lock_path at the time of creation
+ # to ensure we don't accidentally remove a lock we don't own. see
+ # [rule #34](https://github.com/slyphon/zk/issues/34)...er, *issue* #34.
+ #
def create_lock_path!(prefix='lock')
synchronize do
- @lock_path = @zk.create("#{root_lock_path}/#{prefix}", "", :mode => :ephemeral_sequential)
+ @lock_path = @zk.create("#{root_lock_path}/#{prefix}", :mode => :ephemeral_sequential)
+ @parent_stat = @zk.stat(root_lock_path)
end
logger.debug { "got lock path #{@lock_path}" }
@lock_path
rescue NoNode
create_root_path!
retry
end
+ # if the root_lock_path has the same stat .ctime as the one
+ # we cached when we created our lock path, then we can be sure
+ # that we actually own the lock_path
+ #
+ # see [issue #34](https://github.com/slyphon/zk/issues/34)
+ #
+ def root_lock_path_same?
+ synchronize do
+ return false unless @parent_stat
+
+ cur_stat = zk.stat(root_lock_path)
+ cur_stat.exists? and (cur_stat.ctime == @parent_stat.ctime)
+ end
+ end
+
+ # we make a best-effort to clean up, this case is rife with race
+ # conditions if there is a lot of contention for the locks, so if we
+ # can't remove a path or if that path happens to not be empty we figure
+ # either we got pwned or that someone else will run this same method
+ # later and get to it
+ #
def cleanup_lock_path!
- logger.debug { "removing lock path #{@lock_path}" }
- zk.delete(@lock_path)
+ rval = false
- zk.delete(root_lock_path, :ignore => :not_empty)
- @lock_path = nil
+ synchronize do
+ if root_lock_path_same?
+ logger.debug { "removing lock path #{@lock_path}" }
+
+ zk.delete(@lock_path, :ignore => :no_node)
+ zk.delete(root_lock_path, :ignore => [:not_empty, :no_node])
+ rval = true
+ end
+
+ @lock_path = @parent_stat = nil
+ end
+
+ rval
end
end # LockerBase
end # Locker
end # ZK