zk 0.6.5 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.dotfiles/rspec-logging +4 -0
- data/.gitignore +2 -0
- data/.yardopts +8 -0
- data/Gemfile +6 -1
- data/README.markdown +86 -0
- data/lib/z_k/client/base.rb +692 -0
- data/lib/z_k/client/conveniences.rb +134 -0
- data/lib/z_k/client/state_mixin.rb +94 -0
- data/lib/z_k/client/unixisms.rb +89 -0
- data/lib/z_k/client.rb +12 -891
- data/lib/z_k/election.rb +3 -0
- data/lib/z_k/event_handler.rb +7 -5
- data/lib/z_k/mongoid.rb +1 -1
- data/lib/z_k/pool.rb +70 -27
- data/lib/z_k/threadpool.rb +7 -2
- data/lib/z_k/version.rb +1 -1
- data/lib/z_k.rb +1 -2
- data/spec/spec_helper.rb +1 -0
- data/spec/support/logging_progress_bar_formatter.rb +14 -0
- data/spec/watch_spec.rb +26 -8
- data/spec/{client_spec.rb → z_k/client_spec.rb} +1 -1
- data/spec/{election_spec.rb → z_k/election_spec.rb} +2 -3
- data/spec/{locker_spec.rb → z_k/locker_spec.rb} +1 -1
- data/spec/{mongoid_spec.rb → z_k/mongoid_spec.rb} +1 -1
- data/spec/{client_pool_spec.rb → z_k/pool_spec.rb} +98 -126
- data/spec/{threadpool_spec.rb → z_k/threadpool_spec.rb} +6 -3
- data/zk.gemspec +1 -0
- metadata +37 -26
@@ -0,0 +1,134 @@
|
|
1
|
+
module ZK
|
2
|
+
module Client
|
3
|
+
module Conveniences
|
4
|
+
# Queue an operation to be run on an internal threadpool. You may either
|
5
|
+
# provide an object that responds_to?(:call) or pass a block. There is no
|
6
|
+
# mechanism for retrieving the result of the operation, it is purely
|
7
|
+
# fire-and-forget, so the user is expected to make arrangements for this in
|
8
|
+
# their code.
|
9
|
+
#
|
10
|
+
# An ArgumentError will be raised if +callable+ does not <tt>respond_to?(:call)</tt>
|
11
|
+
#
|
12
|
+
# ==== Arguments
|
13
|
+
# * <tt>callable</tt>: an object that <tt>respond_to?(:call)</tt>, takes precedence
|
14
|
+
# over a given block
|
15
|
+
#
|
16
|
+
def defer(callable=nil, &block)
|
17
|
+
@threadpool.defer(callable, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
# does a stat on '/', rescues all zookeeper-protocol exceptions
|
21
|
+
#
|
22
|
+
# @private intended for use in monitoring scripts
|
23
|
+
# @return [bool]
|
24
|
+
def ping?
|
25
|
+
false unless connected?
|
26
|
+
false|stat('/')
|
27
|
+
rescue ZK::Exceptions::KeeperException
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
#--
|
33
|
+
#
|
34
|
+
# EXTENSIONS
|
35
|
+
#
|
36
|
+
# convenience methods for dealing with zookeeper (rm -rf, mkdir -p, etc)
|
37
|
+
#
|
38
|
+
#++
|
39
|
+
|
40
|
+
# creates a new locker based on the name you send in
|
41
|
+
#
|
42
|
+
# see ZK::Locker::ExclusiveLocker
|
43
|
+
#
|
44
|
+
# returns a ZK::Locker::ExclusiveLocker instance using this Client and provided
|
45
|
+
# lock name
|
46
|
+
#
|
47
|
+
# ==== Arguments
|
48
|
+
# * <tt>name</tt> name of the lock you wish to use
|
49
|
+
#
|
50
|
+
# ==== Examples
|
51
|
+
#
|
52
|
+
# zk.locker("blah")
|
53
|
+
# # => #<ZK::Locker::ExclusiveLocker:0x102034cf8 ...>
|
54
|
+
#
|
55
|
+
def locker(name)
|
56
|
+
Locker.exclusive_locker(self, name)
|
57
|
+
end
|
58
|
+
|
59
|
+
# create a new shared locking instance based on the name given
|
60
|
+
#
|
61
|
+
# returns a ZK::Locker::SharedLocker instance using this Client and provided
|
62
|
+
# lock name
|
63
|
+
#
|
64
|
+
# ==== Arguments
|
65
|
+
# * <tt>name</tt> name of the lock you wish to use
|
66
|
+
#
|
67
|
+
# ==== Examples
|
68
|
+
#
|
69
|
+
# zk.shared_locker("blah")
|
70
|
+
# # => #<ZK::Locker::SharedLocker:0x102034cf8 ...>
|
71
|
+
#
|
72
|
+
def shared_locker(name)
|
73
|
+
Locker.shared_locker(self, name)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Convenience method for acquiring a lock then executing a code block. This
|
77
|
+
# will block the caller until the lock is acquired.
|
78
|
+
#
|
79
|
+
# ==== Arguments
|
80
|
+
# * <tt>name</tt>: the name of the lock to use
|
81
|
+
# * <tt>:mode</tt>: either :shared or :exclusive, defaults to :exclusive
|
82
|
+
#
|
83
|
+
# ==== Examples
|
84
|
+
#
|
85
|
+
# zk.with_lock('foo') do
|
86
|
+
# # this code is executed while holding the lock
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
def with_lock(name, opts={}, &b)
|
90
|
+
mode = opts[:mode] || :exclusive
|
91
|
+
|
92
|
+
raise ArgumentError, ":mode option must be either :shared or :exclusive, not #{mode.inspect}" unless [:shared, :exclusive].include?(mode)
|
93
|
+
|
94
|
+
if mode == :shared
|
95
|
+
shared_locker(name).with_lock(&b)
|
96
|
+
else
|
97
|
+
locker(name).with_lock(&b)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Convenience method for constructing a ZK::Election::Candidate object using this
|
102
|
+
# Client connection, the given election +name+ and +data+.
|
103
|
+
#
|
104
|
+
def election_candidate(name, data, opts={})
|
105
|
+
opts = opts.merge(:data => data)
|
106
|
+
ZK::Election::Candidate.new(self, name, opts)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Convenience method for constructing a ZK::Election::Observer object using this
|
110
|
+
# Client connection, and the given election +name+.
|
111
|
+
#
|
112
|
+
def election_observer(name, opts={})
|
113
|
+
ZK::Election::Observer.new(self, name, opts)
|
114
|
+
end
|
115
|
+
|
116
|
+
# creates a new message queue of name +name+
|
117
|
+
#
|
118
|
+
# returns a ZK::MessageQueue object
|
119
|
+
#
|
120
|
+
# ==== Arguments
|
121
|
+
# * <tt>name</tt> the name of the queue
|
122
|
+
#
|
123
|
+
# ==== Examples
|
124
|
+
#
|
125
|
+
# zk.queue("blah").publish({:some_data => "that is yaml serializable"})
|
126
|
+
#
|
127
|
+
def queue(name)
|
128
|
+
MessageQueue.new(self, name)
|
129
|
+
end
|
130
|
+
|
131
|
+
end # Conveniences
|
132
|
+
end # Client
|
133
|
+
end # ZK
|
134
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module ZK
|
2
|
+
module Client
|
3
|
+
# Provides client-state related methods. Included in ZK::Client::Base.
|
4
|
+
# (refactored out to this class to ease documentation overload)
|
5
|
+
module StateMixin
|
6
|
+
# Returns true if the underlying connection is in the +connected+ state.
|
7
|
+
def connected?
|
8
|
+
wrap_state_closed_error { cnx and cnx.connected? }
|
9
|
+
end
|
10
|
+
|
11
|
+
# is the underlying connection is in the +associating+ state?
|
12
|
+
# @return [bool]
|
13
|
+
def associating?
|
14
|
+
wrap_state_closed_error { cnx and cnx.associating? }
|
15
|
+
end
|
16
|
+
|
17
|
+
# is the underlying connection is in the +connecting+ state?
|
18
|
+
# @return [bool]
|
19
|
+
def connecting?
|
20
|
+
wrap_state_closed_error { cnx and cnx.connecting? }
|
21
|
+
end
|
22
|
+
|
23
|
+
# is the underlying connection is in the +expired_session+ state?
|
24
|
+
# @return [bool]
|
25
|
+
def expired_session?
|
26
|
+
return nil unless @cnx
|
27
|
+
|
28
|
+
if defined?(::JRUBY_VERSION)
|
29
|
+
cnx.state == Java::OrgApacheZookeeper::ZooKeeper::States::EXPIRED_SESSION
|
30
|
+
else
|
31
|
+
wrap_state_closed_error { cnx.state == Zookeeper::ZOO_EXPIRED_SESSION_STATE }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# returns the current state of the connection as reported by the underlying driver
|
36
|
+
# as a symbol. The possible values are <tt>[:closed, :expired_session, :auth_failed
|
37
|
+
# :connecting, :connected, :associating]</tt>.
|
38
|
+
#
|
39
|
+
# See the Zookeeper session
|
40
|
+
# {documentation}[http://hadoop.apache.org/zookeeper/docs/current/zookeeperProgrammers.html#ch_zkSessions]
|
41
|
+
# for more information
|
42
|
+
#
|
43
|
+
def state
|
44
|
+
if defined?(::JRUBY_VERSION)
|
45
|
+
cnx.state.to_string.downcase.to_sym
|
46
|
+
else
|
47
|
+
STATE_SYM_MAP.fetch(cnx.state) { |k| raise IndexError, "unrecognized state: #{k}" }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @private
|
52
|
+
#
|
53
|
+
# Register a block to be called on connection, when the client has
|
54
|
+
# connected.
|
55
|
+
#
|
56
|
+
# the block will be called with no arguments
|
57
|
+
#
|
58
|
+
# returns an EventHandlerSubscription object that can be used to unregister
|
59
|
+
# this block from further updates
|
60
|
+
#
|
61
|
+
def on_connected(&block)
|
62
|
+
watcher.register_state_handler(:connected, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
# register a block to be called when the client is attempting to reconnect
|
66
|
+
# to the zookeeper server. the documentation says that this state should be
|
67
|
+
# taken to mean that the application should enter into "safe mode" and operate
|
68
|
+
# conservatively, as it won't be getting updates until it has reconnected
|
69
|
+
#
|
70
|
+
def on_connecting(&block)
|
71
|
+
watcher.register_state_handler(:connecting, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
# register a block to be called when our session has expired. This usually happens
|
75
|
+
# due to a network partitioning event, and means that all callbacks and watches must
|
76
|
+
# be re-registered with the server
|
77
|
+
#
|
78
|
+
# @todo need to come up with a way to test this
|
79
|
+
def on_expired_session(&block)
|
80
|
+
watcher.register_state_handler(:expired_session, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
def wrap_state_closed_error
|
85
|
+
yield
|
86
|
+
rescue RuntimeError => e
|
87
|
+
# gah, lame error parsing here
|
88
|
+
raise e unless e.message == 'zookeeper handle is closed'
|
89
|
+
false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module ZK
|
2
|
+
module Client
|
3
|
+
module Unixisms
|
4
|
+
# Creates all parent paths and 'path' in zookeeper as persistent nodes with
|
5
|
+
# zero data.
|
6
|
+
#
|
7
|
+
# ==== Arguments
|
8
|
+
# * <tt>path</tt>: An absolute znode path to create
|
9
|
+
#
|
10
|
+
# ==== Examples
|
11
|
+
#
|
12
|
+
# zk.exists?('/path')
|
13
|
+
# # => false
|
14
|
+
#
|
15
|
+
# zk.mkdir_p('/path/to/blah')
|
16
|
+
# # => "/path/to/blah"
|
17
|
+
#
|
18
|
+
#--
|
19
|
+
# TODO: write a non-recursive version of this. ruby doesn't have TCO, so
|
20
|
+
# this could get expensive w/ psychotically long paths
|
21
|
+
def mkdir_p(path)
|
22
|
+
create(path, '', :mode => :persistent)
|
23
|
+
rescue Exceptions::NodeExists
|
24
|
+
return
|
25
|
+
rescue Exceptions::NoNode
|
26
|
+
if File.dirname(path) == '/'
|
27
|
+
# ok, we're screwed, blow up
|
28
|
+
raise KeeperException, "could not create '/', something is wrong", caller
|
29
|
+
end
|
30
|
+
|
31
|
+
mkdir_p(File.dirname(path))
|
32
|
+
retry
|
33
|
+
end
|
34
|
+
|
35
|
+
# recursively remove all children of path then remove path itself
|
36
|
+
def rm_rf(paths)
|
37
|
+
Array(paths).flatten.each do |path|
|
38
|
+
begin
|
39
|
+
children(path).each do |child|
|
40
|
+
rm_rf(File.join(path, child))
|
41
|
+
end
|
42
|
+
|
43
|
+
delete(path)
|
44
|
+
nil
|
45
|
+
rescue Exceptions::NoNode
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# see ZK::Find for explanation
|
51
|
+
def find(*paths, &block)
|
52
|
+
ZK::Find.find(self, *paths, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
# will block the caller until +abs_node_path+ has been removed
|
56
|
+
#
|
57
|
+
# @private this method is of dubious value and may be removed in a later
|
58
|
+
# version
|
59
|
+
#
|
60
|
+
# @note this is dangerous to use in callbacks! there is only one
|
61
|
+
# event-delivery thread, so if you use this method in a callback or
|
62
|
+
# watcher, you *will* deadlock!
|
63
|
+
def block_until_node_deleted(abs_node_path)
|
64
|
+
queue = Queue.new
|
65
|
+
ev_sub = nil
|
66
|
+
|
67
|
+
node_deletion_cb = lambda do |event|
|
68
|
+
if event.node_deleted?
|
69
|
+
queue.enq(:deleted)
|
70
|
+
else
|
71
|
+
queue.enq(:deleted) unless exists?(abs_node_path, :watch => true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
ev_sub = watcher.register(abs_node_path, &node_deletion_cb)
|
76
|
+
|
77
|
+
# set up the callback, but bail if we don't need to wait
|
78
|
+
return true unless exists?(abs_node_path, :watch => true)
|
79
|
+
|
80
|
+
queue.pop # block waiting for node deletion
|
81
|
+
true
|
82
|
+
ensure
|
83
|
+
# be sure we clean up after ourselves
|
84
|
+
ev_sub.unregister if ev_sub
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|