zk-eventmachine 0.1.13 → 0.2.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +0 -1
- data/Gemfile +3 -1
- data/Rakefile +0 -35
- data/lib/z_k/z_k_event_machine/callback.rb +1 -1
- data/lib/z_k/z_k_event_machine/client.rb +32 -92
- data/lib/z_k/z_k_event_machine/deferred.rb +39 -0
- data/lib/z_k/z_k_event_machine/synchrony_client.rb +111 -0
- data/lib/z_k/z_k_event_machine/unixisms.rb +0 -5
- data/lib/z_k/z_k_event_machine/version.rb +1 -1
- data/lib/z_k/z_k_event_machine.rb +4 -1
- data/spec/spec_helper.rb +2 -19
- data/spec/support/logging.rb +5 -10
- data/spec/z_k/z_k_event_machine/client_spec.rb +61 -181
- data/spec/z_k/z_k_event_machine/synchrony_client_spec.rb +339 -0
- data/zk-eventmachine.gemspec +9 -11
- metadata +86 -178
- data/.yardopts +0 -8
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,37 +1,2 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
Bundler::GemHelper.install_tasks
|
3
|
-
|
4
|
-
task :yard do
|
5
|
-
Bundler.setup
|
6
|
-
require 'yard'
|
7
|
-
|
8
|
-
YARD::Rake::YardocTask.new(:run_yardoc) do |t|
|
9
|
-
t.files = ['lib/**/*.rb']
|
10
|
-
end
|
11
|
-
|
12
|
-
Rake::Task[:run_yardoc].invoke
|
13
|
-
end
|
14
|
-
|
15
|
-
%w[1.8.7 1.9.2 1.9.3 jruby].each do |rvm_ruby|
|
16
|
-
gemset_name = 'zk-em'
|
17
|
-
ruby_with_gemset = "#{rvm_ruby}@#{gemset_name}"
|
18
|
-
create_gemset_name = "mb:#{rvm_ruby}:create_gemset"
|
19
|
-
bundle_task_name = "mb:#{rvm_ruby}:bundle_install"
|
20
|
-
rspec_task_name = "mb:#{rvm_ruby}:run_rspec"
|
21
|
-
|
22
|
-
task create_gemset_name do
|
23
|
-
sh "rvm #{rvm_ruby} do rvm gemset create #{gemset_name}"
|
24
|
-
end
|
25
|
-
|
26
|
-
task bundle_task_name => create_gemset_name do
|
27
|
-
rm_f 'Gemfile.lock'
|
28
|
-
sh "rvm #{ruby_with_gemset} do bundle install"
|
29
|
-
end
|
30
|
-
|
31
|
-
task rspec_task_name => bundle_task_name do
|
32
|
-
sh "rvm #{ruby_with_gemset} do bundle exec rspec spec --fail-fast"
|
33
|
-
end
|
34
|
-
|
35
|
-
task 'mb:test_all' => rspec_task_name
|
36
|
-
end
|
37
|
-
|
@@ -1,63 +1,26 @@
|
|
1
1
|
module ZK
|
2
2
|
module ZKEventMachine
|
3
3
|
class Client < ZK::Client::Base
|
4
|
-
include Deferred::Accessors
|
5
4
|
include ZK::Logging
|
6
5
|
include Unixisms
|
7
6
|
|
8
7
|
DEFAULT_TIMEOUT = 10
|
9
8
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
|
13
|
-
#
|
14
|
-
# once this deferred has been fired, it will be replaced with a new
|
15
|
-
# deferred, so callbacks must be re-registered, and *should* be
|
16
|
-
# re-registered *within* the callback to avoid missing events
|
17
|
-
#
|
18
|
-
# @method on_connection_lost
|
19
|
-
# @return [Deferred::Default]
|
20
|
-
deferred_event :connection_lost
|
21
|
-
|
22
|
-
|
23
|
-
# Registers a one-shot callback for the ZOO_CONNECTED_STATE event.
|
24
|
-
#
|
25
|
-
# @note this is experimental currently. This may or may not fire for the *initial* connection.
|
26
|
-
# it's purpose is to warn an already-existing client with watches that a connection has been
|
27
|
-
# re-established (with session information saved). From the ZooKeeper Programmers' Guide:
|
28
|
-
#
|
29
|
-
# If you are using watches, you must look for the connected watch event.
|
30
|
-
# When a ZooKeeper client disconnects from a server, you will not receive
|
31
|
-
# notification of changes until reconnected. If you are watching for a
|
32
|
-
# znode to come into existance, you will miss the event if the znode is
|
33
|
-
# created and deleted while you are disconnected.
|
34
|
-
#
|
35
|
-
# once this deferred has been fired, it will be replaced with a new
|
36
|
-
# deferred, so callbacks must be re-registered, and *should* be
|
37
|
-
# re-registered *within* the callback to avoid missing events
|
38
|
-
#
|
39
|
-
# @method on_connected
|
40
|
-
# @return [Deferred::Default]
|
41
|
-
deferred_event :connected
|
42
|
-
|
43
|
-
# Registers a one-shot callback for the ZOO_CONNECTING_STATE event
|
44
|
-
#
|
45
|
-
# This event is triggered when we have become disconnected from the
|
46
|
-
# cluster and are in the process of reconnecting.
|
47
|
-
deferred_event :connecting
|
48
|
-
|
49
|
-
# called back once the connection has been closed.
|
50
|
-
#
|
51
|
-
# @method on_close
|
52
|
-
# @return [Deferred::Default]
|
53
|
-
deferred_event :close
|
9
|
+
# allows the synchrony client to muck with this
|
10
|
+
# @private
|
11
|
+
attr_writer :event_handler
|
54
12
|
|
55
13
|
# Takes same options as ZK::Client::Base
|
56
14
|
def initialize(host, opts={})
|
57
15
|
@host = host
|
58
|
-
@
|
59
|
-
@
|
60
|
-
|
16
|
+
@close_deferred = Deferred::Default.new
|
17
|
+
@connection_lost_deferred = Deferred::Default.new
|
18
|
+
@event_handler = EventHandlerEM.new(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_close(&blk)
|
22
|
+
@close_deferred.callback(&blk) if blk
|
23
|
+
@close_deferred
|
61
24
|
end
|
62
25
|
|
63
26
|
# open a ZK connection, attach it to the reactor.
|
@@ -65,21 +28,34 @@ module ZK
|
|
65
28
|
# ready for use
|
66
29
|
def connect(&blk)
|
67
30
|
# XXX: maybe move this into initialize, need to figure out how to schedule it properly
|
68
|
-
@cnx ||= (
|
69
|
-
ZookeeperEM::Client.new(@host, DEFAULT_TIMEOUT, event_handler.get_default_watcher_block)
|
70
|
-
)
|
31
|
+
@cnx ||= ZookeeperEM::Client.new(@host, DEFAULT_TIMEOUT, event_handler.get_default_watcher_block)
|
71
32
|
@cnx.on_attached(&blk)
|
72
33
|
end
|
73
34
|
|
74
35
|
# @private
|
36
|
+
# XXX: move this down into ZK::Client::Base
|
37
|
+
def connected?
|
38
|
+
@cnx and @cnx.connected?
|
39
|
+
end
|
40
|
+
|
41
|
+
# If we get a ZK::Exceptions::ConnectionLoss exeption back from any call,
|
42
|
+
# we will call back any handlers registered here with the exception
|
43
|
+
# instance as the argument
|
44
|
+
#
|
45
|
+
# once this deferred has been fired, it will be replaced with a new
|
46
|
+
# deferred, so callbacks must be re-registered
|
47
|
+
#
|
48
|
+
def on_connection_lost(&blk)
|
49
|
+
@connection_lost_deferred.callback(&blk) if blk
|
50
|
+
@connection_lost_deferred
|
51
|
+
end
|
52
|
+
|
75
53
|
def reopen(*a)
|
76
54
|
raise NotImplementedError, "reoopen is not implemented for the eventmachine version of the client"
|
77
55
|
end
|
78
56
|
|
79
57
|
def close!(&blk)
|
80
58
|
on_close(&blk)
|
81
|
-
return on_close if @closing
|
82
|
-
@closing = true
|
83
59
|
|
84
60
|
if @cnx
|
85
61
|
logger.debug { "#{self.class.name}: in close! clearing event_handler" }
|
@@ -101,7 +77,7 @@ module ZK
|
|
101
77
|
|
102
78
|
# get data at path, optionally enabling a watch on the node
|
103
79
|
#
|
104
|
-
# @
|
80
|
+
# @returns [Callback] returns a Callback which is an EM::Deferred (so you
|
105
81
|
# can assign callbacks/errbacks) see Callback::Base for discussion
|
106
82
|
#
|
107
83
|
def get(path, opts={}, &block)
|
@@ -168,47 +144,11 @@ module ZK
|
|
168
144
|
end
|
169
145
|
end
|
170
146
|
|
171
|
-
# @return [Fixnum] The underlying connection's session_id
|
172
|
-
def session_id
|
173
|
-
return nil unless @cnx
|
174
|
-
@cnx.session_id
|
175
|
-
end
|
176
|
-
|
177
|
-
# @return [String] The underlying connection's session passwd (an opaque value)
|
178
|
-
def session_passwd
|
179
|
-
return nil unless @cnx
|
180
|
-
@cnx.session_passwd
|
181
|
-
end
|
182
|
-
|
183
147
|
protected
|
184
|
-
# @private
|
185
|
-
def register_default_event_handlers!
|
186
|
-
@event_handler.register_state_handler(Zookeeper::ZOO_EXPIRED_SESSION_STATE, &method(:handle_expired_session_state_event!))
|
187
|
-
@event_handler.register_state_handler(Zookeeper::ZOO_CONNECTED_STATE, &method(:handle_connected_state_event!))
|
188
|
-
@event_handler.register_state_handler(Zookeeper::ZOO_CONNECTING_STATE, &method(:handle_connecting_state_event!))
|
189
|
-
end
|
190
|
-
|
191
|
-
# @private
|
192
|
-
def handle_connected_state_event!(event)
|
193
|
-
reset_connected_event.succeed(event)
|
194
|
-
end
|
195
|
-
|
196
|
-
# @private
|
197
|
-
def handle_connecting_state_event!(event)
|
198
|
-
reset_connecting_event.succeed(event)
|
199
|
-
end
|
200
|
-
|
201
|
-
# @private
|
202
|
-
def handle_expired_session_state_event!(event)
|
203
|
-
exc = ZK::Exceptions::ConnectionLoss.new("Received EXPIRED_SESSION_STATE event: #{event.inspect}")
|
204
|
-
exc.set_backtrace(caller)
|
205
|
-
connection_lost_hook(exc)
|
206
|
-
end
|
207
|
-
|
208
|
-
# @private
|
209
148
|
def connection_lost_hook(exc)
|
210
149
|
if exc and exc.kind_of?(ZK::Exceptions::ConnectionLoss)
|
211
|
-
|
150
|
+
dfr, @connection_lost_deferred = @connection_lost_deferred, Deferred::Default.new
|
151
|
+
dfr.succeed(exc)
|
212
152
|
end
|
213
153
|
end
|
214
154
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ZK
|
2
|
+
module ZKEventMachine
|
3
|
+
module Deferred
|
4
|
+
include EM::Deferrable
|
5
|
+
|
6
|
+
# slight modification to EM::Deferrable,
|
7
|
+
#
|
8
|
+
# @returns [self] to allow for chaining
|
9
|
+
#
|
10
|
+
def callback(&block)
|
11
|
+
super(&block)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# @see #callback
|
16
|
+
def errback(&block)
|
17
|
+
super(&block)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# adds the block to both the callback and errback chains
|
22
|
+
def ensure_that(&block)
|
23
|
+
callback(&block)
|
24
|
+
errback(&block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def chain_to(other_dfr, opts={})
|
28
|
+
other_dfr.callback { |*a| self.succeed(*a) }
|
29
|
+
other_dfr.errback { |*a| self.fail(*a) } unless opts[:ignore_errors]
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
class Default
|
34
|
+
include ZK::ZKEventMachine::Deferred
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module ZK
|
2
|
+
module ZKEventMachine
|
3
|
+
class SynchronyEventHandlerWrapper
|
4
|
+
include ZK::Logging
|
5
|
+
|
6
|
+
def initialize(event_handler)
|
7
|
+
@event_handler = event_handler
|
8
|
+
end
|
9
|
+
|
10
|
+
# registers a block to be called back within a fiber context
|
11
|
+
def register(path, &block)
|
12
|
+
new_block = proc do |*a|
|
13
|
+
Fiber.new { block.call(*a) }.resume
|
14
|
+
end
|
15
|
+
|
16
|
+
@event_handler.register(path, &new_block)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def method_missing(meth, *a, &b)
|
21
|
+
@event_handler.__send__(meth, *a, &b)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# This class is an EM::Synchrony wrapper around a ZK::ZKEventMachine::Client
|
26
|
+
#
|
27
|
+
# It should behave exactly like a ZK::Client instance (when called in a
|
28
|
+
# synchronous fashion), and one should look there for documentation about
|
29
|
+
# the various methods
|
30
|
+
#
|
31
|
+
# @note this class is implemented as a wrapper instead of a subclass of Client
|
32
|
+
# so that it can support the unixisms like rm_rf and mkdir_p. The
|
33
|
+
# synchrony pattern of aliasing the base class methods and specializing for
|
34
|
+
# synchrony didn't work in this case.
|
35
|
+
#
|
36
|
+
class SynchronyClient
|
37
|
+
include ZK::Logging
|
38
|
+
|
39
|
+
attr_reader :event_handler, :client
|
40
|
+
|
41
|
+
# @overload new(client_instance)
|
42
|
+
# Wrap an existing ZK::ZKEventMachine::Client instance in an
|
43
|
+
# EM::Synchrony compatible way
|
44
|
+
# @param [ZK::ZKEventMachine::Client] client_instance an instance of Client to wrap
|
45
|
+
# @overload new(host, opts={})
|
46
|
+
# Creates a new ZK::ZKEventMachine::Client instance to manage
|
47
|
+
# takes the same arguments as ZK::Client::Base
|
48
|
+
def initialize(host, opts={})
|
49
|
+
case host
|
50
|
+
when Client
|
51
|
+
@client = host
|
52
|
+
when String
|
53
|
+
@client = Client.new(host, opts)
|
54
|
+
else
|
55
|
+
raise ArgumentError, "argument must be either a ZK::ZKEventMachine::Client instance or a hostname:port string"
|
56
|
+
end
|
57
|
+
|
58
|
+
@event_handler = SynchronyEventHandlerWrapper.new(@client.event_handler)
|
59
|
+
end
|
60
|
+
|
61
|
+
%w[connect close close! get set create stat delete children get_acl set_acl mkdir_p rm_rf].each do |meth|
|
62
|
+
class_eval(<<-EOMETH, __FILE__, __LINE__ + 1)
|
63
|
+
def #{meth}(*args,&blk)
|
64
|
+
sync!(@client.#{meth}(*args, &blk))
|
65
|
+
end
|
66
|
+
EOMETH
|
67
|
+
end
|
68
|
+
|
69
|
+
# @deprecated for backwards compatibility only
|
70
|
+
def watcher
|
71
|
+
event_handler
|
72
|
+
end
|
73
|
+
|
74
|
+
def exists?(path, opts={})
|
75
|
+
stat(path, opts={}).exists?
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
# a modification of EM::Synchrony.sync to handle multiple callback arguments properly
|
80
|
+
def sync(df)
|
81
|
+
f = Fiber.current
|
82
|
+
|
83
|
+
xback = proc do |*args|
|
84
|
+
if f == Fiber.current
|
85
|
+
return *args
|
86
|
+
else
|
87
|
+
f.resume(*args)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
df.callback(&xback)
|
92
|
+
df.errback(&xback)
|
93
|
+
|
94
|
+
Fiber.yield
|
95
|
+
end
|
96
|
+
|
97
|
+
# like sync, but if the deferred returns an exception instance, re-raises
|
98
|
+
def sync!(deferred)
|
99
|
+
rval = sync(deferred)
|
100
|
+
raise rval if rval.kind_of?(Exception)
|
101
|
+
rval
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
def method_missing(meth, *a, &b)
|
106
|
+
@client.__send__(meth, *a, &b)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
@@ -31,18 +31,15 @@ module ZK
|
|
31
31
|
_handle_calling_convention(dfr, &blk)
|
32
32
|
end
|
33
33
|
|
34
|
-
# @private
|
35
34
|
def find(*paths, &block)
|
36
35
|
raise NotImplementedError, "Coming soon"
|
37
36
|
end
|
38
37
|
|
39
|
-
# @private
|
40
38
|
def block_until_node_deleted(abs_node_path)
|
41
39
|
raise NotImplementedError, "blocking does not make sense in EventMachine-land"
|
42
40
|
end
|
43
41
|
|
44
42
|
protected
|
45
|
-
# @private
|
46
43
|
def _handle_calling_convention(dfr, &blk)
|
47
44
|
return dfr unless blk
|
48
45
|
dfr.callback { |*a| blk.call(nil, *a) }
|
@@ -50,7 +47,6 @@ module ZK
|
|
50
47
|
dfr
|
51
48
|
end
|
52
49
|
|
53
|
-
# @private
|
54
50
|
def _rm_rf_dfr(path)
|
55
51
|
Deferred::Default.new.tap do |my_dfr|
|
56
52
|
delete(path) do |exc|
|
@@ -89,7 +85,6 @@ module ZK
|
|
89
85
|
end
|
90
86
|
end
|
91
87
|
|
92
|
-
# @private
|
93
88
|
def _mkdir_p_dfr(path)
|
94
89
|
Deferred::Default.new.tap do |my_dfr|
|
95
90
|
d = create(path, '')
|
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'eventmachine'
|
2
|
+
require 'em-synchrony'
|
2
3
|
|
3
4
|
require 'zookeeper'
|
4
5
|
require 'zookeeper/em_client'
|
5
6
|
|
6
7
|
require 'zk'
|
7
|
-
|
8
|
+
|
8
9
|
|
9
10
|
module ZK
|
10
11
|
module ZKEventMachine
|
@@ -15,9 +16,11 @@ end
|
|
15
16
|
$LOAD_PATH.unshift(File.expand_path('../..', __FILE__)).uniq!
|
16
17
|
|
17
18
|
require 'z_k/z_k_event_machine/iterator'
|
19
|
+
require 'z_k/z_k_event_machine/deferred'
|
18
20
|
require 'z_k/z_k_event_machine/callback'
|
19
21
|
require 'z_k/z_k_event_machine/event_handler_e_m'
|
20
22
|
require 'z_k/z_k_event_machine/unixisms'
|
21
23
|
require 'z_k/z_k_event_machine/client'
|
24
|
+
require 'z_k/z_k_event_machine/synchrony_client'
|
22
25
|
|
23
26
|
|
data/spec/spec_helper.rb
CHANGED
@@ -10,6 +10,8 @@ require 'evented-spec'
|
|
10
10
|
# in spec/support/ and its subdirectories.
|
11
11
|
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}
|
12
12
|
|
13
|
+
$stderr.sync = true
|
14
|
+
|
13
15
|
case `uname -s`.chomp
|
14
16
|
when 'Linux'
|
15
17
|
$stderr.puts "WARN: setting EM.epoll = true for tests"
|
@@ -19,25 +21,6 @@ when 'Darwin'
|
|
19
21
|
EM.kqueue = true
|
20
22
|
end
|
21
23
|
|
22
|
-
# method to wait until block passed returns true or timeout (default is 2 seconds) is reached
|
23
|
-
def wait_until(timeout=2)
|
24
|
-
time_to_stop = Time.now + timeout
|
25
|
-
|
26
|
-
until yield
|
27
|
-
break if Time.now > time_to_stop
|
28
|
-
Thread.pass
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def wait_while(timeout=2)
|
33
|
-
time_to_stop = Time.now + timeout
|
34
|
-
|
35
|
-
while yield
|
36
|
-
break if Time.now > time_to_stop
|
37
|
-
Thread.pass
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
24
|
RSpec.configure do |config|
|
42
25
|
config.mock_with :flexmock
|
43
26
|
end
|
data/spec/support/logging.rb
CHANGED
@@ -1,19 +1,14 @@
|
|
1
1
|
require 'logger'
|
2
2
|
|
3
|
-
|
3
|
+
logger = Logger.new(File.expand_path('../../../test.log', __FILE__)).tap {|l| l.level = Logger::DEBUG}
|
4
4
|
|
5
|
-
Logger.new(log_file).tap do |log|
|
6
|
-
log.level = Logger::DEBUG
|
7
|
-
ZK.logger = log
|
8
|
-
Zookeeper.logger = log
|
9
|
-
end
|
10
|
-
|
11
|
-
# for debugging along with C output uncomment the following
|
12
|
-
#
|
13
|
-
# $stderr.sync = true
|
14
5
|
# logger = Logger.new($stderr).tap { |l| l.level = Logger::DEBUG }
|
15
6
|
# Zookeeper.set_debug_level(4)
|
16
7
|
|
8
|
+
ZK.logger = logger
|
9
|
+
Zookeeper.logger = ZK.logger
|
10
|
+
|
11
|
+
|
17
12
|
def logger
|
18
13
|
ZK.logger
|
19
14
|
end
|