zk 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,24 +3,10 @@ module ZK
3
3
  class SharedLocker < LockerBase
4
4
  include Exceptions
5
5
 
6
+ # (see LockerBase#lock)
6
7
  # obtain a shared lock.
7
8
  #
8
- # @param blocking [true,false] if true we block the caller until we can obtain
9
- # a lock on the resource
10
- #
11
- # @return [true] if we're already obtained a shared lock, or if we were able to
12
- # obtain the lock in non-blocking mode.
13
- #
14
- # @return [false] if we did not obtain the lock in non-blocking mode
15
- #
16
- # @return [void] if we obtained the lock in blocking mode.
17
- #
18
- # @raise [InterruptedSession] raised when blocked waiting for a lock and
19
- # the underlying client's session is interrupted.
20
- #
21
- # @see ZK::Client::Unixisms#block_until_node_deleted more about possible execptions
22
- #
23
- def lock!(blocking=false)
9
+ def lock(blocking=false)
24
10
  return true if @locked
25
11
  create_lock_path!(SHARED_LOCK_PREFIX)
26
12
 
@@ -38,9 +24,35 @@ module ZK
38
24
  end
39
25
  end
40
26
 
27
+ # (see LockerBase#assert!)
28
+ #
29
+ # checks that we:
30
+ #
31
+ # * we have obtained the lock (i.e. {#locked?} is true)
32
+ # * have a lock path
33
+ # * our lock path still exists
34
+ # * there are no exclusive locks with lower numbers than ours
35
+ #
36
+ def assert!
37
+ super
38
+ end
39
+
40
+ # (see LockerBase#locked?)
41
+ def locked?
42
+ false|@locked
43
+ end
44
+
45
+ # (see LockerBase#acquirable?)
46
+ def acquirable?
47
+ return true if locked?
48
+ !lock_children.any? { |n| n.start_with?(EXCLUSIVE_LOCK_PREFIX) }
49
+ rescue Exceptions::NoNode
50
+ true
51
+ end
52
+
41
53
  # @private
42
54
  def lock_number
43
- @lock_number ||= (lock_path and digit_from(lock_path))
55
+ lock_path and digit_from(lock_path)
44
56
  end
45
57
 
46
58
  # returns the sequence number of the next lowest write lock node
@@ -69,9 +81,9 @@ module ZK
69
81
  ary = ordered_lock_children()
70
82
  my_idx = ary.index(lock_basename) # our idx would be 2
71
83
 
72
- not_found = lambda { raise NoWriteLockFoundException }
73
-
74
- ary[0..my_idx].reverse.find(not_found) { |n| n =~ /^#{EXCLUSIVE_LOCK_PREFIX}/ }
84
+ ary[0..my_idx].reverse.find { |n| n.start_with?(EXCLUSIVE_LOCK_PREFIX) }.tap do |rv|
85
+ raise NoWriteLockFoundException if rv.nil?
86
+ end
75
87
  end
76
88
 
77
89
  # @private
@@ -80,13 +92,13 @@ module ZK
80
92
  rescue NoWriteLockFoundException
81
93
  true
82
94
  end
95
+ alias got_lock? got_read_lock?
83
96
 
84
97
  protected
85
98
  # TODO: make this generic, can either block or non-block
86
- # @private
87
99
  def block_until_read_lock!
88
100
  begin
89
- path = [root_lock_path, next_lowest_write_lock_name].join('/')
101
+ path = "#{root_lock_path}/#{next_lowest_write_lock_name}"
90
102
  logger.debug { "SharedLocker#block_until_read_lock! path=#{path.inspect}" }
91
103
  @zk.block_until_node_deleted(path)
92
104
  rescue NoWriteLockFoundException
data/lib/zk/stat.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module ZK
2
- # Included in ZookeeperStat::Stat, extends it with some conveniences for
2
+ # Included in Zookeeper::Stat, extends it with some conveniences for
3
3
  # dealing with Stat objects. Also provides docuemntation here for the meaning
4
4
  # of these values.
5
5
  #
@@ -109,7 +109,7 @@ module ZK
109
109
  end # Stat
110
110
  end # ZK
111
111
 
112
- class ZookeeperStat::Stat
112
+ class Zookeeper::Stat
113
113
  include ZK::Stat
114
114
  end
115
115
 
@@ -0,0 +1,65 @@
1
+ module ZK
2
+ # Basic pattern for objects that have the concept of a parent (the thing that
3
+ # granted this subscription), a callback, and that can unregister (so the
4
+ # callback no longer receives events).
5
+ #
6
+ # expects the 'parent' to respond_to? the 'unregister' method, and will
7
+ # be passed the subscription instance
8
+ module Subscription
9
+ class Base
10
+ include ZK::Logging
11
+
12
+ # the object from which we will attempt to #unregister on
13
+ # XXX: need a better name for this
14
+ attr_reader :parent
15
+
16
+ # the user-supplied callback block, used to create a ThreadedCallback
17
+ attr_reader :callable
18
+
19
+ def initialize(parent, block)
20
+ raise ArgumentError, "block must repsond_to?(:call)" unless block.respond_to?(:call)
21
+ @parent = parent
22
+ @callable = block
23
+ @mutex = Monitor.new
24
+ end
25
+
26
+ def unregister
27
+ parent.unregister(self)
28
+ end
29
+ alias unsubscribe unregister
30
+
31
+ # @private
32
+ def call(*args)
33
+ callable.call(*args)
34
+ end
35
+
36
+ protected
37
+ def synchronize
38
+ @mutex.synchronize { yield }
39
+ end
40
+ end
41
+
42
+ module ActorStyle
43
+ extend Concern
44
+
45
+ included do
46
+ alias_method_chain :unsubscribe, :threaded_callback
47
+ alias_method_chain :callable, :threaded_callback_wrapper
48
+ end
49
+
50
+ def unsubscribe_with_threaded_callback
51
+ synchronize do
52
+ @threaded_callback.shutdown
53
+ unsubscribe_without_threaded_callback
54
+ end
55
+ end
56
+
57
+ def callable_with_threaded_callback_wrapper(*args)
58
+ synchronize do
59
+ @threaded_callback ||= ThreadedCallback.new(@callable)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
data/lib/zk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ZK
2
- VERSION = "1.1.1"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -0,0 +1,20 @@
1
+ A Day at the Races
2
+
3
+ * What are race conditions?
4
+ * Simple example with changing a node
5
+ * Simple example with a node being deleted
6
+ * What pattern do we use in ZK to manage this problem?
7
+
8
+ ```ruby
9
+ def block_until_node_deleted(zk, abs_node_path)
10
+ node_deletion_cb = lambda do |event|
11
+ if event.node_deleted?
12
+ queue.enq(:deleted)
13
+ else
14
+ queue.enq(:deleted) unless exists?(abs_node_path, :watch => true)
15
+ end
16
+ end
17
+
18
+ subs << event_handler.register(abs_node_path, &node_deletion_cb)
19
+ ```
20
+
@@ -0,0 +1,39 @@
1
+ overall subject of voting is '/blah'
2
+
3
+ all nodes denote presence create('/blah/nodes/id', '', :ephemeral => true)
4
+
5
+ coordinator notes children('/blah/nodes/id', watch: true)
6
+
7
+ if any presence nodes are deleted, the transaction is aborted
8
+
9
+ nodes children('/blah/tx', watch: true)
10
+
11
+ coordinator voteseq = create('/blah/tx/vote', 'proposal', sequential: true) # beginning of election
12
+
13
+ coordinator children('/blah/tx/voteseq', watch: true) # loop until all nodes checked in
14
+
15
+ nodes children('/blah/tx')
16
+ nodes children('/blah/tx/voteseq', watch: true) # all nodes voted, then commit
17
+ nodes get('/blah/tx/voteseq', watch: true) # if data == 'abort' then delete id node
18
+ nodes get('/blah/tx/votes_uuid/result' watch: true) # result will be placed here?
19
+ nodes create('/blah/tx/voteseq/id', up_or_down, ephemeral: true) # any node votes down, abort
20
+
21
+ # coordinator sees all votes have been cast
22
+
23
+ # NOTE: is this step necessary? the coordinator confirming?
24
+
25
+ coordinator create('/blah/tx/voteseq/result', up_or_down, ephemeral: true)
26
+
27
+ # nodes tear down
28
+
29
+ node delete('/blah/tx/voteseq/id')
30
+
31
+ # when just result node is left
32
+
33
+ coordinator rm_rf('/blah/tx/voteseq')
34
+
35
+
36
+ # at many points we need a timer to see if things are taking too long
37
+
38
+
39
+
@@ -4,19 +4,22 @@ require 'spec_helper'
4
4
  describe EventCatcher do
5
5
  subject { EventCatcher.new }
6
6
 
7
+ let!(:latch) { Latch.new }
8
+
7
9
  describe :wait_for do
8
10
  it %[should wake when an event is delivered] do
11
+
9
12
  th = Thread.new do
10
13
  subject.synchronize do
11
14
  logger.debug { "about to wait for created" }
15
+ latch.release
12
16
  subject.wait_for_created
13
17
  logger.debug { "woke up, created must have been delivered" }
14
18
  end
15
19
  true
16
20
  end
17
21
 
18
- th.run
19
- Thread.pass until th.status == 'sleep'
22
+ latch.await
20
23
 
21
24
  logger.debug { "th.status: #{th.status}" }
22
25
 
@@ -8,7 +8,7 @@ ZK.logger = LOG
8
8
  Zookeeper.logger = LOG
9
9
 
10
10
  class CloseInEventThread
11
- include ZookeeperConstants
11
+ include Zookeeper::Constants
12
12
 
13
13
  def initialize
14
14
  @zk = ZK.new
@@ -116,7 +116,7 @@ shared_examples_for 'client' do
116
116
  end
117
117
 
118
118
  it %[should return a Stat object] do
119
- @zk.stat(@missing_path).should be_kind_of(ZookeeperStat::Stat)
119
+ @zk.stat(@missing_path).should be_kind_of(Zookeeper::Stat)
120
120
  end
121
121
 
122
122
  it %[should return a stat that not exists?] do
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,14 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
3
 
4
- # $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
4
+ # not a constant so we don't pollute global namespace
5
+ release_ops_path = File.expand_path('../../releaseops/lib', __FILE__)
6
+
7
+ if File.exists?(release_ops_path)
8
+ require File.join(release_ops_path, 'releaseops')
9
+ ReleaseOps::SimpleCov.maybe_start
10
+ end
11
+
5
12
 
6
13
  Bundler.require(:development, :test)
7
14
 
@@ -6,7 +6,7 @@ class EventCatcher
6
6
 
7
7
  MEMBERS = [:created, :changed, :deleted, :child, :all]
8
8
 
9
- attr_reader :events
9
+ attr_reader :events, :mutex
10
10
 
11
11
  def initialize(*args)
12
12
  @mutex = Monitor.new
@@ -64,15 +64,15 @@ class EventCatcher
64
64
 
65
65
  # waits for an event group to not be empty (up to timeout sec)
66
66
  def wait_for_#{name}(timeout=5)
67
- cond(:#{name}).wait(timeout)
67
+ wait_for(:#{name}, timeout)
68
68
  end
69
69
 
70
- def wait_while_#{name}
71
- cond(:#{name}).wait_while { yield __send__(:#{name}) }
70
+ def wait_while_#{name}(&blk)
71
+ wait_while(:#{name}, &blk)
72
72
  end
73
73
 
74
- def wait_until_#{name}
75
- cond(:#{name}).wait_until { yield __send__(:#{name}) }
74
+ def wait_until_#{name}(&blk)
75
+ wait_until(:#{name}, &blk)
76
76
  end
77
77
  EOS
78
78
  end
@@ -0,0 +1,23 @@
1
+ # the much fabled 'latch' that tenderlove and nahi were on about
2
+
3
+ class Latch
4
+ def initialize(count = 1)
5
+ @count = count
6
+ @mutex = Monitor.new
7
+ @cond = @mutex.new_cond
8
+ end
9
+
10
+ def release
11
+ @mutex.synchronize {
12
+ @count -= 1 if @count > 0
13
+ @cond.broadcast if @count.zero?
14
+ }
15
+ end
16
+
17
+ def await
18
+ @mutex.synchronize {
19
+ @cond.wait_while { @count > 0 }
20
+ }
21
+ end
22
+ end
23
+
@@ -21,7 +21,7 @@ module SpecGlobalLogger
21
21
  # sets the log level to FATAL for the duration of the block
22
22
  def mute_logger
23
23
  orig_level, ZK.logger.level = ZK.logger.level, Logger::FATAL
24
- orig_zk_level, Zookeeper.debug_level = Zookeeper.debug_level, ZookeeperConstants::ZOO_LOG_LEVEL_ERROR
24
+ orig_zk_level, Zookeeper.debug_level = Zookeeper.debug_level, Zookeeper::Constants::ZOO_LOG_LEVEL_ERROR
25
25
  yield
26
26
  ensure
27
27
  ZK.logger.level = orig_level
data/spec/watch_spec.rb CHANGED
@@ -378,7 +378,7 @@ describe ZK do
378
378
  m.should_receive(:zk=).with(any())
379
379
  m.should_receive(:node_event?).and_return(false)
380
380
  m.should_receive(:state_event?).and_return(true)
381
- m.should_receive(:state).and_return(ZookeeperConstants::ZOO_CONNECTED_STATE)
381
+ m.should_receive(:state).and_return(Zookeeper::Constants::ZOO_CONNECTED_STATE)
382
382
  end
383
383
  end
384
384
  end # registered listeners
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  shared_examples_for 'session death' do
4
4
  def deliver_session_event_to(event_num, zk)
5
5
  # jeez, Zookeeper callbacks are so frustratingly stupid
6
- bogus_event = ZookeeperCallbacks::WatcherCallback.new
6
+ bogus_event = Zookeeper::Callbacks::WatcherCallback.new
7
7
  bogus_event.initialize_context(:type => -1, :state => event_num, :path => '', :context => 'bogustestevent')
8
8
  # XXX: this is bad because we're in the wrong thread, but we'll fix this after the next Zookeeper release
9
9
  zk.event_handler.process(bogus_event)
@@ -51,22 +51,22 @@ end # session death
51
51
  shared_examples_for 'locking and session death' do
52
52
  describe 'exceptional conditions' do
53
53
  describe 'ZOO_EXPIRED_SESSION_STATE' do
54
- let(:zoo_state) { ZookeeperConstants::ZOO_EXPIRED_SESSION_STATE }
55
- let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::SessionExpired }
54
+ let(:zoo_state) { Zookeeper::Constants::ZOO_EXPIRED_SESSION_STATE }
55
+ let(:zoo_error_class) { Zookeeper::Exceptions::SessionExpired }
56
56
 
57
57
  it_behaves_like 'session death'
58
58
  end
59
59
 
60
60
  describe 'ZOO_CONNECTING_STATE' do
61
- let(:zoo_state) { ZookeeperConstants::ZOO_CONNECTING_STATE }
62
- let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::NotConnected }
61
+ let(:zoo_state) { Zookeeper::Constants::ZOO_CONNECTING_STATE }
62
+ let(:zoo_error_class) { Zookeeper::Exceptions::NotConnected }
63
63
 
64
64
  it_behaves_like 'session death'
65
65
  end
66
66
 
67
67
  describe 'ZOO_CLOSED_STATE' do
68
- let(:zoo_state) { ZookeeperConstants::ZOO_CLOSED_STATE }
69
- let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::ConnectionClosed }
68
+ let(:zoo_state) { Zookeeper::Constants::ZOO_CLOSED_STATE }
69
+ let(:zoo_error_class) { Zookeeper::Exceptions::ConnectionClosed }
70
70
 
71
71
  it_behaves_like 'session death'
72
72
  end
@@ -23,42 +23,42 @@ describe 'ZK::Client#locker' do
23
23
  end
24
24
 
25
25
  it "should be able to acquire the lock if no one else is locking it" do
26
- @zk.locker(@path_to_lock).lock!.should be_true
26
+ @zk.locker(@path_to_lock).lock.should be_true
27
27
  end
28
28
 
29
29
  it "should not be able to acquire the lock if someone else is locking it" do
30
- @zk.locker(@path_to_lock).lock!.should be_true
31
- @zk2.locker(@path_to_lock).lock!.should be_false
30
+ @zk.locker(@path_to_lock).lock.should be_true
31
+ @zk2.locker(@path_to_lock).lock.should be_false
32
32
  end
33
33
 
34
34
  it "should be able to acquire the lock after the first one releases it" do
35
35
  lock1 = @zk.locker(@path_to_lock)
36
36
  lock2 = @zk2.locker(@path_to_lock)
37
37
 
38
- lock1.lock!.should be_true
39
- lock2.lock!.should be_false
40
- lock1.unlock!
41
- lock2.lock!.should be_true
38
+ lock1.lock.should be_true
39
+ lock2.lock.should be_false
40
+ lock1.unlock
41
+ lock2.lock.should be_true
42
42
  end
43
43
 
44
44
  it "should be able to acquire the lock if the first locker goes away" do
45
45
  lock1 = @zk.locker(@path_to_lock)
46
46
  lock2 = @zk2.locker(@path_to_lock)
47
47
 
48
- lock1.lock!.should be_true
49
- lock2.lock!.should be_false
48
+ lock1.lock.should be_true
49
+ lock2.lock.should be_false
50
50
  @zk.close!
51
- lock2.lock!.should be_true
51
+ lock2.lock.should be_true
52
52
  end
53
53
 
54
54
  it "should be able to handle multi part path locks" do
55
- @zk.locker("my/multi/part/path").lock!.should be_true
55
+ @zk.locker("my/multi/part/path").lock.should be_true
56
56
  end
57
57
 
58
58
  it "should blocking lock" do
59
59
  array = []
60
60
  first_lock = @zk.locker("mylock")
61
- first_lock.lock!.should be_true
61
+ first_lock.lock.should be_true
62
62
  array << :first_lock
63
63
 
64
64
  thread = Thread.new do
@@ -69,7 +69,7 @@ describe 'ZK::Client#locker' do
69
69
  end
70
70
 
71
71
  array.length.should == 1
72
- first_lock.unlock!
72
+ first_lock.unlock
73
73
  thread.join(10)
74
74
  array.length.should == 2
75
75
  end
@@ -77,26 +77,87 @@ describe 'ZK::Client#locker' do
77
77
  end
78
78
 
79
79
  shared_examples_for 'SharedLocker' do
80
- before do
81
- @shared_locker = ZK::Locker.shared_locker(zk, path)
82
- @shared_locker2 = ZK::Locker.shared_locker(zk2, path)
80
+ let(:shared_locker) { ZK::Locker.shared_locker(zk, path) }
81
+ let(:shared_locker2) { ZK::Locker.shared_locker(zk2, path) }
82
+
83
+ describe :assert! do
84
+ it %[should raise LockAssertionFailedError if its connection is no longer connected?] do
85
+ zk.close!
86
+ lambda { shared_locker.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
87
+ end
88
+
89
+ it %[should raise LockAssertionFailedError if locked? is false] do
90
+ shared_locker.should_not be_locked
91
+ lambda { shared_locker.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
92
+ end
93
+
94
+ it %[should raise LockAssertionFailedError lock_path does not exist] do
95
+ shared_locker.lock
96
+ lambda { shared_locker.assert! }.should_not raise_error
97
+
98
+ zk.delete(shared_locker.lock_path)
99
+ lambda { shared_locker.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
100
+ end
101
+
102
+ it %[should raise LockAssertionFailedError if there is an exclusive lock with a number lower than ours] do
103
+ # this should *really* never happen
104
+ shared_locker.lock.should be_true
105
+ shl_path = shared_locker.lock_path
106
+
107
+ shared_locker2.lock.should be_true
108
+
109
+ shared_locker.unlock.should be_true
110
+ shared_locker.should_not be_locked
111
+
112
+ zk.exists?(shl_path).should be_false
113
+
114
+ shared_locker2.lock_path.should_not == shl_path
115
+
116
+ # convert the first shared lock path into a exclusive one
117
+
118
+ exl_path = shl_path.sub(%r%/sh(\d+)\Z%, '/ex\1')
119
+
120
+ zk.create(exl_path, :ephemeral => true)
121
+
122
+ lambda { shared_locker2.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
123
+ end
124
+ end
125
+
126
+ describe :acquirable? do
127
+ describe %[with default options] do
128
+ it %[should work if the lock root doesn't exist] do
129
+ zk.rm_rf(ZK::Locker.default_root_lock_node)
130
+ shared_locker.should be_acquirable
131
+ end
132
+
133
+ it %[should check local state of lockedness] do
134
+ shared_locker.lock.should be_true
135
+ shared_locker.should be_acquirable
136
+ end
137
+
138
+ it %[should check if any participants would prevent us from acquiring the lock] do
139
+ ex_lock = ZK::Locker.exclusive_locker(zk, path)
140
+ ex_lock.lock.should be_true
141
+ shared_locker.should_not be_acquirable
142
+ end
143
+ end
83
144
  end
84
145
 
85
- describe :lock! do
146
+ describe :lock do
86
147
  describe 'non-blocking success' do
87
148
  before do
88
- @rval = @shared_locker.lock!
89
- @rval2 = @shared_locker2.lock!
149
+ @rval = shared_locker.lock
150
+ @rval2 = shared_locker2.lock
90
151
  end
91
152
 
92
153
  it %[should acquire the first lock] do
93
154
  @rval.should be_true
94
- @shared_locker.should be_locked
155
+ shared_locker.should be_locked
95
156
  end
96
157
 
97
158
  it %[should acquire the second lock] do
98
159
  @rval2.should be_true
99
- @shared_locker2.should be_locked
160
+ shared_locker2.should be_locked
100
161
  end
101
162
  end
102
163
 
@@ -104,11 +165,7 @@ shared_examples_for 'SharedLocker' do
104
165
  before do
105
166
  zk.mkdir_p(root_lock_path)
106
167
  @write_lock_path = zk.create("#{root_lock_path}/#{ZK::Locker::EXCLUSIVE_LOCK_PREFIX}", '', :mode => :ephemeral_sequential)
107
- @rval = @shared_locker.lock!
108
- end
109
-
110
- after do
111
- zk.rm_rf('/_zklocking')
168
+ @rval = shared_locker.lock
112
169
  end
113
170
 
114
171
  it %[should return false] do
@@ -116,7 +173,7 @@ shared_examples_for 'SharedLocker' do
116
173
  end
117
174
 
118
175
  it %[should not be locked] do
119
- @shared_locker.should_not be_locked
176
+ shared_locker.should_not be_locked
120
177
  end
121
178
  end
122
179
 
@@ -130,15 +187,15 @@ shared_examples_for 'SharedLocker' do
130
187
  it %[should acquire the lock after the write lock is released] do
131
188
  ary = []
132
189
 
133
- @shared_locker.lock!.should be_false
190
+ shared_locker.lock.should be_false
134
191
 
135
192
  th = Thread.new do
136
- @shared_locker.lock!(true)
193
+ shared_locker.lock(true)
137
194
  ary << :locked
138
195
  end
139
196
 
140
197
  ary.should be_empty
141
- @shared_locker.should_not be_locked
198
+ shared_locker.should_not be_locked
142
199
 
143
200
  zk.delete(@write_lock_path)
144
201
 
@@ -147,23 +204,82 @@ shared_examples_for 'SharedLocker' do
147
204
  wait_until(2) { !ary.empty? }
148
205
  ary.length.should == 1
149
206
 
150
- @shared_locker.should be_locked
207
+ shared_locker.should be_locked
151
208
  end
152
209
  end
153
210
  end
154
211
  end # SharedLocker
155
212
 
156
213
  shared_examples_for 'ExclusiveLocker' do
157
- before do
158
- @ex_locker = ZK::Locker.exclusive_locker(zk, path)
159
- @ex_locker2 = ZK::Locker.exclusive_locker(zk2, path)
214
+ let(:ex_locker) { ZK::Locker.exclusive_locker(zk, path) }
215
+ let(:ex_locker2) { ZK::Locker.exclusive_locker(zk2, path) }
216
+
217
+ describe :assert! do
218
+ it %[should raise LockAssertionFailedError if its connection is no longer connected?] do
219
+ zk.close!
220
+ lambda { ex_locker.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
221
+ end
222
+
223
+ it %[should raise LockAssertionFailedError if locked? is false] do
224
+ ex_locker.should_not be_locked
225
+ lambda { ex_locker.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
226
+ end
227
+
228
+ it %[should raise LockAssertionFailedError lock_path does not exist] do
229
+ ex_locker.lock
230
+ lambda { ex_locker.assert! }.should_not raise_error
231
+
232
+ zk.delete(ex_locker.lock_path)
233
+ lambda { ex_locker.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
234
+ end
235
+
236
+ it %[should raise LockAssertionFailedError if there is an exclusive lock with a number lower than ours] do
237
+ # this should *really* never happen
238
+ ex_locker.lock.should be_true
239
+ exl_path = ex_locker.lock_path
240
+
241
+ th = Thread.new do
242
+ ex_locker2.lock(true)
243
+ end
244
+
245
+ wait_until { th.status == 'sleep' }
246
+
247
+ ex_locker.unlock.should be_true
248
+ ex_locker.should_not be_locked
249
+ zk.exists?(exl_path).should be_false
250
+
251
+ th.join(5).should == th
252
+
253
+ ex_locker2.lock_path.should_not == exl_path
254
+
255
+ zk.create(exl_path, :ephemeral => true)
256
+
257
+ lambda { ex_locker2.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
258
+ end
259
+ end
260
+
261
+ describe :acquirable? do
262
+ it %[should work if the lock root doesn't exist] do
263
+ zk.rm_rf(ZK::Locker.default_root_lock_node)
264
+ ex_locker.should be_acquirable
265
+ end
266
+
267
+ it %[should check local state of lockedness] do
268
+ ex_locker.lock.should be_true
269
+ ex_locker.should be_acquirable
270
+ end
271
+
272
+ it %[should check if any participants would prevent us from acquiring the lock] do
273
+ ex_locker.lock.should be_true
274
+ ex_locker2.should_not be_acquirable
275
+ end
160
276
  end
161
277
 
162
- describe :lock! do
278
+ describe :lock do
163
279
  describe 'non-blocking' do
164
280
  before do
165
- @rval = @ex_locker.lock!
166
- @rval2 = @ex_locker2.lock!
281
+ @rval = ex_locker.lock
282
+ @rval2 = ex_locker2.lock
167
283
  end
168
284
 
169
285
  it %[should acquire the first lock] do
@@ -175,8 +291,8 @@ shared_examples_for 'ExclusiveLocker' do
175
291
  end
176
292
 
177
293
  it %[should acquire the second lock after the first lock is released] do
178
- @ex_locker.unlock!.should be_true
179
- @ex_locker2.lock!.should be_true
294
+ ex_locker.unlock.should be_true
295
+ ex_locker2.lock.should be_true
180
296
  end
181
297
  end
182
298
 
@@ -189,41 +305,39 @@ shared_examples_for 'ExclusiveLocker' do
189
305
  it %[should block waiting for the lock] do
190
306
  ary = []
191
307
 
192
- @ex_locker.lock!.should be_false
308
+ ex_locker.lock.should be_false
193
309
 
194
310
  th = Thread.new do
195
- @ex_locker.lock!(true)
311
+ ex_locker.lock(true)
196
312
  ary << :locked
197
313
  end
198
314
 
199
315
  th.run
200
316
 
201
317
  ary.should be_empty
202
- @ex_locker.should_not be_locked
318
+ ex_locker.should_not be_locked
203
319
 
204
320
  zk.delete(@read_lock_path)
205
321
 
206
322
  th.join(2)
207
323
 
208
324
  ary.length.should == 1
209
- @ex_locker.should be_locked
325
+ ex_locker.should be_locked
210
326
  end
211
327
  end
212
-
213
328
  end
214
329
  end # ExclusiveLocker
215
330
 
216
331
  shared_examples_for 'shared-exclusive interaction' do
217
332
  before do
218
- zk.rm_rf('/_zklocking')
219
333
  @sh_lock = ZK::Locker.shared_locker(zk, path)
220
334
  @ex_lock = ZK::Locker.exclusive_locker(zk2, path)
221
335
  end
222
336
 
223
337
  describe 'shared lock acquired first' do
224
338
  it %[should block exclusive locks from acquiring until released] do
225
- @sh_lock.lock!.should be_true
226
- @ex_lock.lock!.should be_false
339
+ @sh_lock.lock.should be_true
340
+ @ex_lock.lock.should be_false
227
341
 
228
342
  mutex = Monitor.new
229
343
  cond = mutex.new_cond
@@ -242,7 +356,7 @@ shared_examples_for 'shared-exclusive interaction' do
242
356
 
243
357
  mutex.synchronize do
244
358
  logger.debug { "unlocking the shared lock" }
245
- @sh_lock.unlock!.should be_true
359
+ @sh_lock.unlock.should be_true
246
360
  cond.wait_until { th[:got_lock] } # make sure they actually received the lock (avoid race)
247
361
  th[:got_lock].should be_true
248
362
  logger.debug { "ok, they got the lock" }
@@ -258,8 +372,8 @@ shared_examples_for 'shared-exclusive interaction' do
258
372
 
259
373
  describe 'exclusive lock acquired first' do
260
374
  it %[should block shared lock from acquiring until released] do
261
- @ex_lock.lock!.should be_true
262
- @sh_lock.lock!.should be_false
375
+ @ex_lock.lock.should be_true
376
+ @sh_lock.lock.should be_false
263
377
 
264
378
  mutex = Monitor.new
265
379
  cond = mutex.new_cond
@@ -278,7 +392,7 @@ shared_examples_for 'shared-exclusive interaction' do
278
392
 
279
393
  mutex.synchronize do
280
394
  logger.debug { "unlocking the shared lock" }
281
- @ex_lock.unlock!.should be_true
395
+ @ex_lock.unlock.should be_true
282
396
  cond.wait_until { th[:got_lock] } # make sure they actually received the lock (avoid race)
283
397
  th[:got_lock].should be_true
284
398
  logger.debug { "ok, they got the lock" }
@@ -301,16 +415,16 @@ shared_examples_for 'shared-exclusive interaction' do
301
415
  it %[should act something like a queue] do
302
416
  @array = []
303
417
 
304
- @sh_lock.lock!.should be_true
418
+ @sh_lock.lock.should be_true
305
419
  @sh_lock.should be_locked
306
420
 
307
421
  ex_th = Thread.new do
308
422
  begin
309
- @ex_lock.lock!(true) # blocking lock
423
+ @ex_lock.lock(true) # blocking lock
310
424
  Thread.current[:got_lock] = true
311
425
  @array << :ex_lock
312
426
  ensure
313
- @ex_lock.unlock!
427
+ @ex_lock.unlock
314
428
  end
315
429
  end
316
430
 
@@ -320,22 +434,22 @@ shared_examples_for 'shared-exclusive interaction' do
320
434
 
321
435
  # this is the important one, does the second shared lock get blocked by
322
436
  # the exclusive lock
323
- @sh_lock2.lock!.should_not be_true
437
+ @sh_lock2.lock.should_not be_true
324
438
 
325
439
  sh2_th = Thread.new do
326
440
  begin
327
- @sh_lock2.lock!(true)
441
+ @sh_lock2.lock(true)
328
442
  Thread.current[:got_lock] = true
329
443
  @array << :sh_lock2
330
444
  ensure
331
- @sh_lock2.unlock!
445
+ @sh_lock2.unlock
332
446
  end
333
447
  end
334
448
 
335
449
  sh2_th.join_until { @sh_lock2.waiting? }
336
450
  @sh_lock2.should be_waiting
337
451
 
338
- @sh_lock.unlock!.should be_true
452
+ @sh_lock.unlock.should be_true
339
453
 
340
454
  ex_th.join_until { ex_th[:got_lock] }
341
455
  ex_th[:got_lock].should be_true
@@ -349,7 +463,6 @@ shared_examples_for 'shared-exclusive interaction' do
349
463
  end
350
464
  end # shared-exclusive interaction
351
465
 
352
-
353
466
  describe ZK::Locker do
354
467
  include_context 'connection opts'
355
468
 
@@ -360,15 +473,17 @@ describe ZK::Locker do
360
473
  let(:connections) { [zk, zk2, zk3] }
361
474
 
362
475
  let(:path) { "shlock" }
363
- let(:root_lock_path) { "/_zklocking/#{path}" }
476
+ let(:root_lock_path) { "#{ZK::Locker.default_root_lock_node}/#{path}" }
364
477
 
365
478
  before do
366
479
  wait_until{ connections.all?(&:connected?) }
480
+ zk.rm_rf(ZK::Locker.default_root_lock_node)
367
481
  end
368
482
 
369
483
  after do
370
484
  connections.each { |c| c.close! }
371
485
  wait_until { !connections.any?(&:connected?) }
486
+ ZK.open("localhost:#{ZK.test_port}") { |z| z.rm_rf(ZK::Locker.default_root_lock_node) }
372
487
  end
373
488
 
374
489
  it_should_behave_like 'SharedLocker'
@@ -376,7 +491,7 @@ describe ZK::Locker do
376
491
  it_should_behave_like 'shared-exclusive interaction'
377
492
  end # ZK::Locker
378
493
 
379
- describe "ZK::Locker chrooted" do
494
+ describe ZK::Locker, :chrooted => true do
380
495
  include_context 'connection opts'
381
496
 
382
497
  let(:chroot_path) { '/_zk_chroot_' }
@@ -390,7 +505,7 @@ describe "ZK::Locker chrooted" do
390
505
  let(:connections) { [zk, zk2, zk3] }
391
506
 
392
507
  let(:path) { "shlock" }
393
- let(:root_lock_path) { "/_zklocking/#{path}" }
508
+ let(:root_lock_path) { "#{ZK::Locker.default_root_lock_node}/#{path}" }
394
509
 
395
510
  before do
396
511
  ZK.open("localhost:#{ZK.test_port}") do |zk|