zk 0.8.9 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.dotfiles/rvmrc +1 -1
- data/.yardopts +1 -0
- data/Gemfile +11 -1
- data/README.markdown +19 -10
- data/RELEASES.markdown +14 -0
- data/lib/z_k/client/base.rb +171 -46
- data/lib/z_k/client/continuation_proxy.rb +99 -0
- data/lib/z_k/client/conveniences.rb +10 -13
- data/lib/z_k/client/drop_box.rb +98 -0
- data/lib/z_k/client/multiplexed.rb +28 -0
- data/lib/z_k/client/threaded.rb +43 -13
- data/lib/z_k/client/unixisms.rb +74 -14
- data/lib/z_k/client.rb +3 -0
- data/lib/z_k/event_handler.rb +5 -45
- data/lib/z_k/event_handler_subscription.rb +13 -6
- data/lib/z_k/exceptions.rb +22 -2
- data/lib/z_k/extensions.rb +10 -2
- data/lib/z_k/find.rb +5 -2
- data/lib/z_k/locker.rb +9 -4
- data/lib/z_k/pool.rb +24 -6
- data/lib/z_k/version.rb +1 -1
- data/lib/z_k.rb +1 -2
- data/spec/shared/client_contexts.rb +15 -0
- data/spec/shared/client_examples.rb +155 -0
- data/spec/spec_helper.rb +9 -53
- data/spec/support/logging.rb +27 -0
- data/spec/support/special_happy_funtime_error.rb +6 -0
- data/spec/support/wait_watchers.rb +48 -0
- data/spec/watch_spec.rb +19 -4
- data/spec/z_k/client/drop_box_spec.rb +90 -0
- data/spec/z_k/client/locking_and_session_death_spec.rb +108 -0
- data/spec/z_k/client/multiplexed_spec.rb +20 -0
- data/spec/z_k/client_spec.rb +3 -231
- data/spec/z_k/election_spec.rb +12 -11
- data/spec/z_k/locker_spec.rb +69 -3
- data/spec/zookeeper_spec.rb +3 -3
- data/zk.gemspec +1 -2
- metadata +28 -8
data/.dotfiles/rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm
|
1
|
+
rvm 1.9.3@zk --create
|
data/.yardopts
CHANGED
data/Gemfile
CHANGED
@@ -2,15 +2,25 @@
|
|
2
2
|
source ENV['MBOX_BUNDLER_SOURCE'] if ENV['MBOX_BUNDLER_SOURCE']
|
3
3
|
source "http://rubygems.org"
|
4
4
|
|
5
|
+
# gem 'slyphon-zookeeper', :path => '~/zookeeper'
|
6
|
+
|
5
7
|
group :development do
|
6
8
|
gem 'pry'
|
9
|
+
gem 'rake'
|
10
|
+
end
|
11
|
+
|
12
|
+
group :docs do
|
13
|
+
gem 'yard', '~> 0.7.5'
|
14
|
+
|
15
|
+
platform :mri_19 do
|
16
|
+
gem 'redcarpet'
|
17
|
+
end
|
7
18
|
end
|
8
19
|
|
9
20
|
group :test do
|
10
21
|
gem 'rspec', '~> 2.8.0'
|
11
22
|
gem 'flexmock', '~> 0.8.10'
|
12
23
|
gem 'ZenTest', '~> 4.5.0'
|
13
|
-
gem 'rake'
|
14
24
|
end
|
15
25
|
|
16
26
|
# Specify your gem's dependencies in zk.gemspec
|
data/README.markdown
CHANGED
@@ -2,10 +2,14 @@
|
|
2
2
|
|
3
3
|
ZK is a high-level interface to the Apache [ZooKeeper][] server. It is based on the [zookeeper gem][] which is a multi-Ruby low-level driver. Currently MRI 1.8.7, 1.9.2, 1.9.3, and JRuby are supported (rubinius 1.2 is experimental but _should_ work). It is licensed under the [MIT][] license.
|
4
4
|
|
5
|
+
The key place to start in the documentation is with ZK::Client::Base ([rubydoc.info][ZK::Client::Base], [local](/docs/ZK/Client/Base)
|
6
|
+
).
|
7
|
+
|
5
8
|
This library is heavily used in a production deployment and is actively developed and maintained.
|
6
9
|
|
7
10
|
Development is sponsored by [Snapfish][] and has been generously released to the Open Source community by HPDC, L.P.
|
8
11
|
|
12
|
+
[ZK::Client::Base]: http://rubydoc.info/gems/zk/ZK/Client/Base
|
9
13
|
[ZooKeeper]: http://zookeeper.apache.org/ "Apache ZooKeeper"
|
10
14
|
[zookeeper gem]: https://github.com/slyphon/zookeeper "slyphon-zookeeper gem"
|
11
15
|
[MIT]: http://www.gnu.org/licenses/license-list.html#Expat "MIT (Expat) License"
|
@@ -28,7 +32,7 @@ ZooKeeper is also (relatively) easy to deploy in a [Highly Available][ha-config]
|
|
28
32
|
|
29
33
|
## What does ZK do that the zookeeper gem doesn't?
|
30
34
|
|
31
|
-
The [zookeeper gem][] provides a low-level, cross platform library for interfacing with ZooKeeper. While it is full featured, it only handles the basic operations that the driver provides. ZK implements the majority of the [recipes][] in the ZooKeeper documentation, plus a number of other conveniences for a production environment.
|
35
|
+
The [zookeeper gem][] provides a low-level, cross platform library for interfacing with ZooKeeper. While it is full featured, it only handles the basic operations that the driver provides. ZK implements the majority of the [recipes][] in the ZooKeeper documentation, plus a number of other conveniences for a production environment. ZK aims to be to Zookeeper, as Sequel or ActiveRecord is to the MySQL or Postgres drivers (not that ZK is attempting to provide an object persistence system, but rather a higher level API that users can develop applications with).
|
32
36
|
|
33
37
|
ZK provides:
|
34
38
|
|
@@ -38,7 +42,7 @@ ZK provides:
|
|
38
42
|
* a simple threadpool implementation
|
39
43
|
* a bounded, dynamically-growable (threadsafe) client pool implementation
|
40
44
|
* a recursive Find class (like the Find module in ruby-core)
|
41
|
-
* unix-like rm\_rf and mkdir\_p methods
|
45
|
+
* unix-like rm\_rf and mkdir\_p methods
|
42
46
|
* an extension for the [Mongoid][] ORM to provide advisory locks on mongodb records
|
43
47
|
|
44
48
|
In addition to all of that, I would like to think that the public API the ZK::Client provides is more convenient to use for the common (synchronous) case. For use with [EventMachine][] there is [zk-eventmachine][] which provides a convenient API for writing evented code that uses the ZooKeeper server.
|
@@ -52,19 +56,24 @@ In addition to all of that, I would like to think that the public API the ZK::Cl
|
|
52
56
|
|
53
57
|
ZK strives to be a complete, correct, and convenient way of interacting with ZooKeeper. There are a few weak points in the implementation:
|
54
58
|
|
59
|
+
* Starting with 0.9 there is only *one* event dispatch thread (in 0.8 there are 5). It is *very important* that you don't block the event delivery thread.
|
60
|
+
|
61
|
+
* If you're not familiar with developing solutions with zookeeper, you should read about [sessions][] and [watches][] in the Programmer's Guide. Even if you *are* familiar, you should probably go read it again.
|
62
|
+
|
63
|
+
* It is very important that you not ignore connection state events if you're using watches.
|
64
|
+
|
55
65
|
* _ACLS: HOW DO THEY WORK?!_ ACL support is mainly faith-based now. I have not had a need for ACLs, and the authors of the upstream [twitter/zookeeper][] code also don't seem to have much experience with them/use for them (purely my opinion, no offense intended). If you are using ACLs and you find bugs or have suggestions, I would much appreciate feedback or examples of how they *should* work so that support and tests can be added.
|
56
66
|
|
57
67
|
* ZK::Client supports asynchronous calls of all basic methods (get, set, delete, etc.) however these versions are kind of inconvenient to use. For a fully evented stack, try [zk-eventmachine][], which is designed to be compatible and convenient to use in event-driven code.
|
58
68
|
|
59
69
|
* ZooKeeper "chroot" [connection syntax][chroot] is currently being developed and should work for most cases. Right now we require that the root path exist before the chrooted client is used, but that may change [in the near future](https://github.com/slyphon/zk/issues/7).
|
60
70
|
|
61
|
-
* I am currently in the process of cleaning up the API documentation and converting it to use [YARD][].
|
62
|
-
|
63
71
|
[twitter/zookeeper]: https://github.com/twitter/zookeeper
|
64
72
|
[async-branch]: https://github.com/slyphon/zk/tree/dev%2Fasync-conveniences
|
65
73
|
[chroot]: http://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#ch_zkSessions
|
66
74
|
[YARD]: http://yardoc.org/
|
67
|
-
[
|
75
|
+
[sessions]: http://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#ch_zkSessions
|
76
|
+
[watches]: http://zookeeper.apache.org/doc/r3.3.5/zookeeperProgrammers.html#ch_zkWatches
|
68
77
|
|
69
78
|
## Dependencies
|
70
79
|
|
@@ -78,11 +87,11 @@ ZK strives to be a complete, correct, and convenient way of interacting with Zoo
|
|
78
87
|
[szk-jar-gem]: https://rubygems.org/gems/slyphon-zookeeper_jar
|
79
88
|
[szk-jar-repo]: https://github.com/slyphon/zookeeper_jar
|
80
89
|
|
81
|
-
|
82
|
-
|
83
|
-
There are a few related projects that extend ZK.
|
90
|
+
## Contacting the author
|
84
91
|
|
85
|
-
*
|
92
|
+
* Send me a github message (slyphon)
|
93
|
+
* I'm usually hanging out in IRC on freenode.net in #ruby-lang and in #zookeeper
|
94
|
+
* if you really want to, you can also reach me via twitter [@slyphon][]
|
86
95
|
|
87
|
-
[
|
96
|
+
[@slyphon]: https://twitter.com/#!/slyphon
|
88
97
|
|
data/RELEASES.markdown
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
This file notes feature differences and bugfixes contained between releases.
|
2
|
+
|
3
|
+
### v0.9.0 ###
|
4
|
+
|
5
|
+
* Default threadpool size has been changed from 5 to 1. This should only affect people who are using the Election code.
|
6
|
+
* `ZK::Client::Base#register` delegates to its `event_handler` for convenience (so you can write `zk.register` instead of `zk.event_handler.register`, which always irked me)
|
7
|
+
* `ZK::Client::Base#event_dispatch_thread?` added to more easily allow users to tell if they're currently in the event thread (and possibly make decisions about the safety of their actions). This is now used by `block_until_node_deleted` in the Unixisms module, and prevents a situation where the user could deadlock event delivery.
|
8
|
+
* Fixed issue 9, where using a Locker in the main thread would never awaken if the connection was dropped or interrupted. Now a `ZK::Exceptions::InterruptedSession` exception (or mixee) will be thrown to alert the caller that something bad happened.
|
9
|
+
* `ZK::Find.find` now returns the results in sorted order.
|
10
|
+
* Added documentation explaining the Pool class, reasons for using it, reasons why you shouldn't (added complexities around watchers and events).
|
11
|
+
* Began work on an experimental Multiplexed client, that would allow multithreaded clients to more effectively share a single connection by making all requests asynchronous behind the scenes, and using a queue to provide a synchronous (blocking) API.
|
12
|
+
|
13
|
+
|
14
|
+
# vim:ft=markdown
|
data/lib/z_k/client/base.rb
CHANGED
@@ -1,17 +1,47 @@
|
|
1
1
|
module ZK
|
2
2
|
module Client
|
3
|
+
|
4
|
+
# This class forms the base API for interacting with ZooKeeper. Most people will
|
5
|
+
# want to create instances of the class ZK::Client::Threaded, and the most
|
6
|
+
# convenient way of doing that is through the top-level method `ZK.new`
|
7
|
+
#
|
8
|
+
# @note There is a lot of functionality mixed into the subclasses of this
|
9
|
+
# class! You should take a look at {Unixisms}, {Conveniences}, and
|
10
|
+
# {StateMixin} for a lot of the higher-level functionality!
|
11
|
+
#
|
12
|
+
# @example Create a new default connection
|
13
|
+
#
|
14
|
+
# # if no host:port is given, we connect to localhost:2181 by default
|
15
|
+
# # (convenient for use in tests and in irb/pry)
|
16
|
+
#
|
17
|
+
# zk = ZK.new
|
18
|
+
#
|
19
|
+
# @example Create a new connection, specifying host
|
20
|
+
#
|
21
|
+
# zk = ZK.new('localhost:2181')
|
22
|
+
#
|
23
|
+
# @example For quick tasks, you can use the visitor pattern, (like the File class)
|
24
|
+
#
|
25
|
+
# ZK.open('localhost:2181') do |zk|
|
26
|
+
# # do stuff with connection
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # connection is automatically closed
|
30
|
+
#
|
3
31
|
class Base
|
4
32
|
# The Eventhandler is used by client code to register callbacks to handle
|
5
33
|
# events triggerd for given paths.
|
6
34
|
#
|
7
|
-
# @see ZK::
|
35
|
+
# @see ZK::Client::Base#register
|
8
36
|
attr_reader :event_handler
|
9
37
|
|
10
|
-
#
|
38
|
+
# the wrapped connection object
|
39
|
+
# @private
|
11
40
|
attr_reader :cnx
|
12
|
-
|
41
|
+
protected :cnx
|
13
42
|
|
14
43
|
# @deprecated for backwards compatibility only
|
44
|
+
# use ZK::Client::Base#event_handler instead
|
15
45
|
def watcher
|
16
46
|
event_handler
|
17
47
|
end
|
@@ -24,26 +54,44 @@ module ZK
|
|
24
54
|
|
25
55
|
# @private
|
26
56
|
def inspect
|
27
|
-
"#<#{self.class.name}:#{object_id} ...>"
|
57
|
+
"#<#{self.class.name}:#{object_id} zk_session_id=#{safe_session_id} ...>"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Create a new client and connect to the zookeeper server.
|
61
|
+
#
|
62
|
+
# @param [String] host should be a string of comma-separated host:port
|
63
|
+
# pairs. You can also supply an optional "chroot" suffix that will act as
|
64
|
+
# an implicit prefix to all paths supplied.
|
65
|
+
#
|
66
|
+
# @see ZK::Client::Threaded#initialize valid options to use with the
|
67
|
+
# synchronous (non-evented) client
|
68
|
+
#
|
69
|
+
# @example Threaded client with two hosts and a chroot path
|
70
|
+
#
|
71
|
+
# ZK::Client.new("zk01:2181,zk02:2181/chroot/path")
|
72
|
+
#
|
73
|
+
def initialize(host, opts={})
|
74
|
+
# no-op
|
28
75
|
end
|
29
76
|
|
30
77
|
private
|
31
78
|
# @private
|
32
79
|
def jruby_closed?
|
33
|
-
|
80
|
+
cnx.state == Java::OrgApacheZookeeper::ZooKeeper::States::CLOSED
|
34
81
|
end
|
35
82
|
|
36
83
|
# @private
|
37
84
|
def mri_closed?
|
38
|
-
|
85
|
+
cnx.closed?
|
39
86
|
end
|
40
87
|
|
41
88
|
public
|
42
89
|
|
43
90
|
# reopen the underlying connection
|
44
91
|
# returns state of connection after operation
|
45
|
-
def reopen(timeout=
|
46
|
-
|
92
|
+
def reopen(timeout=nil)
|
93
|
+
timeout ||= @session_timeout # XXX: @session_timeout ?
|
94
|
+
cnx.reopen(timeout)
|
47
95
|
@threadpool.start! # restart the threadpool if previously stopped by close!
|
48
96
|
state
|
49
97
|
end
|
@@ -51,7 +99,7 @@ module ZK
|
|
51
99
|
# close the underlying connection and clear all pending events.
|
52
100
|
def close!
|
53
101
|
event_handler.clear!
|
54
|
-
wrap_state_closed_error {
|
102
|
+
wrap_state_closed_error { cnx.close unless cnx.closed? }
|
55
103
|
end
|
56
104
|
|
57
105
|
# Create a node with the given path. The node data will be the given data.
|
@@ -86,7 +134,7 @@ module ZK
|
|
86
134
|
# @param [String] data the data to create the znode with
|
87
135
|
#
|
88
136
|
# @option opts [Integer] :acl defaults to <tt>ZookeeperACLs::ZOO_OPEN_ACL_UNSAFE</tt>,
|
89
|
-
# otherwise the ACL for the node. Should be a
|
137
|
+
# otherwise the ACL for the node. Should be a `ZOO_*` constant defined under the
|
90
138
|
# ZookeeperACLs module in the zookeeper gem.
|
91
139
|
#
|
92
140
|
# @option opts [bool] :ephemeral (false) if true, the created node will be ephemeral
|
@@ -96,14 +144,14 @@ module ZK
|
|
96
144
|
# @option opts [ZookeeperCallbacks::StringCallback] :callback (nil) provide a callback object
|
97
145
|
# that will be called when the znode has been created
|
98
146
|
#
|
99
|
-
# @option opts [Object] :context (nil) an object passed to the
|
100
|
-
# given as the
|
147
|
+
# @option opts [Object] :context (nil) an object passed to the `:callback`
|
148
|
+
# given as the `context` param
|
101
149
|
#
|
102
150
|
# @option opts [:ephemeral_sequential, :persistent_sequential, :persistent, :ephemeral] :mode (nil)
|
103
|
-
# may be specified instead of :ephemeral and :sequence options. If
|
104
|
-
# the
|
151
|
+
# may be specified instead of :ephemeral and :sequence options. If `:mode` *and* either of
|
152
|
+
# the `:ephermeral` or `:sequential` options are given, the `:mode` option will win
|
105
153
|
#
|
106
|
-
# @raise [ZK::Exceptions::NodeExists] if a node with the same
|
154
|
+
# @raise [ZK::Exceptions::NodeExists] if a node with the same `path` already exists
|
107
155
|
#
|
108
156
|
# @raise [ZK::Exceptions::NoNode] if the parent node does not exist
|
109
157
|
#
|
@@ -209,17 +257,17 @@ module ZK
|
|
209
257
|
end
|
210
258
|
end
|
211
259
|
|
212
|
-
rv = check_rc(
|
260
|
+
rv = check_rc(cnx.create(h), h)
|
213
261
|
|
214
262
|
h[:callback] ? rv : rv[:path]
|
215
263
|
end
|
216
264
|
|
217
265
|
# Return the data and stat of the node of the given path.
|
218
266
|
#
|
219
|
-
# If
|
267
|
+
# If `:watch` is true and the call is successful (no exception is
|
220
268
|
# raised), registered watchers on the node will be 'armed'. The watch
|
221
269
|
# will be triggered by a successful operation that sets data on the node,
|
222
|
-
# or deletes the node. See
|
270
|
+
# or deletes the node. See `watcher` for documentation on how to register
|
223
271
|
# blocks to be called when a watch event is fired.
|
224
272
|
#
|
225
273
|
# @todo fix references to Watcher documentation
|
@@ -233,8 +281,8 @@ module ZK
|
|
233
281
|
#
|
234
282
|
# @option opts [ZookeeperCallbacks::DataCallback] :callback to make this call asynchronously
|
235
283
|
#
|
236
|
-
# @option opts [Object] :context an object passed to the
|
237
|
-
# given as the
|
284
|
+
# @option opts [Object] :context an object passed to the `:callback`
|
285
|
+
# given as the `context` param
|
238
286
|
#
|
239
287
|
# @return [Array] a two-element array of ['node data', #<ZookeeperStat::Stat>]
|
240
288
|
#
|
@@ -271,7 +319,7 @@ module ZK
|
|
271
319
|
|
272
320
|
setup_watcher!(:data, h)
|
273
321
|
|
274
|
-
rv = check_rc(
|
322
|
+
rv = check_rc(cnx.get(h), h)
|
275
323
|
|
276
324
|
opts[:callback] ? rv : rv.values_at(:data, :stat)
|
277
325
|
end
|
@@ -308,8 +356,8 @@ module ZK
|
|
308
356
|
# @option opts [ZookeeperCallbacks::StatCallback] :callback will recieve the
|
309
357
|
# ZookeeperStat::Stat object asynchronously
|
310
358
|
#
|
311
|
-
# @option opts [Object] :context an object passed to the
|
312
|
-
# given as the
|
359
|
+
# @option opts [Object] :context an object passed to the `:callback`
|
360
|
+
# given as the `context` param
|
313
361
|
#
|
314
362
|
# @example unconditionally set the data of "/path"
|
315
363
|
#
|
@@ -335,7 +383,7 @@ module ZK
|
|
335
383
|
|
336
384
|
h = { :path => path, :data => data }.merge(opts)
|
337
385
|
|
338
|
-
rv = check_rc(
|
386
|
+
rv = check_rc(cnx.set(h), h)
|
339
387
|
|
340
388
|
opts[:callback] ? rv : rv[:stat]
|
341
389
|
end
|
@@ -359,8 +407,8 @@ module ZK
|
|
359
407
|
# @option opts [ZookeeperCallbacks::StatCallback] :callback will recieve the
|
360
408
|
# ZookeeperStat::Stat object asynchronously
|
361
409
|
#
|
362
|
-
# @option opts [Object] :context an object passed to the
|
363
|
-
# given as the
|
410
|
+
# @option opts [Object] :context an object passed to the `:callback`
|
411
|
+
# given as the `context` param
|
364
412
|
#
|
365
413
|
# @return [ZookeeperStat::Stat] a stat object of the specified node
|
366
414
|
#
|
@@ -399,7 +447,7 @@ module ZK
|
|
399
447
|
|
400
448
|
setup_watcher!(:data, h)
|
401
449
|
|
402
|
-
rv =
|
450
|
+
rv = cnx.stat(h)
|
403
451
|
|
404
452
|
return rv if opts[:callback]
|
405
453
|
|
@@ -440,9 +488,12 @@ module ZK
|
|
440
488
|
# If the watch is true and the call is successful (no exception is thrown),
|
441
489
|
# registered watchers of the children of the node will be enabled. The
|
442
490
|
# watch will be triggered by a successful operation that deletes the node
|
443
|
-
# of the given path or creates/delete a child under the node. See
|
491
|
+
# of the given path or creates/delete a child under the node. See `watcher`
|
444
492
|
# for documentation on how to register blocks to be called when a watch
|
445
493
|
# event is fired.
|
494
|
+
#
|
495
|
+
# @note It is important to note that the list of children is _not sorted_. If you
|
496
|
+
# need them to be ordered, you must call `.sort` on the returned array
|
446
497
|
#
|
447
498
|
# @raise [ZK::Exceptions::NoNode] if the node does not exist
|
448
499
|
#
|
@@ -454,8 +505,8 @@ module ZK
|
|
454
505
|
# @option opts [ZookeeperCallbacks::StringsCallback] :callback to make this
|
455
506
|
# call asynchronously
|
456
507
|
#
|
457
|
-
# @option opts [Object] :context an object passed to the
|
458
|
-
# given as the
|
508
|
+
# @option opts [Object] :context an object passed to the `:callback`
|
509
|
+
# given as the `context` param
|
459
510
|
#
|
460
511
|
# @example get children for path
|
461
512
|
#
|
@@ -490,7 +541,7 @@ module ZK
|
|
490
541
|
|
491
542
|
setup_watcher!(:child, h)
|
492
543
|
|
493
|
-
rv = check_rc(
|
544
|
+
rv = check_rc(cnx.get_children(h), h)
|
494
545
|
opts[:callback] ? rv : rv[:children]
|
495
546
|
end
|
496
547
|
|
@@ -505,9 +556,6 @@ module ZK
|
|
505
556
|
# Can be called with just the path, otherwise a hash with the arguments
|
506
557
|
# set. Supports being executed asynchronousy by passing a callback object.
|
507
558
|
#
|
508
|
-
# A KeeperException with error code KeeperException::NotEmpty will be
|
509
|
-
# thrown if the node has children.
|
510
|
-
#
|
511
559
|
# @raise [ZK::Exceptions::NoNode] raised if no node with the given path exists
|
512
560
|
#
|
513
561
|
# @raise [ZK::Exceptions::BadVersion] raised if the given version does not
|
@@ -524,8 +572,8 @@ module ZK
|
|
524
572
|
# @option opts [ZookeeperCallbacks::VoidCallback] :callback will be called
|
525
573
|
# asynchronously when the operation is complete
|
526
574
|
#
|
527
|
-
# @option opts [Object] :context an object passed to the
|
528
|
-
# given as the
|
575
|
+
# @option opts [Object] :context an object passed to the `:callback`
|
576
|
+
# given as the `context` param
|
529
577
|
#
|
530
578
|
# @example delete a node
|
531
579
|
# zk.delete("/path")
|
@@ -549,7 +597,7 @@ module ZK
|
|
549
597
|
|
550
598
|
|
551
599
|
h = { :path => path, :version => -1 }.merge(opts)
|
552
|
-
rv = check_rc(
|
600
|
+
rv = check_rc(cnx.delete(h), h)
|
553
601
|
opts[:callback] ? rv : nil
|
554
602
|
end
|
555
603
|
|
@@ -567,8 +615,8 @@ module ZK
|
|
567
615
|
# @option opts [ZookeeperCallback::AclCallback] (nil) :callback for an
|
568
616
|
# asynchronous call to occur
|
569
617
|
#
|
570
|
-
# @option opts [Object] :context (nil) an object passed to the
|
571
|
-
# given as the
|
618
|
+
# @option opts [Object] :context (nil) an object passed to the `:callback`
|
619
|
+
# given as the `context` param
|
572
620
|
#
|
573
621
|
# @example get acl
|
574
622
|
#
|
@@ -595,7 +643,7 @@ module ZK
|
|
595
643
|
# zk.acls("/path", :callback => callback, :context => context)
|
596
644
|
|
597
645
|
h = { :path => path }.merge(opts)
|
598
|
-
rv = check_rc(
|
646
|
+
rv = check_rc(cnx.get_acl(h), h)
|
599
647
|
opts[:callback] ? rv : rv.values_at(:children, :stat)
|
600
648
|
end
|
601
649
|
|
@@ -619,14 +667,14 @@ module ZK
|
|
619
667
|
# @option opts [ZookeeperCallbacks::VoidCallback] :callback will be called
|
620
668
|
# asynchronously when the operation is complete
|
621
669
|
#
|
622
|
-
# @option opts [Object] :context an object passed to the
|
623
|
-
# given as the
|
670
|
+
# @option opts [Object] :context an object passed to the `:callback`
|
671
|
+
# given as the `context` param
|
624
672
|
#
|
625
673
|
# @todo: TBA - waiting on clarification of method use
|
626
674
|
#
|
627
675
|
def set_acl(path, acls, opts={})
|
628
676
|
h = { :path => path, :acl => acls }.merge(opts)
|
629
|
-
rv = check_rc(
|
677
|
+
rv = check_rc(cnx.set_acl(h), h)
|
630
678
|
opts[:callback] ? rv : rv[:stat]
|
631
679
|
end
|
632
680
|
|
@@ -647,21 +695,88 @@ module ZK
|
|
647
695
|
|
648
696
|
raise ArgumentError, "#{level.inspect} is not a valid argument to set_debug_level" unless num
|
649
697
|
|
650
|
-
|
698
|
+
cnx.set_debug_level(num)
|
651
699
|
end
|
652
700
|
end
|
653
701
|
|
654
702
|
# returns the session_id of the underlying connection
|
655
703
|
def session_id
|
656
|
-
|
704
|
+
cnx.session_id
|
657
705
|
end
|
658
706
|
|
659
707
|
# returns the session_passwd of the underlying connection
|
660
708
|
def session_passwd
|
661
|
-
|
709
|
+
cnx.session_passwd
|
710
|
+
end
|
711
|
+
|
712
|
+
# Register a block that should be delivered events for a given path. After
|
713
|
+
# registering a block, you need to call {#get}, {#stat}, or {#children} with the
|
714
|
+
# `:watch => true` option for the block to receive _the next event_ (see note).
|
715
|
+
# {#get} and {#stat} will cause the block to receive events when the path is
|
716
|
+
# created, deleted, or its data is changed. {#children} will cause the block to
|
717
|
+
# receive events about its list of child nodes changing (i.e. being added
|
718
|
+
# or deleted, but *not* their content changing).
|
719
|
+
#
|
720
|
+
# This method will return an {EventHandlerSubscription} instance that can be used
|
721
|
+
# to remove the block from further updates by calling its `.unsubscribe` method.
|
722
|
+
#
|
723
|
+
# @note All node watchers are one-shot handlers. After an event is delivered to
|
724
|
+
# your handler, you *must* re-watch the node to receive more events. This
|
725
|
+
# leads to a pattern you will find throughout ZK code that avoids races,
|
726
|
+
# see the example below "avoiding a race"
|
727
|
+
#
|
728
|
+
# @example avoiding a race waiting for a node to be deleted
|
729
|
+
#
|
730
|
+
# # we expect that '/path/to/node' exists currently and want to be notified
|
731
|
+
# # when it's deleted
|
732
|
+
#
|
733
|
+
# # register a handler that will be called back when an event occurs on
|
734
|
+
# # node
|
735
|
+
# #
|
736
|
+
# node_subscription = zk.register('/path/to/node') do |event|
|
737
|
+
# if event.node_deleted?
|
738
|
+
# do_something_when_node_deleted
|
739
|
+
# end
|
740
|
+
# end
|
741
|
+
#
|
742
|
+
# # check to see if our condition is true *while* setting a watch on the node
|
743
|
+
# # if our condition happens to be true while setting the watch
|
744
|
+
# #
|
745
|
+
# unless exists?('/path/to/node', :watch => true)
|
746
|
+
# node_subscription.unsubscribe # cancel the watch
|
747
|
+
# do_something_when_node_deleted # call the callback
|
748
|
+
# end
|
749
|
+
#
|
750
|
+
#
|
751
|
+
# @param [String] path the path you want to listen to
|
752
|
+
#
|
753
|
+
# @param [Block] block the block to execute when a watch event happpens
|
754
|
+
#
|
755
|
+
# @yield [event] We will call your block with the watch event object (which
|
756
|
+
# has the connection the event occurred on as its #zk attribute)
|
757
|
+
#
|
758
|
+
# @return [EventHandlerSubscription] the subscription object
|
759
|
+
# you can use to to unsubscribe from an event
|
760
|
+
#
|
761
|
+
# @see ZooKeeper::WatcherEvent
|
762
|
+
# @see ZK::EventHandlerSubscription
|
763
|
+
#
|
764
|
+
def register(path, &block)
|
765
|
+
event_handler.register(path, &block)
|
766
|
+
end
|
767
|
+
|
768
|
+
# returns true if the caller is calling from the event dispatch thread
|
769
|
+
def event_dispatch_thread?
|
770
|
+
cnx.event_dispatch_thread?
|
771
|
+
end
|
772
|
+
|
773
|
+
# @private
|
774
|
+
def assert_we_are_not_on_the_event_dispatch_thread!
|
775
|
+
raise Exceptions::EventDispatchThreadException, "blocking method called on dispatch thread" if event_dispatch_thread?
|
662
776
|
end
|
663
777
|
|
664
778
|
protected
|
779
|
+
# @private
|
665
780
|
def check_rc(hash, inputs=nil)
|
666
781
|
code = hash[:rc]
|
667
782
|
if code && (code != Zookeeper::ZOK)
|
@@ -672,9 +787,19 @@ module ZK
|
|
672
787
|
end
|
673
788
|
end
|
674
789
|
|
790
|
+
# @private
|
675
791
|
def setup_watcher!(watch_type, opts)
|
676
792
|
event_handler.setup_watcher!(watch_type, opts)
|
677
793
|
end
|
794
|
+
|
795
|
+
# used in #inspect, doesn't raise an error if we're not connected
|
796
|
+
def safe_session_id
|
797
|
+
if cnx and cnx.session_id
|
798
|
+
'0x%x' % cnx.session_id
|
799
|
+
end
|
800
|
+
rescue ZookeeperExceptions::ZookeeperException, ZK::Exceptions::KeeperException
|
801
|
+
nil
|
802
|
+
end
|
678
803
|
end # Base
|
679
804
|
end # Client
|
680
805
|
end # ZK
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module ZK
|
2
|
+
module Client
|
3
|
+
# Wraps calls to zookeeper so that the requests are made asynchronously,
|
4
|
+
# but still provides a blocking API
|
5
|
+
#
|
6
|
+
# @private
|
7
|
+
class ContinuationProxy
|
8
|
+
include ZK::Logging
|
9
|
+
|
10
|
+
attr_accessor :zookeeper_cnx
|
11
|
+
|
12
|
+
# @private
|
13
|
+
def self.call_with_continuation(*syms)
|
14
|
+
syms.each do |sym|
|
15
|
+
class_eval(<<-EOS, __FILE__, __LINE__+1)
|
16
|
+
def #{sym}(opts)
|
17
|
+
logger.debug { "_call_continue(#{sym.inspect}, \#{opts.inspect})" }
|
18
|
+
_call_continue(#{sym.inspect}, opts)
|
19
|
+
end
|
20
|
+
EOS
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
call_with_continuation :create, :get, :set, :stat, :children, :delete, :get_acl, :set_acl
|
25
|
+
|
26
|
+
def initialize(zookeeper_cnx=nil)
|
27
|
+
@zookeeper_cnx = zookeeper_cnx
|
28
|
+
@mutex = Mutex.new
|
29
|
+
@dropboxen = []
|
30
|
+
end
|
31
|
+
|
32
|
+
# called by the multiplxed client to wake up threads that are waiting for
|
33
|
+
# results (with an exception)
|
34
|
+
# @private
|
35
|
+
def connection_closed!
|
36
|
+
_oh_noes(ZookeeperExceptions::ZookeeperException::NotConnected, 'connection closed')
|
37
|
+
end
|
38
|
+
|
39
|
+
# called by the multiplxed client to wake up threads that are waiting for
|
40
|
+
# results (with an exception)
|
41
|
+
# @private
|
42
|
+
def expired_session!
|
43
|
+
_oh_noes(ZookeeperExceptions::ZookeeperException::SessionExpired, 'session expired')
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def method_missing(m, *a, &b)
|
48
|
+
@zookeeper_cnx.respond_to?(m) ? @zookeeper_cnx.__send__(m, *a, &b) : super
|
49
|
+
end
|
50
|
+
|
51
|
+
def _oh_noes(exception, message)
|
52
|
+
@mutex.synchronize do
|
53
|
+
@dropboxen.each do |db|
|
54
|
+
db.oh_noes!(exception, message)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# not really callcc, but close enough
|
60
|
+
# opts should be an options hash as passed through to the Zookeeper
|
61
|
+
# layer
|
62
|
+
def _call_continue(meth, opts)
|
63
|
+
_assert_not_async!(meth, opts)
|
64
|
+
|
65
|
+
opts = opts.dup
|
66
|
+
|
67
|
+
_with_drop_box do |db|
|
68
|
+
cb = lambda do |hash|
|
69
|
+
logger.debug { "#{self.class}##{__method__} block pushing: #{hash.inspect}" }
|
70
|
+
db.push(hash)
|
71
|
+
end
|
72
|
+
|
73
|
+
opts[:callback] = cb
|
74
|
+
|
75
|
+
@zookeeper_cnx.__send__(meth, opts)
|
76
|
+
|
77
|
+
db.pop.tap do |obj|
|
78
|
+
logger.debug { "#{self.class}##{__method__} popped and returning: #{obj.inspect}" }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def _with_drop_box
|
84
|
+
db = DropBox.current
|
85
|
+
@mutex.synchronize { @dropboxen << db }
|
86
|
+
yield db
|
87
|
+
ensure
|
88
|
+
@mutex.synchronize { @dropboxen.delete(db) }
|
89
|
+
db.clear
|
90
|
+
end
|
91
|
+
|
92
|
+
def _assert_not_async!(meth, opts)
|
93
|
+
return unless opts.has_key?(:callback)
|
94
|
+
raise ArgumentError, "you cannot use async callbacks with a Multiplexed client! meth: #{meth.inspect}, opts: #{opts.inspect}"
|
95
|
+
end
|
96
|
+
end # ContinuationProxy
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|