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.
@@ -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
+