zk 0.9.1 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +2 -2
  2. data/Gemfile +3 -4
  3. data/README.markdown +14 -5
  4. data/RELEASES.markdown +69 -1
  5. data/Rakefile +50 -18
  6. data/docs/examples/block_until_node_deleted_ex.rb +63 -0
  7. data/docs/examples/events_01.rb +36 -0
  8. data/docs/examples/events_02.rb +41 -0
  9. data/lib/{z_k → zk}/client/base.rb +87 -30
  10. data/lib/{z_k → zk}/client/conveniences.rb +0 -1
  11. data/lib/{z_k → zk}/client/state_mixin.rb +0 -0
  12. data/lib/zk/client/threaded.rb +196 -0
  13. data/lib/{z_k → zk}/client/unixisms.rb +0 -0
  14. data/lib/zk/client.rb +59 -0
  15. data/lib/zk/core_ext.rb +75 -0
  16. data/lib/{z_k → zk}/election.rb +0 -0
  17. data/lib/zk/event.rb +168 -0
  18. data/lib/{z_k → zk}/event_handler.rb +53 -28
  19. data/lib/zk/event_handler_subscription.rb +68 -0
  20. data/lib/{z_k → zk}/exceptions.rb +38 -23
  21. data/lib/zk/extensions.rb +79 -0
  22. data/lib/{z_k → zk}/find.rb +0 -0
  23. data/lib/{z_k → zk}/locker.rb +0 -0
  24. data/lib/{z_k → zk}/logging.rb +0 -0
  25. data/lib/{z_k → zk}/message_queue.rb +8 -4
  26. data/lib/{z_k → zk}/mongoid.rb +0 -0
  27. data/lib/{z_k → zk}/pool.rb +0 -0
  28. data/lib/zk/stat.rb +115 -0
  29. data/lib/{z_k → zk}/threadpool.rb +52 -4
  30. data/lib/zk/version.rb +3 -0
  31. data/lib/zk.rb +238 -1
  32. data/spec/message_queue_spec.rb +2 -2
  33. data/spec/shared/client_contexts.rb +8 -20
  34. data/spec/shared/client_examples.rb +136 -2
  35. data/spec/spec_helper.rb +4 -2
  36. data/spec/support/event_catcher.rb +11 -0
  37. data/spec/support/exist_matcher.rb +6 -0
  38. data/spec/support/logging.rb +2 -1
  39. data/spec/watch_spec.rb +194 -10
  40. data/spec/{z_k → zk}/client/locking_and_session_death_spec.rb +0 -32
  41. data/spec/zk/client_spec.rb +23 -0
  42. data/spec/{z_k → zk}/election_spec.rb +0 -0
  43. data/spec/{z_k → zk}/extensions_spec.rb +0 -0
  44. data/spec/{z_k → zk}/locker_spec.rb +0 -40
  45. data/spec/zk/module_spec.rb +185 -0
  46. data/spec/{z_k → zk}/mongoid_spec.rb +0 -2
  47. data/spec/{z_k → zk}/pool_spec.rb +0 -2
  48. data/spec/{z_k → zk}/threadpool_spec.rb +32 -4
  49. data/spec/zookeeper_spec.rb +1 -6
  50. data/zk.gemspec +2 -2
  51. metadata +64 -56
  52. data/lib/z_k/client/continuation_proxy.rb +0 -109
  53. data/lib/z_k/client/drop_box.rb +0 -98
  54. data/lib/z_k/client/multiplexed.rb +0 -28
  55. data/lib/z_k/client/threaded.rb +0 -76
  56. data/lib/z_k/client.rb +0 -35
  57. data/lib/z_k/event_handler_subscription.rb +0 -36
  58. data/lib/z_k/extensions.rb +0 -155
  59. data/lib/z_k/version.rb +0 -3
  60. data/lib/z_k.rb +0 -97
  61. data/spec/z_k/client/drop_box_spec.rb +0 -90
  62. data/spec/z_k/client/multiplexed_spec.rb +0 -20
  63. data/spec/z_k/client_spec.rb +0 -7
data/.gitignore CHANGED
@@ -1,10 +1,10 @@
1
1
  *.gem
2
2
  .bundle
3
- Gemfile.lock
4
3
  pkg/*
5
- Gemfile.lock
6
4
  *.log
7
5
  html
8
6
  .yardoc
9
7
  .rspec
10
8
  .rvmrc
9
+ Gemfile.*
10
+ wiki
data/Gemfile CHANGED
@@ -1,14 +1,13 @@
1
- # this is here for doing internal builds in our environment
2
- source ENV['MBOX_BUNDLER_SOURCE'] if ENV['MBOX_BUNDLER_SOURCE']
3
- source "http://rubygems.org"
1
+ source :rubygems
4
2
 
5
3
  # gem 'slyphon-zookeeper', :path => '~/zookeeper'
6
4
 
7
5
  group :development do
8
- gem 'pry'
9
6
  gem 'rake'
10
7
  end
11
8
 
9
+ gem 'pry', :group => [:development, :test]
10
+
12
11
  group :docs do
13
12
  gem 'yard', '~> 0.7.5'
14
13
 
data/README.markdown CHANGED
@@ -54,9 +54,11 @@ In addition to all of that, I would like to think that the public API the ZK::Cl
54
54
 
55
55
  ## Caveats
56
56
 
57
- ZK strives to be a complete, correct, and convenient way of interacting with ZooKeeper. There are a few weak points in the implementation:
57
+ ZK strives to be a complete, correct, and convenient way of interacting with ZooKeeper. There are a few things to be aware of:
58
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.
59
+ * In versions &lte; 0.9 there is only *one* event dispatch thread. It is *very important* that you don't block the event delivery thread. In 1.0, there is one delivery thread by default, but you can adjust the level of concurrency, allowing more control and convenience for building your event-driven app.
60
+
61
+ * ZK uses threads. You will have to use synchronization primitives if you want to avoid getting hurt. There are use cases that do not require you to think about this, but as soon as you want to register for events, you're using multiple threads.
60
62
 
61
63
  * 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
64
 
@@ -66,7 +68,7 @@ ZK strives to be a complete, correct, and convenient way of interacting with Zoo
66
68
 
67
69
  * 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.
68
70
 
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).
71
+ * ZooKeeper "chroot" [connection syntax][chroot] 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).
70
72
 
71
73
  [twitter/zookeeper]: https://github.com/twitter/zookeeper
72
74
  [async-branch]: https://github.com/slyphon/zk/tree/dev%2Fasync-conveniences
@@ -75,14 +77,21 @@ ZK strives to be a complete, correct, and convenient way of interacting with Zoo
75
77
  [sessions]: http://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#ch_zkSessions
76
78
  [watches]: http://zookeeper.apache.org/doc/r3.3.5/zookeeperProgrammers.html#ch_zkWatches
77
79
 
80
+ ## Users
81
+
82
+ * [papertrail](http://papertrailapp.com/): Hosted log management service
83
+ * [redis\_failover](https://github.com/ryanlecompte/redis_failover): Redis client/server failover managment system
84
+ * [DCell](https://github.com/celluloid/dcell): Distributed ruby objects, built on top of the super cool [Celluloid](https://github.com/celluloid/celluloid) framework.
85
+
86
+
78
87
  ## Dependencies
79
88
 
80
- * The [slyphon-zookeeper gem][szk-gem] ([repo][szk-repo], branch with Gemfile [here][szk-repo-bundler]), which adds JRuby compatibility and a full suite of tests to the excellent [twitter/zookeeper][] project. _(I'm hoping to get this merged upstream, but it's a large change and, you know, people have day jobs)_.
89
+ * The [slyphon-zookeeper gem][szk-gem] ([repo][szk-repo]), which adds JRuby compatibility and a full suite of tests to the excellent [twitter/zookeeper][] project.
81
90
 
82
91
  * For JRuby, the [slyphon-zookeeper\_jar gem][szk-jar-gem] ([repo][szk-jar-repo]), which just wraps the upstream zookeeper driver jar in a gem for easy installation
83
92
 
84
93
  [szk-gem]: https://rubygems.org/gems/slyphon-zookeeper
85
- [szk-repo]: https://github.com/slyphon/zookeeper/tree/dev/xplatform
94
+ [szk-repo]: https://github.com/slyphon/zookeeper
86
95
  [szk-repo-bundler]: https://github.com/slyphon/zookeeper/tree/dev/gemfile/
87
96
  [szk-jar-gem]: https://rubygems.org/gems/slyphon-zookeeper_jar
88
97
  [szk-jar-repo]: https://github.com/slyphon/zookeeper_jar
data/RELEASES.markdown CHANGED
@@ -1,5 +1,73 @@
1
1
  This file notes feature differences and bugfixes contained between releases.
2
2
 
3
+ ### v1.0.0 ###
4
+
5
+ * Support for 1.8.7 WILL BE *DROPPED* in v1.1. You've been warned.
6
+
7
+ * Threaded client (the default one) will now automatically reconnect (i.e. `reopen()`) if a `SESSION_EXPIRED` or `AUTH_FAILED` event is received. Thanks to @eric for pointing out the _nose-on-your-face obviousness_ and importance of this. If users want to handle these events themselves, and not automatically reopen, you can pass `:reconnect => false` to the constructor.
8
+
9
+ * allow for both :sequence and :sequential arguments to create, because I always forget which one is the "right one"
10
+
11
+ * add zk.register(:all) to recevie node updates for all nodes (i.e. not filtered on path)
12
+
13
+ * add 'interest' feature to zk.register, now you can indicate what kind of events should be delivered to the given block (previously you had to do that filtering inside the block). The default behavior is still the same, if no 'interest' is given, then all event types for the given path will be delivered to that block.
14
+
15
+ zk.register('/path', :created) do |event|
16
+ # event.node_created? will always be true
17
+ end
18
+
19
+ # or multiple kinds of events
20
+
21
+ zk.register('/path', [:created, :changed]) do |event|
22
+ # (event.node_created? or event.node_changed?) will always be true
23
+ end
24
+
25
+ * create now allows you to pass a path and options, instead of requiring the blank string
26
+
27
+ zk.create('/path', '', :sequential => true)
28
+
29
+ # now also
30
+
31
+ zk.create('/path', :sequential => true)
32
+
33
+ * fix for shutdown: close! called from threadpool will do the right thing
34
+
35
+ * Chroot users rejoice! By default, ZK.new will create a chrooted path for you.
36
+
37
+ ZK.new('localhost:2181/path', :chroot => :create) # the default, create the path before returning connection
38
+
39
+ ZK.new('localhost:2181/path', :chroot => :check) # make sure the chroot exists, raise if not
40
+
41
+ ZK.new('localhost:2181/path', :chroot => :do_nothing) # old default behavior
42
+
43
+ # and, just for kicks
44
+
45
+ ZK.new('localhost:2181', :chroot => '/path') # equivalent to 'localhost:2181/path', :chroot => :create
46
+
47
+ * Most of the event functionality used is now in a ZK::Event module. This is still mixed into the underlying slyphon-zookeeper class, but now all of the important and relevant methods are documented, and Event appears as a first-class citizen.
48
+
49
+ ### v0.9.1 ###
50
+
51
+ The "Don't forget to update the RELEASES file before pushing a new release" release
52
+
53
+ * Fix a fairly bad bug in event de-duplication (diff: http://is.gd/a1iKNc)
54
+
55
+ This is fairly edge-case-y but could bite someone. If you'd set a watch
56
+ when doing a get that failed because the node didn't exist, any subsequent
57
+ attempts to set a watch would fail silently, because the client thought that the
58
+ watch had already been set.
59
+
60
+ We now wrap the operation in the setup_watcher! method, which rolls back the
61
+ record-keeping of what watches have already been set for what nodes if an
62
+ exception is raised.
63
+
64
+ This change has the side-effect that certain operations (get,stat,exists?,children)
65
+ will block event delivery until completion, because they need to have a consistent
66
+ idea about what events are pending, and which have been delivered. This also means
67
+ that calling these methods represent a synchronization point between user threads
68
+ (these operations can only occur serially, not simultaneously).
69
+
70
+
3
71
  ### v0.9.0 ###
4
72
 
5
73
  * Default threadpool size has been changed from 5 to 1. This should only affect people who are using the Election code.
@@ -11,4 +79,4 @@ This file notes feature differences and bugfixes contained between releases.
11
79
  * 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
80
 
13
81
 
14
- # vim:ft=markdown
82
+ # vim:ft=markdown:sts=2:sw=2:et
data/Rakefile CHANGED
@@ -1,36 +1,68 @@
1
- # require 'rubygems'
2
- # gem 'rdoc', '~> 2.5'
3
- # require 'rdoc/task'
1
+ require 'benchmark'
2
+ gemset_name = 'zk'
4
3
 
5
- # RDoc::Task.new do |rd|
6
- # rd.title = 'ZK Documentation'
7
- # rd.rdoc_files.include("lib/**/*.rb")
8
- # end
4
+ # this nonsense with the Gemfile symlinks is a bundler optimization
9
5
 
10
- gemset_name = 'zk'
6
+ GEMSPEC_NAME = 'zk.gemspec'
7
+
8
+ %w[1.8.7 1.9.2 jruby rbx 1.9.3].each do |ns_name|
9
+ rvm_ruby = (ns_name == 'rbx') ? "rbx-2.0.testing" : ns_name
10
+
11
+ ruby_with_gemset = "#{rvm_ruby}@#{gemset_name}"
12
+ create_gemset_task_name = "mb:#{ns_name}:create_gemset"
13
+ bundle_task_name = "mb:#{ns_name}:bundle_install"
14
+ rspec_task_name = "mb:#{ns_name}:run_rspec"
15
+
16
+ phony_gemfile_link_name = "Gemfile.#{ns_name}"
17
+ phony_gemfile_lock_name = "#{phony_gemfile_link_name}.lock"
11
18
 
12
- %w[1.8.7 1.9.2 1.9.3 jruby].each do |rvm_ruby|
13
- ruby_with_gemset = "#{rvm_ruby}@#{gemset_name}"
14
- create_gemset_task_name = "mb:#{rvm_ruby}:create_gemset"
15
- bundle_task_name = "mb:#{rvm_ruby}:bundle_install"
16
- rspec_task_name = "mb:#{rvm_ruby}:run_rspec"
19
+ file phony_gemfile_link_name do
20
+ # apparently, rake doesn't deal with symlinks intelligently :P
21
+ ln_s('Gemfile', phony_gemfile_link_name) unless File.symlink?(phony_gemfile_link_name)
22
+ end
23
+
24
+ task :clean do
25
+ rm_rf [phony_gemfile_lock_name, phony_gemfile_lock_name]
26
+ end
17
27
 
18
28
  task create_gemset_task_name do
19
29
  sh "rvm #{rvm_ruby} do rvm gemset create #{gemset_name}"
20
30
  end
21
31
 
22
- task bundle_task_name => create_gemset_task_name do
23
- rm_f 'Gemfile.lock'
24
- sh "rvm #{ruby_with_gemset} do bundle install"
32
+ task bundle_task_name => [phony_gemfile_link_name, create_gemset_task_name] do
33
+ sh "rvm #{ruby_with_gemset} do bundle install --gemfile #{phony_gemfile_link_name}"
25
34
  end
26
35
 
27
36
  task rspec_task_name => bundle_task_name do
28
- sh "rvm #{ruby_with_gemset} do bundle exec rspec spec --fail-fast"
37
+ sh "rvm #{ruby_with_gemset} do env BUNDLE_GEMFILE=#{phony_gemfile_link_name} bundle exec rspec spec --fail-fast"
38
+ end
39
+
40
+ task "mb:#{ns_name}" => rspec_task_name
41
+
42
+ task "mb:test_all_rubies" => rspec_task_name
43
+ end
44
+
45
+ task 'mb:test_all' do
46
+ tm = Benchmark.realtime do
47
+ Rake::Task['mb:test_all_rubies'].invoke
48
+ end
49
+
50
+ $stderr.puts "Test run took: #{tm}"
51
+ end
52
+
53
+
54
+ namespace :yard do
55
+ task :clean do
56
+ rm_rf '.yardoc'
29
57
  end
30
58
 
31
- task "mb:test_all" => rspec_task_name
59
+ task :server => :clean do
60
+ sh "yard server --reload"
61
+ end
32
62
  end
33
63
 
64
+ task :clean => 'yard:clean'
65
+
34
66
  namespace :spec do
35
67
  task :define do
36
68
  require 'rubygems'
@@ -0,0 +1,63 @@
1
+ # docs/examples/block_until_node_deleted_ex.rb
2
+
3
+ require 'zk'
4
+
5
+ class BlockUntilNodeDeleted
6
+ attr_reader :zk
7
+
8
+ def initialize
9
+ @zk = ZK.new
10
+ @path = @zk.create('/zk-examples', sequence: true, ephemeral: true)
11
+ end
12
+
13
+ def block_until_node_deleted(abs_node_path)
14
+ queue = Queue.new
15
+
16
+ ev_sub = zk.register(abs_node_path) do |event|
17
+ if event.node_deleted?
18
+ queue.enq(:deleted)
19
+ else
20
+ if zk.exists?(abs_node_path, :watch => true)
21
+ # node still exists, wait for next event (better luck next time)
22
+ else
23
+ # ooh! surprise! it's gone!
24
+ queue.enq(:deleted)
25
+ end
26
+ end
27
+ end
28
+
29
+ # set up the callback, but bail if we don't need to wait
30
+ return true unless zk.exists?(abs_node_path, :watch => true)
31
+
32
+ queue.pop # block waiting for node deletion
33
+ true
34
+ ensure
35
+ ev_sub.unsubscribe
36
+ end
37
+
38
+ def run
39
+ waiter = Thread.new do
40
+ $stderr.puts "waiter thread, about to block"
41
+ block_until_node_deleted(@path)
42
+ $stderr.puts "waiter unblocked"
43
+ end
44
+
45
+ # This is not a good way to wait on another thread in general (it's
46
+ # busy-waiting) but simple for this example.
47
+ #
48
+ Thread.pass until waiter.status == 'sleep'
49
+
50
+ # we now know the other thread is waiting for deletion
51
+ # so give 'em a thrill
52
+ @zk.delete(@path)
53
+
54
+ waiter.join
55
+
56
+ $stderr.puts "hooray! success!"
57
+ ensure
58
+ @zk.close!
59
+ end
60
+ end
61
+
62
+ BlockUntilNodeDeleted.new.run
63
+
@@ -0,0 +1,36 @@
1
+ require 'thread'
2
+ require 'zk'
3
+
4
+ class Events
5
+ def initialize
6
+ @zk = ZK.new
7
+ @queue = Queue.new
8
+ @path = '/zk-example-events01'
9
+ end
10
+
11
+ def do_something_with(data)
12
+ puts "I was told to say #{data.inspect}"
13
+ end
14
+
15
+ def run
16
+ @zk.delete(@path) rescue ZK::Exceptions::NoNode
17
+
18
+ @zk.register(@path) do |event|
19
+ if event.node_changed? or event.node_created?
20
+ # fetch the latest data
21
+ data = @zk.get(@path).first
22
+ do_something_with(data)
23
+ @queue.push(:got_event)
24
+ end
25
+ end
26
+
27
+ @zk.stat(@path, watch: true)
28
+ @zk.create(@path, 'Hello, events!')
29
+
30
+ @queue.pop
31
+ ensure
32
+ @zk.close!
33
+ end
34
+ end
35
+
36
+ Events.new.run
@@ -0,0 +1,41 @@
1
+ # docs/examples/events_02.rb
2
+
3
+ require 'thread'
4
+ require 'zk'
5
+
6
+ class Events
7
+ def initialize
8
+ @zk = ZK.new
9
+ @queue = Queue.new
10
+ @path = '/zk-example-events01'
11
+ end
12
+
13
+ def do_something_with(data)
14
+ puts "I was told to say #{data.inspect}"
15
+ @queue.push(:got_event)
16
+ end
17
+
18
+ def run
19
+ @zk.register(@path) do |event|
20
+ if event.node_changed? or event.node_created?
21
+ data = @zk.get(@path, watch: true).first # fetch the latest data and re-set watch
22
+ do_something_with(data)
23
+ end
24
+ end
25
+
26
+ @zk.delete(@path) rescue ZK::Exceptions::NoNode
27
+ @zk.stat(@path, watch: true)
28
+ @zk.create(@path, 'Hello, events!')
29
+
30
+ @queue.pop
31
+
32
+ @zk.set(@path, "ooh, an update!")
33
+
34
+ @queue.pop
35
+ ensure
36
+ @zk.close!
37
+ end
38
+ end
39
+
40
+ Events.new.run
41
+
@@ -70,6 +70,7 @@ module ZK
70
70
  #
71
71
  # ZK::Client.new("zk01:2181,zk02:2181/chroot/path")
72
72
  #
73
+ # @abstract Overridden in subclasses
73
74
  def initialize(host, opts={})
74
75
  # no-op
75
76
  end
@@ -88,7 +89,16 @@ module ZK
88
89
  public
89
90
 
90
91
  # reopen the underlying connection
91
- # returns state of connection after operation
92
+ #
93
+ # The `timeout` param is here mainly for legacy support.
94
+ #
95
+ # @param [Numeric] timeout how long should we wait for
96
+ # the connection to reach a connected state before returning. Note that
97
+ # the method will not raise and will return whether the connection
98
+ # reaches the 'connected' state or not. The default is actually to use
99
+ # the same value that was passed to the constructor for 'timeout'
100
+ #
101
+ # @return [Symbol] state of connection after operation
92
102
  def reopen(timeout=nil)
93
103
  timeout ||= @session_timeout # XXX: @session_timeout ?
94
104
  cnx.reopen(timeout)
@@ -98,14 +108,6 @@ module ZK
98
108
 
99
109
  # close the underlying connection and clear all pending events.
100
110
  #
101
- # @note it is VERY IMPORTANT that when using the threaded client that you
102
- # __NOT CALL THIS ON THE EVENT DISPATCH THREAD__. If you do call it when
103
- # `event_dispatch_thread?` is true, you will get a big fat Kernel.warn
104
- # and nothing will happen. There is currently no safe way to have the event
105
- # thread cause a shutdown in the main thread (with good reason). There is also
106
- # no way to get an exception from the event thread (they're currently
107
- # just logged and swallowed), so this is the least horrible remedy available.
108
- #
109
111
  def close!
110
112
  event_handler.clear!
111
113
  wrap_state_closed_error { cnx.close unless cnx.closed? }
@@ -129,18 +131,16 @@ module ZK
129
131
  # creating sequential node with the same path argument, the call will never
130
132
  # throw a NodeExists exception.
131
133
  #
132
- # @todo clean up the verbiage around watchers
133
- #
134
- # This operation, if successful, will trigger all the watches left on the
135
- # node of the given path by exists and get API calls, and the watches left
136
- # on the parent node by children API calls.
137
- #
138
134
  # If a node is created successfully, the ZooKeeper server will trigger the
139
135
  # watches on the path left by exists calls, and the watches on the parent
140
136
  # of the node by children calls.
141
137
  #
142
- # @param [String] path absolute path of the znode
143
- # @param [String] data the data to create the znode with
138
+ # @overload create(path, opts={})
139
+ # creates a znode at the absolute `path` with blank data and given
140
+ # options
141
+ #
142
+ # @overload create(path, data, opts={})
143
+ # creates a znode at the absolute `path` with given data and options
144
144
  #
145
145
  # @option opts [Integer] :acl defaults to <tt>ZookeeperACLs::ZOO_OPEN_ACL_UNSAFE</tt>,
146
146
  # otherwise the ACL for the node. Should be a `ZOO_*` constant defined under the
@@ -150,6 +150,9 @@ module ZK
150
150
  #
151
151
  # @option opts [bool] :sequence (false) if true, the created node will be sequential
152
152
  #
153
+ # @option opts [bool] :sequential (false) alias for :sequence option. if both are given
154
+ # an ArgumentError is raised
155
+ #
153
156
  # @option opts [ZookeeperCallbacks::StringCallback] :callback (nil) provide a callback object
154
157
  # that will be called when the znode has been created
155
158
  #
@@ -193,18 +196,18 @@ module ZK
193
196
  #
194
197
  # # or you can also do:
195
198
  #
196
- # zk.create("/path", '', :mode => :persistent_sequence)
199
+ # zk.create("/path", '', :mode => :persistent_sequential)
197
200
  # # => "/path0"
198
201
  #
199
202
  #
200
203
  # @example create ephemeral and sequential node
201
204
  #
202
- # zk.create("/path", '', :sequential => true, :ephemeral => true)
205
+ # zk.create("/path", '', :sequence => true, :ephemeral => true)
203
206
  # # => "/path0"
204
207
  #
205
208
  # # or you can also do:
206
209
  #
207
- # zk.create("/path", "foo", :mode => :ephemeral_sequence)
210
+ # zk.create("/path", "foo", :mode => :ephemeral_sequential)
208
211
  # # => "/path0"
209
212
  #
210
213
  # @example create a child path
@@ -214,12 +217,12 @@ module ZK
214
217
  #
215
218
  # @example create a sequential child path
216
219
  #
217
- # zk.create("/path/child", "bar", :sequential => true, :ephemeral => true)
220
+ # zk.create("/path/child", "bar", :sequence => true, :ephemeral => true)
218
221
  # # => "/path/child0"
219
222
  #
220
223
  # # or you can also do:
221
224
  #
222
- # zk.create("/path/child", "bar", :mode => :ephemeral_sequence)
225
+ # zk.create("/path/child", "bar", :mode => :ephemeral_sequential)
223
226
  # # => "/path/child0"
224
227
  #
225
228
  # @hidden_example create asynchronously with callback object
@@ -245,7 +248,25 @@ module ZK
245
248
  #
246
249
  # zk.create("/path", "foo", :callback => callback, :context => context)
247
250
  #
248
- def create(path, data='', opts={})
251
+ def create(path, *args)
252
+ opts = args.extract_options!
253
+
254
+ # be somewhat strict about how many arguments we accept.
255
+ if args.length > 1
256
+ raise ArgumentError, "create takes path, an optional data argument, and options, you passed: (#{path}, *#{args})"
257
+ end
258
+
259
+ # argh, terrible documentation bug, allow for :sequential, analagous to :sequence
260
+ if opts.has_key?(:sequential)
261
+ if opts.has_key?(:sequence)
262
+ raise ArgumentError, "Only one of :sequential or :sequence options can be given, opts: #{opts}"
263
+ end
264
+
265
+ opts[:sequence] = opts.delete(:sequential)
266
+ end
267
+
268
+ data = args.first || ''
269
+
249
270
  h = { :path => path, :data => data, :ephemeral => false, :sequence => false }.merge(opts)
250
271
 
251
272
  if mode = h.delete(:mode)
@@ -695,12 +716,12 @@ module ZK
695
716
  end
696
717
  end
697
718
 
698
- # returns the session_id of the underlying connection
719
+ # @return [Fixnum] the session_id of the underlying connection
699
720
  def session_id
700
721
  cnx.session_id
701
722
  end
702
723
 
703
- # returns the session_passwd of the underlying connection
724
+ # @return [String] the session_passwd of the underlying connection
704
725
  def session_passwd
705
726
  cnx.session_passwd
706
727
  end
@@ -716,6 +737,10 @@ module ZK
716
737
  # This method will return an {EventHandlerSubscription} instance that can be used
717
738
  # to remove the block from further updates by calling its `.unsubscribe` method.
718
739
  #
740
+ # You can specify a list of event types after the path that you wish to
741
+ # receive in your block. This allows you to register different blocks for
742
+ # different types of events.
743
+ #
719
744
  # @note All node watchers are one-shot handlers. After an event is delivered to
720
745
  # your handler, you *must* re-watch the node to receive more events. This
721
746
  # leads to a pattern you will find throughout ZK code that avoids races,
@@ -743,8 +768,30 @@ module ZK
743
768
  # do_something_when_node_deleted # call the callback
744
769
  # end
745
770
  #
771
+ # @example only creation events
772
+ #
773
+ # sub = zk.register('/path/to/znode', :created) do |event|
774
+ # # do something when the node is created
775
+ # end
776
+ #
777
+ # @example only changed or children events
778
+ #
779
+ # sub = zk.register('/path/to/znode', [:changed, :child]) do |event|
780
+ # if event.node_changed?
781
+ # # do something on change
782
+ # else
783
+ # # we know it's a child event
784
+ # end
785
+ # end
786
+ #
787
+ # @param [String,:all] path the znode path you want to listen to, or the
788
+ # special value :all, that will cause the block to be delivered events
789
+ # for all znode paths
746
790
  #
747
- # @param [String] path the path you want to listen to
791
+ # @param [Array,Symbol,nil] interests a symbol or array-of-symbols indicating
792
+ # which events you would like the block to be called for. Valid events
793
+ # are :created, :deleted, :changed, and :child. If nil, the block will
794
+ # receive all events
748
795
  #
749
796
  # @param [Block] block the block to execute when a watch event happpens
750
797
  #
@@ -756,9 +803,10 @@ module ZK
756
803
  #
757
804
  # @see ZooKeeper::WatcherEvent
758
805
  # @see ZK::EventHandlerSubscription
806
+ # @see https://github.com/slyphon/zk/wiki/Events the wiki page on using events effectively
759
807
  #
760
- def register(path, &block)
761
- event_handler.register(path, &block)
808
+ def register(path, interests=nil, &block)
809
+ event_handler.register(path, interests, &block)
762
810
  end
763
811
 
764
812
  # returns true if the caller is calling from the event dispatch thread
@@ -772,6 +820,15 @@ module ZK
772
820
  raise Exceptions::EventDispatchThreadException, msg if event_dispatch_thread?
773
821
  end
774
822
 
823
+ # called directly from the zookeeper event thread with every event, before they
824
+ # get dispatched to the user callbacks. used by client implementations for
825
+ # critical events like session_expired, so that we don't compete for
826
+ # threads in the threadpool.
827
+ #
828
+ # @private
829
+ def raw_event_handler(event)
830
+ end
831
+
775
832
  protected
776
833
  # @private
777
834
  def check_rc(hash, inputs=nil)
@@ -798,6 +855,6 @@ module ZK
798
855
  nil
799
856
  end
800
857
  end # Base
801
- end # Client
802
- end # ZK
858
+ end # Client
859
+ end # ZK
803
860
 
@@ -18,7 +18,6 @@ module ZK
18
18
  # @yield [] the block that should be run in the threadpool, if `callable`
19
19
  # isn't given
20
20
  #
21
- # @private
22
21
  def defer(callable=nil, &block)
23
22
  @threadpool.defer(callable, &block)
24
23
  end
File without changes