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.
- 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
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -1,14 +1,13 @@
|
|
1
|
-
|
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
|
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
|
-
*
|
59
|
+
* In versions <e; 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]
|
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]
|
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
|
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
|
-
|
2
|
-
|
3
|
-
# require 'rdoc/task'
|
1
|
+
require 'benchmark'
|
2
|
+
gemset_name = 'zk'
|
4
3
|
|
5
|
-
#
|
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
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
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
|
-
#
|
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
|
-
# @
|
143
|
-
#
|
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 => :
|
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", '', :
|
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 => :
|
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", :
|
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 => :
|
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,
|
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
|
-
#
|
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
|
-
#
|
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 [
|
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
|
802
|
-
end
|
858
|
+
end # Client
|
859
|
+
end # ZK
|
803
860
|
|
File without changes
|