zk 0.6.5 → 0.7.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/.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
|
+
|