zk 1.1.0 → 1.1.1
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.
- data/.travis.yml +5 -0
- data/Gemfile +2 -3
- data/README.markdown +7 -7
- data/RELEASES.markdown +18 -0
- data/Rakefile +6 -2
- data/lib/zk/client/conveniences.rb +47 -37
- data/lib/zk/client/threaded.rb +15 -4
- data/lib/zk/client/unixisms.rb +23 -14
- data/lib/zk/client.rb +1 -0
- data/lib/zk/event_handler.rb +1 -1
- data/lib/zk/exceptions.rb +24 -21
- data/lib/zk/locker/exclusive_locker.rb +75 -0
- data/lib/zk/locker/locker_base.rb +161 -0
- data/lib/zk/locker/shared_locker.rb +101 -0
- data/lib/zk/locker.rb +109 -240
- data/lib/zk/message_queue.rb +16 -6
- data/lib/zk/pool.rb +5 -4
- data/lib/zk/version.rb +1 -1
- data/spec/zk/locker_spec.rb +46 -102
- data/spec/zk/pool_spec.rb +1 -1
- metadata +7 -4
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
|
@@ -6,7 +6,7 @@ gem 'rake', :group => [:development, :test]
|
|
|
6
6
|
gem 'pry', :group => [:development]
|
|
7
7
|
|
|
8
8
|
group :docs do
|
|
9
|
-
gem 'yard', '~> 0.
|
|
9
|
+
gem 'yard', '~> 0.8.0'
|
|
10
10
|
|
|
11
11
|
platform :mri_19 do
|
|
12
12
|
gem 'redcarpet'
|
|
@@ -16,8 +16,7 @@ end
|
|
|
16
16
|
group :test do
|
|
17
17
|
gem 'rspec', '~> 2.8.0'
|
|
18
18
|
gem 'flexmock', '~> 0.8.10'
|
|
19
|
-
|
|
20
|
-
gem 'zk-server', '~> 0.9.1'
|
|
19
|
+
gem 'zk-server', '~> 1.0.1'
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
# Specify your gem's dependencies in zk.gemspec
|
data/README.markdown
CHANGED
|
@@ -18,6 +18,13 @@ Development is sponsored by [Snapfish][] and has been generously released to the
|
|
|
18
18
|
[MIT]: http://www.gnu.org/licenses/license-list.html#Expat "MIT (Expat) License"
|
|
19
19
|
[Snapfish]: http://www.snapfish.com/ "Snapfish"
|
|
20
20
|
|
|
21
|
+
## Contacting the author
|
|
22
|
+
|
|
23
|
+
* I'm usually hanging out in IRC on freenode.net in the BRAND NEW #zk-gem channel.
|
|
24
|
+
* if you really want to, you can also reach me via twitter [@slyphon][]
|
|
25
|
+
|
|
26
|
+
[@slyphon]: https://twitter.com/#!/slyphon
|
|
27
|
+
|
|
21
28
|
## New in 1.1 !! ##
|
|
22
29
|
|
|
23
30
|
* NEW! Thread-per-Callback event delivery model! [Read all about it!](https://github.com/slyphon/zk/wiki/EventDeliveryModel). Provides a simple, sane way to increase the concurrency in your ZK-based app while maintaining the ordering guarantees ZooKeeper makes. Each callback can perform whatever work it needs to without blocking other callbacks from receiving events. Inspired by [Celluloid's](https://github.com/celluloid/celluloid) actor model.
|
|
@@ -161,11 +168,4 @@ ZK strives to be a complete, correct, and convenient way of interacting with Zoo
|
|
|
161
168
|
[szk-jar-gem]: https://rubygems.org/gems/slyphon-zookeeper_jar
|
|
162
169
|
[szk-jar-repo]: https://github.com/slyphon/zookeeper_jar
|
|
163
170
|
|
|
164
|
-
## Contacting the author
|
|
165
|
-
|
|
166
|
-
* Send me a github message (slyphon)
|
|
167
|
-
* I'm usually hanging out in IRC on freenode.net in #ruby-lang and in #zookeeper
|
|
168
|
-
* if you really want to, you can also reach me via twitter [@slyphon][]
|
|
169
|
-
|
|
170
|
-
[@slyphon]: https://twitter.com/#!/slyphon
|
|
171
171
|
|
data/RELEASES.markdown
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
This file notes feature differences and bugfixes contained between releases.
|
|
2
|
+
### v1.1.1 ###
|
|
3
|
+
|
|
4
|
+
* Documentation for Locker and ilk
|
|
5
|
+
|
|
6
|
+
* Documentation cleanup
|
|
7
|
+
|
|
8
|
+
* Fixes for Locker tests so that we can run specs against all supported ruby implementations on travis (relies on in-process zookeeper server in the zk-server-1.0.1 gem)
|
|
9
|
+
|
|
10
|
+
* Support for 1.8.7 will be continued
|
|
11
|
+
|
|
12
|
+
## v1.1.0 ##
|
|
13
|
+
|
|
14
|
+
(forgot to put this here, put it in the readme though)
|
|
15
|
+
|
|
16
|
+
* NEW! Thread-per-Callback event delivery model! [Read all about it!](https://github.com/slyphon/zk/wiki/EventDeliveryModel). Provides a simple, sane way to increase the concurrency in your ZK-based app while maintaining the ordering guarantees ZooKeeper makes. Each callback can perform whatever work it needs to without blocking other callbacks from receiving events. Inspired by [Celluloid's](https://github.com/celluloid/celluloid) actor model.
|
|
17
|
+
|
|
18
|
+
* Use the [zk-server](https://github.com/slyphon/zk-server) gem to run a standalone ZooKeeper server for tests (`rake SPAWN_ZOOKEEPER=1`). Makes live-fire testing of any project that uses ZK easy to run anywhere!
|
|
19
|
+
|
|
2
20
|
|
|
3
21
|
### v1.0.0 ###
|
|
4
22
|
|
data/Rakefile
CHANGED
|
@@ -4,7 +4,7 @@ gemset_name = 'zk'
|
|
|
4
4
|
|
|
5
5
|
GEMSPEC_NAME = 'zk.gemspec'
|
|
6
6
|
|
|
7
|
-
%w[1.8.7 1.9.2 jruby rbx 1.9.3].each do |ns_name|
|
|
7
|
+
%w[1.8.7 1.9.2 jruby rbx ree 1.9.3].each do |ns_name|
|
|
8
8
|
rvm_ruby = (ns_name == 'rbx') ? "rbx-2.0.testing" : ns_name
|
|
9
9
|
|
|
10
10
|
ruby_with_gemset = "#{rvm_ruby}@#{gemset_name}"
|
|
@@ -59,6 +59,10 @@ namespace :yard do
|
|
|
59
59
|
task :server => :clean do
|
|
60
60
|
sh "yard server --reload"
|
|
61
61
|
end
|
|
62
|
+
|
|
63
|
+
task :gems do
|
|
64
|
+
sh 'yard server --gems --port=8809'
|
|
65
|
+
end
|
|
62
66
|
end
|
|
63
67
|
|
|
64
68
|
task :clean => 'yard:clean'
|
|
@@ -70,7 +74,7 @@ namespace :spec do
|
|
|
70
74
|
require 'rspec/core/rake_task'
|
|
71
75
|
|
|
72
76
|
RSpec::Core::RakeTask.new('spec:runner') do |t|
|
|
73
|
-
t.rspec_opts = '-f d'
|
|
77
|
+
t.rspec_opts = '-f d' if ENV['TRAVIS']
|
|
74
78
|
end
|
|
75
79
|
end
|
|
76
80
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
module ZK
|
|
2
2
|
module Client
|
|
3
|
-
#
|
|
3
|
+
# Convenience methods for creating instances of the cluster coordination
|
|
4
|
+
# objects ZK provides, using the current connection.
|
|
5
|
+
#
|
|
6
|
+
# Mixed into {ZK::Client::Threaded}
|
|
4
7
|
#
|
|
5
|
-
# convenience methods for dealing with zookeeper (rm -rf, mkdir -p, etc)
|
|
6
8
|
module Conveniences
|
|
7
9
|
# Queue an operation to be run on an internal threadpool. You may either
|
|
8
10
|
# provide an object that responds_to?(:call) or pass a block. There is no
|
|
@@ -18,6 +20,7 @@ module ZK
|
|
|
18
20
|
# @yield [] the block that should be run in the threadpool, if `callable`
|
|
19
21
|
# isn't given
|
|
20
22
|
#
|
|
23
|
+
# @private
|
|
21
24
|
def defer(callable=nil, &block)
|
|
22
25
|
@threadpool.defer(callable, &block)
|
|
23
26
|
end
|
|
@@ -33,55 +36,53 @@ module ZK
|
|
|
33
36
|
false
|
|
34
37
|
end
|
|
35
38
|
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# @see ZK::Locker::ExclusiveLocker
|
|
39
|
-
#
|
|
40
|
-
# returns a ZK::Locker::ExclusiveLocker instance using this Client and provided
|
|
41
|
-
# lock name
|
|
39
|
+
# Creates a new locker based on the name you provide, using this client
|
|
40
|
+
# as the connection.
|
|
42
41
|
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
42
|
+
# @param name [String] the name of the lock you wish to use. see
|
|
43
|
+
# {ZK::Locker} for a description of how the name is used to generate a
|
|
44
|
+
# key.
|
|
45
45
|
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
# zk.locker("blah")
|
|
49
|
-
# # => #<ZK::Locker::ExclusiveLocker:0x102034cf8 ...>
|
|
46
|
+
# @return [Locker::ExclusiveLocker] instance using this Client and
|
|
47
|
+
# provided lock name.
|
|
50
48
|
#
|
|
51
49
|
def locker(name)
|
|
52
50
|
Locker.exclusive_locker(self, name)
|
|
53
51
|
end
|
|
52
|
+
alias exclusive_locker locker
|
|
54
53
|
|
|
55
54
|
# create a new shared locking instance based on the name given
|
|
56
55
|
#
|
|
57
|
-
#
|
|
58
|
-
# lock name
|
|
59
|
-
#
|
|
60
|
-
# ==== Arguments
|
|
61
|
-
# * <tt>name</tt> name of the lock you wish to use
|
|
62
|
-
#
|
|
63
|
-
# ==== Examples
|
|
56
|
+
# @param name (see #locker)
|
|
64
57
|
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
58
|
+
# @return [Locker::SharedLocker] instance using this Client and provided
|
|
59
|
+
# lock name.
|
|
67
60
|
#
|
|
68
61
|
def shared_locker(name)
|
|
69
62
|
Locker.shared_locker(self, name)
|
|
70
63
|
end
|
|
71
64
|
|
|
72
65
|
# Convenience method for acquiring a lock then executing a code block. This
|
|
73
|
-
# will block the caller until the lock is acquired
|
|
66
|
+
# will block the caller until the lock is acquired, and release the lock
|
|
67
|
+
# when the block is exited.
|
|
74
68
|
#
|
|
75
|
-
#
|
|
76
|
-
# * <tt>name</tt>: the name of the lock to use
|
|
77
|
-
# * <tt>:mode</tt>: either :shared or :exclusive, defaults to :exclusive
|
|
69
|
+
# @param name (see #locker)
|
|
78
70
|
#
|
|
79
|
-
#
|
|
71
|
+
# @option opts [:shared,:exclusive] :mode (:exclusive) the type of lock
|
|
72
|
+
# to create and then call with_lock on
|
|
73
|
+
#
|
|
74
|
+
# @return the return value of the given block
|
|
75
|
+
#
|
|
76
|
+
# @yield calls the block once the lock has been acquired
|
|
77
|
+
#
|
|
78
|
+
# @example
|
|
80
79
|
#
|
|
81
80
|
# zk.with_lock('foo') do
|
|
82
81
|
# # this code is executed while holding the lock
|
|
83
82
|
# end
|
|
84
83
|
#
|
|
84
|
+
# @raise [ArgumentError] if `opts[:mode]` is not one of the expected values
|
|
85
|
+
#
|
|
85
86
|
def with_lock(name, opts={}, &b)
|
|
86
87
|
mode = opts[:mode] || :exclusive
|
|
87
88
|
|
|
@@ -94,29 +95,38 @@ module ZK
|
|
|
94
95
|
end
|
|
95
96
|
end
|
|
96
97
|
|
|
97
|
-
#
|
|
98
|
-
#
|
|
98
|
+
# Constructs an {Election::Candidate} object using self as the connection
|
|
99
|
+
#
|
|
100
|
+
# @param [String] name the name of the election to participate in
|
|
101
|
+
# @param [String] data the data we will write to the leadership node if/when we win
|
|
99
102
|
#
|
|
103
|
+
# @return [Election::Candidate] the candidate instance using self as a connection
|
|
100
104
|
def election_candidate(name, data, opts={})
|
|
101
105
|
opts = opts.merge(:data => data)
|
|
102
106
|
ZK::Election::Candidate.new(self, name, opts)
|
|
103
107
|
end
|
|
104
108
|
|
|
105
|
-
#
|
|
106
|
-
#
|
|
109
|
+
# Constructs an {Election::Observer} object using self as the connection
|
|
110
|
+
#
|
|
111
|
+
# @param name (see #election_candidate)
|
|
107
112
|
#
|
|
113
|
+
# @return [Election::Observer] the candidate instance using self as a connection
|
|
108
114
|
def election_observer(name, opts={})
|
|
109
115
|
ZK::Election::Observer.new(self, name, opts)
|
|
110
116
|
end
|
|
111
117
|
|
|
112
|
-
# creates a new message queue of name
|
|
118
|
+
# creates a new message queue of name `name`
|
|
119
|
+
#
|
|
120
|
+
# @note The message queue has some scalability limitations. For
|
|
121
|
+
# heavy-duty message processing, the author recommends investigating
|
|
122
|
+
# a purpose-built solution.
|
|
113
123
|
#
|
|
114
|
-
#
|
|
124
|
+
# @return [MessageQueue] the new instance using self as its
|
|
125
|
+
# client
|
|
115
126
|
#
|
|
116
|
-
#
|
|
117
|
-
# * <tt>name</tt> the name of the queue
|
|
127
|
+
# @param [String] name the name of the queue
|
|
118
128
|
#
|
|
119
|
-
#
|
|
129
|
+
# @example
|
|
120
130
|
#
|
|
121
131
|
# zk.queue("blah").publish({:some_data => "that is yaml serializable"})
|
|
122
132
|
#
|
data/lib/zk/client/threaded.rb
CHANGED
|
@@ -3,7 +3,7 @@ module ZK
|
|
|
3
3
|
# This is the default client that ZK will use. In the zk-eventmachine gem,
|
|
4
4
|
# there is an Evented client.
|
|
5
5
|
#
|
|
6
|
-
# If you want to register `on_*` callbacks (see ZK::Client::StateMixin)
|
|
6
|
+
# If you want to register `on_*` callbacks (see {ZK::Client::StateMixin})
|
|
7
7
|
# then you should pass a block, which will be called before the
|
|
8
8
|
# connection is set up (this way you can get the `on_connected` event). See
|
|
9
9
|
# the 'Register on_connected callback' example.
|
|
@@ -26,7 +26,7 @@ module ZK
|
|
|
26
26
|
# # the nice thing about this pattern is that in the case of a call to #reopen
|
|
27
27
|
# # all your watches will be re-established
|
|
28
28
|
#
|
|
29
|
-
# ZK::Client::Threaded.new('
|
|
29
|
+
# ZK::Client::Threaded.new('localhost:2181') do |zk|
|
|
30
30
|
# # do not do anything in here except register callbacks
|
|
31
31
|
#
|
|
32
32
|
# zk.on_connected do |event|
|
|
@@ -45,6 +45,12 @@ module ZK
|
|
|
45
45
|
|
|
46
46
|
# Construct a new threaded client.
|
|
47
47
|
#
|
|
48
|
+
# Pay close attention to the `:threaded` option, and have a look at the
|
|
49
|
+
# [EventDeliveryModel](https://github.com/slyphon/zk/wiki/EventDeliveryModel)
|
|
50
|
+
# page in the wiki for a discussion of the relative advantages and
|
|
51
|
+
# disadvantages of the choices available. The default is safe, but the
|
|
52
|
+
# alternative will likely provide better performance.
|
|
53
|
+
#
|
|
48
54
|
# @note The `:timeout` argument here is *not* the session_timeout for the
|
|
49
55
|
# connection. rather it is the amount of time we wait for the connection
|
|
50
56
|
# to be established. The session timeout exchanged with the server is
|
|
@@ -68,12 +74,15 @@ module ZK
|
|
|
68
74
|
# [this wiki article](https://github.com/slyphon/zk/wiki/EventDeliveryModel) for more
|
|
69
75
|
# information and a demonstration.
|
|
70
76
|
#
|
|
71
|
-
# @param
|
|
77
|
+
# @param host (see Base#initialize)
|
|
72
78
|
#
|
|
73
79
|
# @option opts [true,false] :reconnect (true) if true, we will register
|
|
74
80
|
# the equivalent of `on_session_expired { zk.reopen }` so that in the
|
|
75
81
|
# case of an expired session, we will keep trying to reestablish the
|
|
76
|
-
# connection.
|
|
82
|
+
# connection. You *almost definately* want to leave this at the default.
|
|
83
|
+
# The only reason not to is if you already have a handler registered
|
|
84
|
+
# that does something application specific, and you want to avoid a
|
|
85
|
+
# conflict.
|
|
77
86
|
#
|
|
78
87
|
# @option opts [:single,:per_callback] :thread (:single) choose your event
|
|
79
88
|
# delivery model:
|
|
@@ -112,6 +121,8 @@ module ZK
|
|
|
112
121
|
# operations with the client as you will get a NoMethodError (the
|
|
113
122
|
# underlying connection is nil).
|
|
114
123
|
#
|
|
124
|
+
# @return [Threaded] a new client instance
|
|
125
|
+
#
|
|
115
126
|
# @see Base#initialize
|
|
116
127
|
def initialize(host, opts={}, &b)
|
|
117
128
|
super(host, opts)
|
data/lib/zk/client/unixisms.rb
CHANGED
|
@@ -2,6 +2,7 @@ module ZK
|
|
|
2
2
|
module Client
|
|
3
3
|
module Unixisms
|
|
4
4
|
include ZookeeperConstants
|
|
5
|
+
include Exceptions
|
|
5
6
|
|
|
6
7
|
# Creates all parent paths and 'path' in zookeeper as persistent nodes with
|
|
7
8
|
# zero data.
|
|
@@ -21,12 +22,12 @@ module ZK
|
|
|
21
22
|
# this could get expensive w/ psychotically long paths
|
|
22
23
|
|
|
23
24
|
create(path, '', :mode => :persistent)
|
|
24
|
-
rescue
|
|
25
|
+
rescue NodeExists
|
|
25
26
|
return
|
|
26
|
-
rescue
|
|
27
|
+
rescue NoNode
|
|
27
28
|
if File.dirname(path) == '/'
|
|
28
29
|
# ok, we're screwed, blow up
|
|
29
|
-
raise
|
|
30
|
+
raise NonExistentRootError, "could not create '/', are you chrooted into a non-existent path?", caller
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
mkdir_p(File.dirname(path))
|
|
@@ -43,7 +44,7 @@ module ZK
|
|
|
43
44
|
|
|
44
45
|
delete(path)
|
|
45
46
|
nil
|
|
46
|
-
rescue
|
|
47
|
+
rescue NoNode
|
|
47
48
|
end
|
|
48
49
|
end
|
|
49
50
|
end
|
|
@@ -103,19 +104,27 @@ module ZK
|
|
|
103
104
|
ZK::Find.find(self, *paths, &block)
|
|
104
105
|
end
|
|
105
106
|
|
|
106
|
-
# Will _safely_ block the caller until `abs_node_path` has been removed
|
|
107
|
-
#
|
|
108
|
-
# if a session event occurs that would ensure the event would
|
|
109
|
-
#
|
|
110
|
-
# from the event distribution thread (which would cause a deadlock).
|
|
111
|
-
#
|
|
112
|
-
# @note this is dangerous to use in callbacks! there is only one
|
|
113
|
-
# event-delivery thread, so if you use this method in a callback or
|
|
114
|
-
# watcher, you *will* deadlock!
|
|
107
|
+
# Will _safely_ block the caller until `abs_node_path` has been removed
|
|
108
|
+
# (this is trickier than it appears at first). This method will wake the
|
|
109
|
+
# caller if a session event occurs that would ensure the event would
|
|
110
|
+
# never be delivered.
|
|
115
111
|
#
|
|
116
112
|
# @raise [Exceptions::InterruptedSession] If a session event occurs while we're
|
|
117
113
|
# blocked waiting for the node to be deleted, an exception that
|
|
118
|
-
# mixes in the InterruptedSession module will be raised
|
|
114
|
+
# mixes in the InterruptedSession module will be raised, so for convenience,
|
|
115
|
+
# users can just rescue {InterruptedSession}.
|
|
116
|
+
#
|
|
117
|
+
# @raise [ZookeeperExceptions::ZookeeperException::SessionExpired] raised
|
|
118
|
+
# when we receive `ZOO_EXPIRED_SESSION_STATE` while blocking waiting for
|
|
119
|
+
# a deleted event. Includes the {InterruptedSession} module.
|
|
120
|
+
#
|
|
121
|
+
# @raise [ZookeeperExceptions::ZookeeperException::NotConnected] raised
|
|
122
|
+
# when we receive `ZOO_CONNECTING_STATE` while blocking waiting for
|
|
123
|
+
# a deleted event. Includes the {InterruptedSession} module.
|
|
124
|
+
#
|
|
125
|
+
# @raise [ZookeeperExceptions::ZookeeperException::ConnectionClosed] raised
|
|
126
|
+
# when we receive `ZOO_CLOSED_STATE` while blocking waiting for
|
|
127
|
+
# a deleted event. Includes the {InterruptedSession} module.
|
|
119
128
|
#
|
|
120
129
|
def block_until_node_deleted(abs_node_path)
|
|
121
130
|
subs = []
|
data/lib/zk/client.rb
CHANGED
data/lib/zk/event_handler.rb
CHANGED
data/lib/zk/exceptions.rb
CHANGED
|
@@ -74,27 +74,30 @@ module ZK
|
|
|
74
74
|
include InterruptedSession
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
77
|
+
silence_warnings do
|
|
78
|
+
# @private
|
|
79
|
+
ERROR_MAP = {
|
|
80
|
+
SYSTEMERROR => SystemError,
|
|
81
|
+
RUNTIMEINCONSISTENCY => RunTimeInconsistency,
|
|
82
|
+
DATAINCONSISTENCY => DataInconsistency,
|
|
83
|
+
CONNECTIONLOSS => ConnectionLoss,
|
|
84
|
+
MARSHALLINGERROR => MarshallingError,
|
|
85
|
+
UNIMPLEMENTED => Unimplemented,
|
|
86
|
+
OPERATIONTIMEOUT => OperationTimeOut,
|
|
87
|
+
BADARGUMENTS => BadArguments,
|
|
88
|
+
APIERROR => ApiError,
|
|
89
|
+
NONODE => NoNode,
|
|
90
|
+
NOAUTH => NoAuth,
|
|
91
|
+
BADVERSION => BadVersion,
|
|
92
|
+
NOCHILDRENFOREPHEMERALS => NoChildrenForEphemerals,
|
|
93
|
+
NODEEXISTS => NodeExists,
|
|
94
|
+
NOTEMPTY => NotEmpty,
|
|
95
|
+
SESSIONEXPIRED => SessionExpired,
|
|
96
|
+
INVALIDCALLBACK => InvalidCallback,
|
|
97
|
+
INVALIDACL => InvalidACL,
|
|
98
|
+
AUTHFAILED => AuthFailed,
|
|
99
|
+
}.freeze
|
|
100
|
+
end
|
|
98
101
|
|
|
99
102
|
# base class of ZK generated errors (not driver-level errors)
|
|
100
103
|
class ZKError < StandardError; end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module ZK
|
|
2
|
+
module Locker
|
|
3
|
+
# An exclusive lock implementation
|
|
4
|
+
#
|
|
5
|
+
# If the name 'dingus' is given, then in the case of an exclusive lock, the
|
|
6
|
+
# algorithm works like:
|
|
7
|
+
#
|
|
8
|
+
# * lock_path = `zk.create("/_zklocking/dingus/ex", :sequential => true, :ephemeral => true)`
|
|
9
|
+
# * extract the digit from the lock path
|
|
10
|
+
# * of all the children under '/_zklocking/dingus', do we have the lowest digit?
|
|
11
|
+
# * __yes__: then we hold the lock, if we're non-blocking, return true
|
|
12
|
+
# * __no__: is the lock blocking?
|
|
13
|
+
# * __yes__: then set a watch on the next-to-lowest node and sleep the current thread until that node has been deleted
|
|
14
|
+
# * __no__: return false, you lose
|
|
15
|
+
#
|
|
16
|
+
class ExclusiveLocker < LockerBase
|
|
17
|
+
# obtain an exclusive lock.
|
|
18
|
+
#
|
|
19
|
+
# @param blocking (see SharedLocker#lock!)
|
|
20
|
+
# @return (see SharedLocker#lock!)
|
|
21
|
+
#
|
|
22
|
+
# @raise [InterruptedSession] raised when blocked waiting for a lock and
|
|
23
|
+
# the underlying client's session is interrupted.
|
|
24
|
+
#
|
|
25
|
+
# @see ZK::Client::Unixisms#block_until_node_deleted more about possible execptions
|
|
26
|
+
#
|
|
27
|
+
def lock!(blocking=false)
|
|
28
|
+
return true if @locked
|
|
29
|
+
create_lock_path!(EXCLUSIVE_LOCK_PREFIX)
|
|
30
|
+
|
|
31
|
+
if got_write_lock?
|
|
32
|
+
@locked = true
|
|
33
|
+
elsif blocking
|
|
34
|
+
in_waiting_status do
|
|
35
|
+
block_until_write_lock!
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
cleanup_lock_path!
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
# the node that is next-lowest in sequence number to ours, the one we
|
|
45
|
+
# watch for updates to
|
|
46
|
+
# @private
|
|
47
|
+
def next_lowest_node
|
|
48
|
+
ary = ordered_lock_children()
|
|
49
|
+
my_idx = ary.index(lock_basename)
|
|
50
|
+
|
|
51
|
+
raise WeAreTheLowestLockNumberException if my_idx == 0
|
|
52
|
+
|
|
53
|
+
ary[(my_idx - 1)]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @private
|
|
57
|
+
def got_write_lock?
|
|
58
|
+
ordered_lock_children.first == lock_basename
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @private
|
|
62
|
+
def block_until_write_lock!
|
|
63
|
+
begin
|
|
64
|
+
path = [root_lock_path, next_lowest_node].join('/')
|
|
65
|
+
logger.debug { "SharedLocker#block_until_write_lock! path=#{path.inspect}" }
|
|
66
|
+
@zk.block_until_node_deleted(path)
|
|
67
|
+
rescue WeAreTheLowestLockNumberException
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
@locked = true
|
|
71
|
+
end
|
|
72
|
+
end # ExclusiveLocker
|
|
73
|
+
end # Locker
|
|
74
|
+
end # ZK
|
|
75
|
+
|