zk 0.9.1 → 1.0.0.rc.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/.gitignore +2 -2
- data/Gemfile +3 -4
- data/README.markdown +14 -5
- data/RELEASES.markdown +69 -1
- data/Rakefile +50 -18
- data/docs/examples/block_until_node_deleted_ex.rb +63 -0
- data/docs/examples/events_01.rb +36 -0
- data/docs/examples/events_02.rb +41 -0
- data/lib/{z_k → zk}/client/base.rb +87 -30
- data/lib/{z_k → zk}/client/conveniences.rb +0 -1
- data/lib/{z_k → zk}/client/state_mixin.rb +0 -0
- data/lib/zk/client/threaded.rb +196 -0
- data/lib/{z_k → zk}/client/unixisms.rb +0 -0
- data/lib/zk/client.rb +59 -0
- data/lib/zk/core_ext.rb +75 -0
- data/lib/{z_k → zk}/election.rb +0 -0
- data/lib/zk/event.rb +168 -0
- data/lib/{z_k → zk}/event_handler.rb +53 -28
- data/lib/zk/event_handler_subscription.rb +68 -0
- data/lib/{z_k → zk}/exceptions.rb +38 -23
- data/lib/zk/extensions.rb +79 -0
- data/lib/{z_k → zk}/find.rb +0 -0
- data/lib/{z_k → zk}/locker.rb +0 -0
- data/lib/{z_k → zk}/logging.rb +0 -0
- data/lib/{z_k → zk}/message_queue.rb +8 -4
- data/lib/{z_k → zk}/mongoid.rb +0 -0
- data/lib/{z_k → zk}/pool.rb +0 -0
- data/lib/zk/stat.rb +115 -0
- data/lib/{z_k → zk}/threadpool.rb +52 -4
- data/lib/zk/version.rb +3 -0
- data/lib/zk.rb +238 -1
- data/spec/message_queue_spec.rb +2 -2
- data/spec/shared/client_contexts.rb +8 -20
- data/spec/shared/client_examples.rb +136 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/support/event_catcher.rb +11 -0
- data/spec/support/exist_matcher.rb +6 -0
- data/spec/support/logging.rb +2 -1
- data/spec/watch_spec.rb +194 -10
- data/spec/{z_k → zk}/client/locking_and_session_death_spec.rb +0 -32
- data/spec/zk/client_spec.rb +23 -0
- data/spec/{z_k → zk}/election_spec.rb +0 -0
- data/spec/{z_k → zk}/extensions_spec.rb +0 -0
- data/spec/{z_k → zk}/locker_spec.rb +0 -40
- data/spec/zk/module_spec.rb +185 -0
- data/spec/{z_k → zk}/mongoid_spec.rb +0 -2
- data/spec/{z_k → zk}/pool_spec.rb +0 -2
- data/spec/{z_k → zk}/threadpool_spec.rb +32 -4
- data/spec/zookeeper_spec.rb +1 -6
- data/zk.gemspec +2 -2
- metadata +64 -56
- data/lib/z_k/client/continuation_proxy.rb +0 -109
- data/lib/z_k/client/drop_box.rb +0 -98
- data/lib/z_k/client/multiplexed.rb +0 -28
- data/lib/z_k/client/threaded.rb +0 -76
- data/lib/z_k/client.rb +0 -35
- data/lib/z_k/event_handler_subscription.rb +0 -36
- data/lib/z_k/extensions.rb +0 -155
- data/lib/z_k/version.rb +0 -3
- data/lib/z_k.rb +0 -97
- data/spec/z_k/client/drop_box_spec.rb +0 -90
- data/spec/z_k/client/multiplexed_spec.rb +0 -20
- data/spec/z_k/client_spec.rb +0 -7
@@ -0,0 +1,196 @@
|
|
1
|
+
module ZK
|
2
|
+
module Client
|
3
|
+
# This is the default client that ZK will use. In the zk-eventmachine gem,
|
4
|
+
# there is an Evented client.
|
5
|
+
#
|
6
|
+
# If you want to register `on_*` callbacks (see ZK::Client::StateMixin)
|
7
|
+
# then you should pass a block, which will be called before the
|
8
|
+
# connection is set up (this way you can get the `on_connected` event). See
|
9
|
+
# the 'Register on_connected callback' example.
|
10
|
+
#
|
11
|
+
# A note on event delivery. There has been some confusion, caused by
|
12
|
+
# incorrect documentation (which I'm very sorry about), about how many
|
13
|
+
# threads are delivering events. The documentation for 0.9.0 was incorrect
|
14
|
+
# in stating the number of threads used to deliver events. There was one,
|
15
|
+
# unconfigurable, event dispatch thread. In 1.0 the number of event
|
16
|
+
# delivery threads is configurable, but still defaults to 1.
|
17
|
+
#
|
18
|
+
# The configurability is intended to allow users to easily dispatch events to
|
19
|
+
# event handlers that will perform (application specific) work. Be aware,
|
20
|
+
# the default will give you the guarantee that only one event will be delivered
|
21
|
+
# at a time. The advantage to this is that you can be sure that no event will
|
22
|
+
# be delivered "behind your back" while you're in an event handler. If you're
|
23
|
+
# comfortable with dealing with threads and concurrency, then feel free to
|
24
|
+
# set the `:threadpool_size` option to the constructor to a value you feel is
|
25
|
+
# correct for your app.
|
26
|
+
#
|
27
|
+
# If you use the threadpool/event callbacks to perform work, you may be
|
28
|
+
# interested in registering an `on_exception` callback that will receive
|
29
|
+
# all exceptions that occur on the threadpool that are not handled (i.e.
|
30
|
+
# that bubble up to top of a block).
|
31
|
+
#
|
32
|
+
# It is recommended that you not run any possibly long-running work on the
|
33
|
+
# event threadpool, as `close!` will attempt to shutdown the threadpool, and
|
34
|
+
# **WILL NOT WAIT FOREVER**. (TODO: more on this)
|
35
|
+
#
|
36
|
+
# @example Register on_connected callback.
|
37
|
+
#
|
38
|
+
# # the nice thing about this pattern is that in the case of a call to #reopen
|
39
|
+
# # all your watches will be re-established
|
40
|
+
#
|
41
|
+
# ZK::Client::Threaded.new('localhsot:2181') do |zk|
|
42
|
+
# # do not do anything in here except register callbacks
|
43
|
+
#
|
44
|
+
# zk.on_connected do |event|
|
45
|
+
# zk.stat('/foo/bar', watch: true)
|
46
|
+
# zk.stat('/baz', watch: true)
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
class Threaded < Base
|
51
|
+
include StateMixin
|
52
|
+
include Unixisms
|
53
|
+
include Conveniences
|
54
|
+
include Logging
|
55
|
+
|
56
|
+
DEFAULT_THREADPOOL_SIZE = 1
|
57
|
+
|
58
|
+
# Construct a new threaded client.
|
59
|
+
#
|
60
|
+
# @note The `:timeout` argument here is *not* the session_timeout for the
|
61
|
+
# connection. rather it is the amount of time we wait for the connection
|
62
|
+
# to be established. The session timeout exchanged with the server is
|
63
|
+
# set to 10s by default in the C implemenation, and as of version 0.8.0
|
64
|
+
# of slyphon-zookeeper has yet to be exposed as an option. That feature
|
65
|
+
# is planned.
|
66
|
+
#
|
67
|
+
# @note The documentation for 0.9.0 was incorrect in stating the number
|
68
|
+
# of threads used to deliver events. There was one, unconfigurable,
|
69
|
+
# event dispatch thread. In 1.0 the number of event delivery threads is
|
70
|
+
# configurable, but still defaults to 1. (The Management apologizes for
|
71
|
+
# any confusion this may have caused).
|
72
|
+
#
|
73
|
+
# @param [String] host (see ZK::Client::Base#initialize)
|
74
|
+
#
|
75
|
+
# @option opts [true,false] :reconnect (true) if true, we will register
|
76
|
+
# the equivalent of `on_session_expired { zk.reopen }` so that in the
|
77
|
+
# case of an expired session, we will keep trying to reestablish the
|
78
|
+
# connection.
|
79
|
+
#
|
80
|
+
# @option opts [Fixnum] :threadpool_size (1) the size of the threadpool that
|
81
|
+
# should be used to deliver events. As of 1.0, this is the number of
|
82
|
+
# event delivery threads and controls the amount of concurrency in your
|
83
|
+
# app if you're doing work in the event callbacks.
|
84
|
+
#
|
85
|
+
# @option opts [Fixnum] :timeout how long we will wait for the connection
|
86
|
+
# to be established. If timeout is nil, we will wait forever *use
|
87
|
+
# carefully*.
|
88
|
+
#
|
89
|
+
# @yield [self] calls the block with the new instance after the event
|
90
|
+
# handler and threadpool have been set up, but before any connections
|
91
|
+
# have been made. This allows the client to register watchers for
|
92
|
+
# session events like `connected`. You *cannot* perform any other
|
93
|
+
# operations with the client as you will get a NoMethodError (the
|
94
|
+
# underlying connection is nil).
|
95
|
+
#
|
96
|
+
# @see Base#initialize
|
97
|
+
def initialize(host, opts={}, &b)
|
98
|
+
super(host, opts)
|
99
|
+
|
100
|
+
tp_size = opts.fetch(:threadpool_size, DEFAULT_THREADPOOL_SIZE)
|
101
|
+
@threadpool = Threadpool.new(tp_size)
|
102
|
+
|
103
|
+
@session_timeout = opts.fetch(:timeout, DEFAULT_TIMEOUT) # maybe move this into superclass?
|
104
|
+
@event_handler = EventHandler.new(self)
|
105
|
+
|
106
|
+
@reconnect = opts.fetch(:reconnect, true)
|
107
|
+
|
108
|
+
@mutex = Mutex.new
|
109
|
+
|
110
|
+
@close_requested = false
|
111
|
+
|
112
|
+
yield self if block_given?
|
113
|
+
|
114
|
+
@cnx = create_connection(host, @session_timeout, @event_handler.get_default_watcher_block)
|
115
|
+
end
|
116
|
+
|
117
|
+
# (see Base#reopen)
|
118
|
+
def reopen(timeout=nil)
|
119
|
+
@mutex.synchronize { @close_requested = false }
|
120
|
+
super
|
121
|
+
end
|
122
|
+
|
123
|
+
# (see Base#close!)
|
124
|
+
#
|
125
|
+
# @note We will make our best effort to do the right thing if you call
|
126
|
+
# this method while in the threadpool. It is _a much better idea_ to
|
127
|
+
# call us from the main thread, or _at least_ a thread we're not going
|
128
|
+
# to be trying to shut down as part of closing the connection and
|
129
|
+
# threadpool.
|
130
|
+
#
|
131
|
+
def close!
|
132
|
+
@mutex.synchronize do
|
133
|
+
return if @close_requested
|
134
|
+
@close_requested = true
|
135
|
+
end
|
136
|
+
|
137
|
+
on_tpool = on_threadpool?
|
138
|
+
|
139
|
+
# Ok, so the threadpool will wait up to N seconds while joining each thread.
|
140
|
+
# If _we're on a threadpool thread_, have it wait until we're ready to jump
|
141
|
+
# out of this method, and tell it to wait up to 5 seconds to let us get
|
142
|
+
# clear, then do the rest of the shutdown of the connection
|
143
|
+
#
|
144
|
+
# if the user *doesn't* hate us, then we just join the shutdown_thread immediately
|
145
|
+
# and wait for it to exit
|
146
|
+
#
|
147
|
+
shutdown_thread = Thread.new do
|
148
|
+
@threadpool.shutdown(2)
|
149
|
+
super
|
150
|
+
end
|
151
|
+
|
152
|
+
shutdown_thread.join unless on_tpool
|
153
|
+
|
154
|
+
nil
|
155
|
+
end
|
156
|
+
|
157
|
+
# (see Threadpool#on_threadpool?)
|
158
|
+
def on_threadpool?
|
159
|
+
@threadpool and @threadpool.on_threadpool?
|
160
|
+
end
|
161
|
+
|
162
|
+
# (see Threadpool#on_exception)
|
163
|
+
def on_exception(&blk)
|
164
|
+
@threadpool.on_exception(&blk)
|
165
|
+
end
|
166
|
+
|
167
|
+
# @private
|
168
|
+
def raw_event_handler(event)
|
169
|
+
return unless event.session_event?
|
170
|
+
|
171
|
+
if event.client_invalid?
|
172
|
+
return unless @reconnect
|
173
|
+
|
174
|
+
@mutex.synchronize do
|
175
|
+
unless @close_requested # a legitimate shutdown case
|
176
|
+
|
177
|
+
logger.error { "Got event #{event.state_name}, calling reopen(0)! things may be messed up until this works itself out!" }
|
178
|
+
|
179
|
+
# reopen(0) means that we don't want to wait for the connection
|
180
|
+
# to reach the connected state before returning
|
181
|
+
reopen(0)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
rescue Exception => e
|
186
|
+
logger.error { "BUG: Exception caught in raw_event_handler: #{e.to_std_format}" }
|
187
|
+
end
|
188
|
+
|
189
|
+
protected
|
190
|
+
# @private
|
191
|
+
def create_connection(*args)
|
192
|
+
::Zookeeper.new(*args)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
File without changes
|
data/lib/zk/client.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module ZK
|
2
|
+
# A ruby-friendly wrapper around the low-level zookeeper drivers.
|
3
|
+
#
|
4
|
+
# You're probably looking for {Client::Base} and {Client::Threaded}.
|
5
|
+
#
|
6
|
+
# Once you've had a look there, take a look at {Client::Conveniences},
|
7
|
+
# {Client::StateMixin}, and {Client::Unixisms}
|
8
|
+
#
|
9
|
+
# @todo ACL support is pretty much unused currently.
|
10
|
+
# If anyone has suggestions, hints, use-cases, examples, etc. by all means please file a bug.
|
11
|
+
#
|
12
|
+
module Client
|
13
|
+
DEFAULT_TIMEOUT = 10 unless defined?(DEFAULT_TIMEOUT)
|
14
|
+
|
15
|
+
# @private
|
16
|
+
STATE_SYM_MAP = {
|
17
|
+
Zookeeper::ZOO_CLOSED_STATE => :closed,
|
18
|
+
Zookeeper::ZOO_EXPIRED_SESSION_STATE => :expired_session,
|
19
|
+
Zookeeper::ZOO_AUTH_FAILED_STATE => :auth_failed,
|
20
|
+
Zookeeper::ZOO_CONNECTING_STATE => :connecting,
|
21
|
+
Zookeeper::ZOO_CONNECTED_STATE => :connected,
|
22
|
+
Zookeeper::ZOO_ASSOCIATING_STATE => :associating,
|
23
|
+
}.freeze unless defined?(STATE_SYM_MAP)
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def new(*a, &b)
|
27
|
+
Threaded.new(*a, &b)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @private
|
31
|
+
def assert_valid_chroot_str!(str)
|
32
|
+
return unless str
|
33
|
+
raise ChrootMustStartWithASlashError, str unless str.start_with?('/')
|
34
|
+
end
|
35
|
+
|
36
|
+
# Takes a connection string and returns an Array of [host, chroot_path].
|
37
|
+
# If the connection string is not chrooted, then chroot_path will be nil.
|
38
|
+
#
|
39
|
+
# @private
|
40
|
+
def split_chroot(str)
|
41
|
+
if idx = str.index('/')
|
42
|
+
host = str[0...idx]
|
43
|
+
chroot_path = str[idx..-1]
|
44
|
+
|
45
|
+
[host, chroot_path]
|
46
|
+
else
|
47
|
+
[str, nil]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
require 'zk/client/state_mixin'
|
55
|
+
require 'zk/client/unixisms'
|
56
|
+
require 'zk/client/conveniences'
|
57
|
+
require 'zk/client/base'
|
58
|
+
require 'zk/client/threaded'
|
59
|
+
|
data/lib/zk/core_ext.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# @private
|
2
|
+
class ::Exception
|
3
|
+
unless method_defined?(:to_std_format)
|
4
|
+
def to_std_format
|
5
|
+
ary = ["#{self.class}: #{message}"]
|
6
|
+
ary.concat(backtrace || [])
|
7
|
+
ary.join("\n\t")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# @private
|
13
|
+
class ::Thread
|
14
|
+
def zk_mongoid_lock_registry
|
15
|
+
self[:_zk_mongoid_lock_registry]
|
16
|
+
end
|
17
|
+
|
18
|
+
def zk_mongoid_lock_registry=(obj)
|
19
|
+
self[:_zk_mongoid_lock_registry] = obj
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @private
|
24
|
+
class ::Hash
|
25
|
+
# taken from ActiveSupport 3.0.12, but we don't replace it if it exists
|
26
|
+
unless method_defined?(:extractable_options?)
|
27
|
+
def extractable_options?
|
28
|
+
instance_of?(Hash)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @private
|
34
|
+
class ::Array
|
35
|
+
unless method_defined?(:extract_options!)
|
36
|
+
def extract_options!
|
37
|
+
if last.is_a?(Hash) && last.extractable_options?
|
38
|
+
pop
|
39
|
+
else
|
40
|
+
{}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# backport this from 1.9.x to 1.8.7
|
46
|
+
#
|
47
|
+
# this obviously cannot replicate the copy-on-write semantics of the
|
48
|
+
# 1.9.3 version, and only provides a naieve filtering functionality.
|
49
|
+
#
|
50
|
+
# also, does not handle the "returning an enumerator" case
|
51
|
+
unless method_defined?(:select!)
|
52
|
+
def select!(&b)
|
53
|
+
replace(select(&b))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @private
|
59
|
+
module ::Kernel
|
60
|
+
unless method_defined?(:silence_warnings)
|
61
|
+
def silence_warnings
|
62
|
+
with_warnings(nil) { yield }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
unless method_defined?(:with_warnings)
|
67
|
+
def with_warnings(flag)
|
68
|
+
old_verbose, $VERBOSE = $VERBOSE, flag
|
69
|
+
yield
|
70
|
+
ensure
|
71
|
+
$VERBOSE = old_verbose
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
data/lib/{z_k → zk}/election.rb
RENAMED
File without changes
|
data/lib/zk/event.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
module ZK
|
2
|
+
# Provides most of the functionality ZK uses around events. Base class is actually
|
3
|
+
# [ZookeeperCallbacks::WatcherCallback](http://rubydoc.info/gems/slyphon-zookeeper/ZookeeperCallbacks/WatcherCallback),
|
4
|
+
# but this module is mixed in and provides a lot of useful syntactic sugar.
|
5
|
+
#
|
6
|
+
module Event
|
7
|
+
include ZookeeperConstants
|
8
|
+
|
9
|
+
# unless defined? apparently messes up yard's ability to see the @private
|
10
|
+
silence_warnings do
|
11
|
+
# XXX: this is not uesd it seems
|
12
|
+
# @private
|
13
|
+
EVENT_NAME_MAP = {
|
14
|
+
1 => 'created',
|
15
|
+
2 => 'deleted',
|
16
|
+
3 => 'changed',
|
17
|
+
4 => 'child',
|
18
|
+
-1 => 'session',
|
19
|
+
-2 => 'notwatching',
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
# @private
|
23
|
+
STATES = %w[connecting associating connected auth_failed expired_session].freeze
|
24
|
+
|
25
|
+
# @private
|
26
|
+
EVENT_TYPES = %w[created deleted changed child session notwatching].freeze
|
27
|
+
end
|
28
|
+
|
29
|
+
# The numeric constant (one of `ZOO_*_EVENT`) that ZooKeeper sets to
|
30
|
+
# indicate the type of event this is. Users are advised to use the '?'
|
31
|
+
# methods below instead of using this value.
|
32
|
+
#
|
33
|
+
# @return [Fixnum]
|
34
|
+
def type
|
35
|
+
# no-op, the functionality is provided by the class this is mixed into.
|
36
|
+
# here only for documentation purposes
|
37
|
+
end
|
38
|
+
|
39
|
+
# The numeric constant (one of `ZOO_*_STATE`) that ZooKeeper sets to
|
40
|
+
# indicate the session state this event is notifying us of. Users are
|
41
|
+
# encouraged to use the '?' methods below, instead of this value.
|
42
|
+
#
|
43
|
+
# @return [Fixnum]
|
44
|
+
def state
|
45
|
+
# no-op, the functionality is provided by the class this is mixed into.
|
46
|
+
# here only for documentation purposes
|
47
|
+
end
|
48
|
+
|
49
|
+
# The path this event is in reference to.
|
50
|
+
#
|
51
|
+
# @return [String,nil] This value will be nil if `session_event?` is false, otherwise
|
52
|
+
# a String containing the path this event was triggered in reference to
|
53
|
+
def path
|
54
|
+
# no-op, the functionality is provided by the class this is mixed into.
|
55
|
+
# here only for documentation purposes
|
56
|
+
end
|
57
|
+
|
58
|
+
# Is this event notifying us we're in the connecting state?
|
59
|
+
def connecting?
|
60
|
+
@state == ZOO_CONNECTING_STATE
|
61
|
+
end
|
62
|
+
alias state_connecting? connecting?
|
63
|
+
|
64
|
+
# Is this event notifying us we're in the associating state?
|
65
|
+
def associating?
|
66
|
+
@state == ZOO_ASSOCIATING_STATE
|
67
|
+
end
|
68
|
+
alias state_associating? associating?
|
69
|
+
|
70
|
+
# Is this event notifying us we're in the connected state?
|
71
|
+
def connected?
|
72
|
+
@state == ZOO_CONNECTED_STATE
|
73
|
+
end
|
74
|
+
alias state_connected? connected?
|
75
|
+
|
76
|
+
# Is this event notifying us we're in the auth_failed state?
|
77
|
+
def auth_failed?
|
78
|
+
@state == ZOO_AUTH_FAILED_STATE
|
79
|
+
end
|
80
|
+
alias state_auth_failed? auth_failed?
|
81
|
+
|
82
|
+
# Is this event notifying us we're in the expired_session state?
|
83
|
+
def expired_session?
|
84
|
+
@state == ZOO_EXPIRED_SESSION_STATE
|
85
|
+
end
|
86
|
+
alias state_expired_session? expired_session?
|
87
|
+
|
88
|
+
# return this event's state name as a string "ZOO_*_STATE", used for debugging
|
89
|
+
def state_name
|
90
|
+
(name = STATE_NAMES[@state]) ? "ZOO_#{name.to_s.upcase}_STATE" : ''
|
91
|
+
end
|
92
|
+
|
93
|
+
# Has a node been created?
|
94
|
+
def node_created?
|
95
|
+
@type == ZOO_CREATED_EVENT
|
96
|
+
end
|
97
|
+
|
98
|
+
# Has a node been deleted?
|
99
|
+
def node_deleted?
|
100
|
+
@type == ZOO_DELETED_EVENT
|
101
|
+
end
|
102
|
+
|
103
|
+
# Has a node changed?
|
104
|
+
def node_changed?
|
105
|
+
@type == ZOO_CHANGED_EVENT
|
106
|
+
end
|
107
|
+
|
108
|
+
# Has a node's list of children changed?
|
109
|
+
def node_child?
|
110
|
+
@type == ZOO_CHILD_EVENT
|
111
|
+
end
|
112
|
+
|
113
|
+
# Is this a session-related event?
|
114
|
+
#
|
115
|
+
# @deprecated This was an artifact of the way these methods were created
|
116
|
+
# originally, will be removed because it's kinda dumb. use {#session_event?}
|
117
|
+
def node_session?
|
118
|
+
@type == ZOO_SESSION_EVENT
|
119
|
+
end
|
120
|
+
|
121
|
+
# I have never seen this event delivered. here for completeness.
|
122
|
+
def node_notwatching?
|
123
|
+
@type == ZOO_NOTWATCHING_EVENT
|
124
|
+
end
|
125
|
+
alias node_not_watching? node_notwatching?
|
126
|
+
|
127
|
+
# return this event's type name as a string "ZOO_*_EVENT", used for debugging
|
128
|
+
def event_name
|
129
|
+
(name = EVENT_TYPE_NAMES[@type]) ? "ZOO_#{name.to_s.upcase}_EVENT" : ''
|
130
|
+
end
|
131
|
+
|
132
|
+
# used by the EventHandler
|
133
|
+
# @private
|
134
|
+
def interest_key
|
135
|
+
EVENT_TYPE_NAMES.fetch(@type).to_sym
|
136
|
+
end
|
137
|
+
|
138
|
+
# has this watcher been called because of a change in connection state?
|
139
|
+
def session_event?
|
140
|
+
@type == ZOO_SESSION_EVENT
|
141
|
+
end
|
142
|
+
alias state_event? session_event?
|
143
|
+
|
144
|
+
# has this watcher been called because of a change to a zookeeper node?
|
145
|
+
# `node_event?` and `session_event?` are mutually exclusive.
|
146
|
+
def node_event?
|
147
|
+
path and not path.empty?
|
148
|
+
end
|
149
|
+
|
150
|
+
# according to [the programmer's guide](http://zookeeper.apache.org/doc/r3.3.4/zookeeperProgrammers.html#Java+Binding)
|
151
|
+
#
|
152
|
+
# > once a ZooKeeper object is closed or receives a fatal event
|
153
|
+
# > (SESSION_EXPIRED and AUTH_FAILED), the ZooKeeper object becomes
|
154
|
+
# > invalid.
|
155
|
+
#
|
156
|
+
# this will return true for either of those cases
|
157
|
+
#
|
158
|
+
def client_invalid?
|
159
|
+
(@state == ZOO_EXPIRED_SESSION_STATE) || (@state == ZOO_AUTH_FAILED_STATE)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# @private
|
165
|
+
class ZookeeperCallbacks::WatcherCallback
|
166
|
+
include ::ZK::Event
|
167
|
+
end
|
168
|
+
|
@@ -10,6 +10,8 @@ module ZK
|
|
10
10
|
|
11
11
|
VALID_WATCH_TYPES = [:data, :child].freeze
|
12
12
|
|
13
|
+
ALL_NODE_EVENTS_KEY = :all_node_events
|
14
|
+
|
13
15
|
ZOOKEEPER_WATCH_TYPE_MAP = {
|
14
16
|
Zookeeper::ZOO_CREATED_EVENT => :data,
|
15
17
|
Zookeeper::ZOO_DELETED_EVENT => :data,
|
@@ -17,7 +19,8 @@ module ZK
|
|
17
19
|
Zookeeper::ZOO_CHILD_EVENT => :child,
|
18
20
|
}.freeze
|
19
21
|
|
20
|
-
|
22
|
+
# @private
|
23
|
+
attr_accessor :zk
|
21
24
|
|
22
25
|
# @private
|
23
26
|
# :nodoc:
|
@@ -33,9 +36,10 @@ module ZK
|
|
33
36
|
end
|
34
37
|
|
35
38
|
# @see ZK::Client::Base#register
|
36
|
-
def register(path, &block)
|
37
|
-
|
38
|
-
|
39
|
+
def register(path, interests=nil, &block)
|
40
|
+
path = ALL_NODE_EVENTS_KEY if path == :all
|
41
|
+
|
42
|
+
EventHandlerSubscription.new(self, path, block, interests).tap do |subscription|
|
39
43
|
synchronize { @callbacks[path] << subscription }
|
40
44
|
end
|
41
45
|
end
|
@@ -98,14 +102,16 @@ module ZK
|
|
98
102
|
#
|
99
103
|
# @private
|
100
104
|
def process(event)
|
105
|
+
@zk.raw_event_handler(event)
|
106
|
+
|
101
107
|
# logger.debug { "EventHandler#process dispatching event: #{event.inspect}" }# unless event.type == -1
|
102
108
|
event.zk = @zk
|
103
109
|
|
104
|
-
|
110
|
+
cb_keys =
|
105
111
|
if event.node_event?
|
106
|
-
event.path
|
112
|
+
[event.path, ALL_NODE_EVENTS_KEY]
|
107
113
|
elsif event.state_event?
|
108
|
-
state_key(event.state)
|
114
|
+
[state_key(event.state)]
|
109
115
|
else
|
110
116
|
raise ZKError, "don't know how to process event: #{event.inspect}"
|
111
117
|
end
|
@@ -113,23 +119,40 @@ module ZK
|
|
113
119
|
# logger.debug { "EventHandler#process: cb_key: #{cb_key}" }
|
114
120
|
|
115
121
|
cb_ary = synchronize do
|
116
|
-
|
117
|
-
if watch_type = ZOOKEEPER_WATCH_TYPE_MAP[event.type]
|
118
|
-
# logger.debug { "re-allowing #{watch_type.inspect} watches on path #{event.path.inspect}" }
|
119
|
-
|
120
|
-
# we recieved a watch event for this path, now we allow code to set new watchers
|
121
|
-
@outstanding_watches[watch_type].delete(event.path)
|
122
|
-
end
|
123
|
-
end
|
122
|
+
clear_watch_restrictions(event)
|
124
123
|
|
125
|
-
@callbacks
|
124
|
+
@callbacks.values_at(*cb_keys)
|
126
125
|
end
|
127
126
|
|
127
|
+
cb_ary.flatten! # takes care of not modifying original arrays
|
128
128
|
cb_ary.compact!
|
129
129
|
|
130
|
+
# we only filter for node events
|
131
|
+
if event.node_event?
|
132
|
+
interest_key = event.interest_key
|
133
|
+
cb_ary.select! { |sub| sub.interests.include?(interest_key) }
|
134
|
+
end
|
135
|
+
|
130
136
|
safe_call(cb_ary, event)
|
131
137
|
end
|
132
138
|
|
139
|
+
private
|
140
|
+
# happens inside the lock, clears the restriction on setting new watches
|
141
|
+
# for a given path/event type combination
|
142
|
+
#
|
143
|
+
def clear_watch_restrictions(event)
|
144
|
+
return unless event.node_event?
|
145
|
+
|
146
|
+
if watch_type = ZOOKEEPER_WATCH_TYPE_MAP[event.type]
|
147
|
+
#logger.debug { "re-allowing #{watch_type.inspect} watches on path #{event.path.inspect}" }
|
148
|
+
|
149
|
+
# we recieved a watch event for this path, now we allow code to set new watchers
|
150
|
+
@outstanding_watches[watch_type].delete(event.path)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
public
|
155
|
+
|
133
156
|
# used during shutdown to clear registered listeners
|
134
157
|
# @private
|
135
158
|
def clear! #:nodoc:
|
@@ -137,7 +160,7 @@ module ZK
|
|
137
160
|
@callbacks.clear
|
138
161
|
nil
|
139
162
|
end
|
140
|
-
end
|
163
|
+
end
|
141
164
|
|
142
165
|
# @private
|
143
166
|
def synchronize
|
@@ -171,8 +194,9 @@ module ZK
|
|
171
194
|
# fired. This prevents one event delivery to *every* callback per :watch => true
|
172
195
|
# argument.
|
173
196
|
#
|
174
|
-
# due to
|
175
|
-
# and the client implictly knows this
|
197
|
+
# due to arguably poor design, we destructively modify opts before we yield
|
198
|
+
# and the client implictly knows this (this method constitutes some of the option
|
199
|
+
# parsing for the base class methods)
|
176
200
|
#
|
177
201
|
# @private
|
178
202
|
def setup_watcher!(watch_type, opts)
|
@@ -228,15 +252,16 @@ module ZK
|
|
228
252
|
|
229
253
|
# @private
|
230
254
|
def safe_call(callbacks, *args)
|
231
|
-
while cb = callbacks.shift
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
255
|
+
# oddly, a `while cb = callbacks.shift` here will have thread safety issues
|
256
|
+
# as cb will be nil when the defer block is called on the threadpool
|
257
|
+
|
258
|
+
callbacks.each do |cb|
|
259
|
+
next unless cb.respond_to?(:call)
|
260
|
+
|
261
|
+
zk.defer { cb.call(*args) }
|
238
262
|
end
|
239
263
|
end
|
240
|
-
|
241
|
-
end
|
264
|
+
|
265
|
+
end # EventHandler
|
266
|
+
end # ZK
|
242
267
|
|