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 CHANGED
@@ -1 +1 @@
1
- rvm jruby-1.6.7@zk --create
1
+ rvm 1.9.3@zk --create
data/.yardopts CHANGED
@@ -2,6 +2,7 @@
2
2
  --no-private
3
3
  --tag hidden_example:Hidden
4
4
  --hide-tag hidden_example
5
+ --markup markdown
5
6
  -
6
7
  LICENSE
7
8
  README.markdown
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 (useful for functional testing)
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
- [dev/yard]: https://github.com/slyphon/zk/tree/dev%2Fyard
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
- ### Related Projects
82
-
83
- There are a few related projects that extend ZK.
90
+ ## Contacting the author
84
91
 
85
- * [ZK::Znode][]: a simple ORM to provide ActiveModel semantics around znodes. While still in early development, may also be a useful example of how to use ZK.
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
- [ZK::Znode]: https://github.com/slyphon/zk-znode
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
@@ -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::EventHandler#register
35
+ # @see ZK::Client::Base#register
8
36
  attr_reader :event_handler
9
37
 
10
- # @private the wrapped connection object
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
- @cnx.state == Java::OrgApacheZookeeper::ZooKeeper::States::CLOSED
80
+ cnx.state == Java::OrgApacheZookeeper::ZooKeeper::States::CLOSED
34
81
  end
35
82
 
36
83
  # @private
37
84
  def mri_closed?
38
- @cnx.closed?
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=10)
46
- @cnx.reopen(timeout, @event_handler.get_default_watcher_block)
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 { @cnx.close unless @cnx.closed? }
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 +ZOO_*+ constant defined under the
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 +:callback+
100
- # given as the +context+ param
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 +:mode+ *and* either of
104
- # the +:ephermeral+ or +:sequential+ options are given, the +:mode+ option will win
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 +path+ already exists
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(@cnx.create(h), h)
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 +:watch+ is true and the call is successful (no exception is
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 +watcher+ for documentation on how to register
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 +:callback+
237
- # given as the +context+ param
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(@cnx.get(h), h)
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 +:callback+
312
- # given as the +context+ param
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(@cnx.set(h), h)
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 +:callback+
363
- # given as the +context+ param
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 = @cnx.stat(h)
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 +watcher+
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 +:callback+
458
- # given as the +context+ param
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(@cnx.get_children(h), h)
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 +:callback+
528
- # given as the +context+ param
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(@cnx.delete(h), h)
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 +:callback+
571
- # given as the +context+ param
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(@cnx.get_acl(h), h)
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 +:callback+
623
- # given as the +context+ param
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(@cnx.set_acl(h), h)
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
- @cnx.set_debug_level(num)
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
- @cnx.session_id
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
- @cnx.session_passwd
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
+