Permalink
Browse files

handle :timeout in ZK::Client::Conveniences#with_lock

  • Loading branch information...
1 parent ceb2ca0 commit 60aa86a530cf350cca685094691682374395f2c7 @slyphon slyphon committed Aug 29, 2012
Showing with 105 additions and 34 deletions.
  1. +21 −4 lib/zk/client/conveniences.rb
  2. +8 −2 lib/zk/locker/locker_base.rb
  3. +76 −28 spec/zk/locker/locker_basic_spec.rb
@@ -66,6 +66,9 @@ def shared_locker(name)
# will block the caller until the lock is acquired, and release the lock
# when the block is exited.
#
+ # Options are the same as for {Locker::LockerBase#lock #lock} with the addition of
+ # `:mode`, documented below.
+ #
# @param name (see #locker)
#
# @option opts [:shared,:exclusive] :mode (:exclusive) the type of lock
@@ -78,21 +81,35 @@ def shared_locker(name)
#
# @example
#
- # zk.with_lock('foo') do
+ # zk.with_lock('foo') do |lock|
# # this code is executed while holding the lock
# end
#
+ # @example with timeout
+ #
+ # begin
+ # zk.with_lock('foo', :timeout => 5.0) do |lock|
+ # # this code is executed while holding the lock
+ # end
+ # rescue ZK::Exceptions::LockWaitTimeoutError
+ # $stderr.puts "we didn't acquire the lock in time"
+ # end
+ #
# @raise [ArgumentError] if `opts[:mode]` is not one of the expected values
#
+ # @raise [ZK::Exceptions::LockWaitTimeoutError] if :timeout is exceeded
+ # without acquiring the lock
+ #
def with_lock(name, opts={}, &b)
- mode = opts[:mode] || :exclusive
+ opts = opts.dup
+ mode = opts.delete(:mode) { |_| :exclusive }
raise ArgumentError, ":mode option must be either :shared or :exclusive, not #{mode.inspect}" unless [:shared, :exclusive].include?(mode)
if mode == :shared
- shared_locker(name).with_lock(&b)
+ shared_locker(name).with_lock(opts, &b)
else
- locker(name).with_lock(&b)
+ locker(name).with_lock(opts, &b)
end
end
@@ -66,8 +66,14 @@ def initialize(client, name, root_lock_node=nil)
# there is no non-blocking version of this method
#
# @yield [lock] calls the block with the lock instance when acquired
- def with_lock
- lock(true)
+ #
+ # @option opts [Numeric] :timeout (nil) if non-nil, the amount of time to
+ # wait for the lock to be acquired.
+ #
+ # @raise [LockWaitTimeoutError] if the :timeout is exceeded
+ def with_lock(opts={})
+ opts = opts.merge(:block => true)
+ lock(opts)
yield self
ensure
unlock
@@ -56,42 +56,90 @@
@zk.locker("my/multi/part/path").lock.should be_true
end
- it "should blocking lock" do
- array = []
- first_lock = @zk.locker("mylock")
- first_lock.lock.should be_true
- array << :first_lock
-
- thread = Thread.new do
- @zk.locker("mylock").with_lock do
- array << :second_lock
+ describe :with_lock do
+ # TODO: reorganize these tests so Convenience testing is done somewhere saner
+ #
+ # this tests ZK::Client::Conveniences, maybe shouldn't be *here*
+ describe 'Client::Conveniences' do
+ it %[should yield the lock instance to the block] do
+ @zk.with_lock(@path_to_lock) do |lock|
+ lock.should_not be_nil
+ lock.should be_kind_of(ZK::Locker::LockerBase)
+ lambda { lock.assert! }.should_not raise_error
+ end
end
- array.length.should == 2
- end
- array.length.should == 1
- first_lock.unlock
- thread.join(10)
- array.length.should == 2
- end
+ it %[should yield a shared lock when :mode => shared given] do
+ @zk.with_lock(@path_to_lock, :mode => :shared) do |lock|
+ lock.should_not be_nil
+ lock.should be_kind_of(ZK::Locker::SharedLocker)
+ lambda { lock.assert! }.should_not raise_error
+ end
+ end
- describe :with_lock do
- it %[should yield the lock instance to the block] do
- @zk.with_lock(@path_to_lock) do |lock|
- lock.should_not be_nil
- lock.should be_kind_of(ZK::Locker::LockerBase)
- lambda { lock.assert! }.should_not raise_error
+ it %[should take a timeout] do
+ first_lock = @zk.locker(@path_to_lock)
+ first_lock.lock.should be_true
+
+ thread = Thread.new do
+ begin
+ @zk.with_lock(@path_to_lock, :timeout => 0.01) do |lock|
+ raise "NO NO NO!! should not have called the block!!"
+ end
+ rescue Exception => e
+ @exc = e
+ end
+ end
+
+ thread.join(2).should == thread
+ @exc.should be_kind_of(ZK::Exceptions::LockWaitTimeoutError)
end
end
- it %[should yield a shared lock when :mode => shared given] do
- @zk.with_lock(@path_to_lock, :mode => :shared) do |lock|
- lock.should_not be_nil
- lock.should be_kind_of(ZK::Locker::SharedLocker)
- lambda { lock.assert! }.should_not raise_error
+ describe 'LockerBase' do
+ it "should blocking lock" do
+ array = []
+ first_lock = @zk.locker("mylock")
+ first_lock.lock.should be_true
+ array << :first_lock
+
+ thread = Thread.new do
+ @zk.locker("mylock").with_lock do
+ array << :second_lock
+ end
+ array.length.should == 2
+ end
+
+ array.length.should == 1
+ first_lock.unlock
+ thread.join(10)
+ array.length.should == 2
+ end
+
+ it %[should accept a :timeout option] do
+ array = []
+ first_lock = @zk.locker("mylock")
+ first_lock.lock.should be_true
+
+ second_lock = @zk.locker("mylock")
+
+ thread = Thread.new do
+ begin
+ second_lock.with_lock(:timeout => 0.01) do
+ array << :second_lock
+ end
+ rescue Exception => e
+ @exc = e
+ end
+ end
+
+ array.should be_empty
+ thread.join(2).should == thread
+ @exc.should_not be_nil
+ @exc.should be_kind_of(ZK::Exceptions::LockWaitTimeoutError)
end
end
- end
+ end # with_lock
end

0 comments on commit 60aa86a

Please sign in to comment.