zk-eventmachine 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ A small place to hide goodies that I use while developing this
2
+
3
+ slyphon-project.vimrc is a local .vimrc file with abbrevs.
4
+ rvmrc is my .rvmrc
5
+
data/.dev_extras/rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.dev_extras/rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.2
@@ -0,0 +1,18 @@
1
+ iabbr _ZEM ZK::ZKEventMachine
2
+ iabbr _ZCB ZK::ZKEventMachine::Callback
3
+ iabbr _ZCLI ZK::ZKEventMachine::Client
4
+
5
+ iabbr _wia i.with_indifferent_access
6
+ iabbr _lam lambda {}hi
7
+
8
+ iabbr _seq i.should ==
9
+ iabbr _srex i.should raise_exception()i
10
+ iabbr _srexarg i.should raise_exception(ArgumentError)i
11
+ iabbr _snrex i.should_not raise_exception
12
+ iabbr _sbnil i.should be_nil
13
+
14
+ iabbr _desc describe do
15
+ iabbr _bef before do
16
+
17
+
18
+ " vim:ft=vim
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
6
+ .vimrc
7
+ .rspec
8
+ *.log
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+ source 'http://localhost:50000'
3
+
4
+
5
+ # Specify your gem's dependencies in zk-em.gemspec
6
+ gemspec
7
+
8
+
9
+ # vim:ft=ruby
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2011 by Hewlett Packard Development Company, L.P.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,8 @@
1
+ ## OMG!
2
+
3
+ [ZK][] client implementation for [EventMachine][]
4
+
5
+
6
+ [ZK]: https://github.com/slyphon/zk
7
+ [EventMachine]: https://github.com/eventmachine/eventmachine
8
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,256 @@
1
+ module ZK
2
+ module ZKEventMachine
3
+ # some improvements (one hopes) around the zookeeper gem's somewhat (ahem)
4
+ # minimal Callback class
5
+ #
6
+ module Callback
7
+
8
+ # Used by ZooKeeper to return an asynchronous result.
9
+ #
10
+ # If callbacks or errbacks are set on the instance, they will be called
11
+ # with just the data returned from the call (much like their synchronous
12
+ # versions).
13
+ #
14
+ # If a block was given to #new or #on_result, then that block is called
15
+ # with a ZK::Exceptions::KeeperException instance or nil, then the rest
16
+ # of the arguments defined for that callback type
17
+ #
18
+ # the node-style and deferred-style results are *NOT* exclusive, so if
19
+ # you use both _you will be called with results in both formats_.
20
+ #
21
+ class Base
22
+ include Deferred
23
+ include ZK::Logging
24
+
25
+ # set the result keys that should be used by node_style_result and to
26
+ # call the deferred_style_result blocks
27
+ #
28
+ def self.async_result_keys(*syms)
29
+ if syms.empty?
30
+ @async_result_keys || []
31
+ else
32
+ @async_result_keys = syms.map { |n| n.to_sym }
33
+ end
34
+ end
35
+
36
+ def initialize(prok=nil, &block)
37
+ on_result(prok, &block)
38
+ end
39
+
40
+ # register a block that should be called (node.js style) with the
41
+ # results
42
+ #
43
+ # @note replaces the block given to #new
44
+ #
45
+ def on_result(prok=nil, &block)
46
+ @block = (prok || block)
47
+ end
48
+
49
+ # Checks the return code from the async call. If the return code was not ZOK,
50
+ # then fire the errbacks and do the node-style error call
51
+ # otherwise, does nothing
52
+ def check_async_rc(hash)
53
+ call(hash) unless success?(hash)
54
+ end
55
+
56
+ # ZK will call this instance with a hash of data, which is the result
57
+ # of the asynchronous call. Depending on the style of callback in use,
58
+ # we take the appropriate actions
59
+ #
60
+ # delegates to #deferred_style_result and #node_style_result
61
+ def call(hash)
62
+ # logger.debug { "#{self.class.name}#call hash: #{hash.inspect}" }
63
+ EM.schedule do
64
+ deferred_style_result(hash)
65
+ node_style_result(hash)
66
+ end
67
+ end
68
+
69
+ # returns true if the request was successful (if return_code was Zookeeper::ZOK)
70
+ #
71
+ # @param [Hash] hash the result of the async call
72
+ #
73
+ # @returns [true, false] for success, failure
74
+ def success?(hash)
75
+ hash[:rc] == Zookeeper::ZOK
76
+ end
77
+
78
+ # Returns an instance of a sublcass ZK::Exceptions::KeeperException
79
+ # based on the asynchronous return_code.
80
+ #
81
+ # facilitates using case statements for error handling
82
+ #
83
+ # @param [Hash] hash the result of the async call
84
+ #
85
+ # @raise [RuntimeError] if the return_code is not known by ZK (this should never
86
+ # happen and if it does, you should report a bug)
87
+ #
88
+ # @return [ZK::Exceptions::KeeperException, nil] subclass based on
89
+ # return_code if there was an error, nil otherwise
90
+ #
91
+ def exception_for(hash)
92
+ return nil if success?(hash)
93
+ return_code = hash.fetch(:rc)
94
+ ZK::Exceptions::KeeperException.by_code(return_code).new
95
+ end
96
+
97
+ # @abstract should call set_deferred_status with the appropriate args
98
+ # for the result and type of call
99
+ def deferred_style_result(hash)
100
+ # ensure this calls the callback on the reactor
101
+
102
+ if success?(hash)
103
+ vals = hash.values_at(*async_result_keys)
104
+ # logger.debug { "#{self.class.name}#deferred_style_result async_result_keys: #{async_result_keys.inspect}, vals: #{vals.inspect}" }
105
+ self.succeed(*hash.values_at(*async_result_keys))
106
+ else
107
+ self.fail(exception_for(hash))
108
+ end
109
+ end
110
+
111
+ # call the user block with the correct Exception class as the first arg
112
+ # (or nil if no error) and then the appropriate args for the type of
113
+ # asynchronous call
114
+ def node_style_result(hash)
115
+ return unless @block
116
+ vals = hash.values_at(*async_result_keys)
117
+ # logger.debug { "#{self.class.name}#node_style_result async_result_keys: #{async_result_keys.inspect}, vals: #{vals.inspect}" }
118
+ if exc = exception_for(hash)
119
+ @block.call(exc)
120
+ else
121
+ @block.call(nil, *vals)
122
+ end
123
+ end
124
+
125
+ protected
126
+ def async_result_keys
127
+ self.class.async_result_keys
128
+ end
129
+ end
130
+
131
+ # used with Client#get call
132
+ class DataCallback < Base
133
+ async_result_keys :data, :stat
134
+ end
135
+
136
+ # used with Client#children call
137
+ class ChildrenCallback < Base
138
+ async_result_keys :strings, :stat
139
+ end
140
+
141
+ # used with Client#create
142
+ class StringCallback < Base
143
+ async_result_keys :string
144
+ end
145
+
146
+ # used with Client#stat and Client#exists?
147
+ class StatCallback < Base
148
+ async_result_keys :stat
149
+
150
+ # stat has a different concept of 'success', stat on a node that doesn't
151
+ # exist is not an exception, it's a certain kind of stat (like a null stat).
152
+ def success?(hash)
153
+ rc = hash[:rc]
154
+ (rc == Zookeeper::ZOK) || (rc == Zookeeper::ZNONODE)
155
+ end
156
+ end
157
+
158
+ # supports the syntactic sugar exists? call
159
+ class ExistsCallback < StatCallback
160
+ async_result_keys :stat
161
+
162
+ # @abstract should call set_deferred_status with the appropriate args
163
+ # for the result and type of call
164
+ def deferred_style_result(hash)
165
+ # ensure this calls the callback on the reactor
166
+
167
+ if success?(hash)
168
+ succeed(hash[:stat].exists?)
169
+ else
170
+ fail(exception_for(hash))
171
+ end
172
+ end
173
+
174
+ # call the user block with the correct Exception class as the first arg
175
+ # (or nil if no error) and then the appropriate args for the type of
176
+ # asynchronous call
177
+ def node_style_result(hash)
178
+ return unless @block
179
+ @block.call(exception_for(hash), hash[:stat].exists?)
180
+ end
181
+ end
182
+
183
+ # set operation returns a stat object, but in this case a NONODE is an
184
+ # error (unlike with StatCallback)
185
+ class SetCallback < Base
186
+ async_result_keys :stat
187
+ end
188
+
189
+ # used with Client#delete and Client#set_acl
190
+ class VoidCallback < Base
191
+ end
192
+
193
+ # used with Client#get_acl
194
+ class ACLCallback < Base
195
+ async_result_keys :acl, :stat
196
+ end
197
+
198
+ class << self
199
+ def new_data_cb(njs_block)
200
+ DataCallback.new(njs_block).tap do |cb| # create the callback with the user-provided block
201
+ cb.check_async_rc(yield(cb)) # yield the callback to the caller, check the return result
202
+ # of the async operation (not the async result)
203
+ end # return the callback
204
+ end
205
+ alias :new_get_cb :new_data_cb # create alias so that this matches the client API name
206
+
207
+ def new_string_cb(njs_block)
208
+ StringCallback.new(njs_block).tap do |cb|
209
+ cb.check_async_rc(yield(cb))
210
+ end
211
+ end
212
+ alias :new_create_cb :new_string_cb
213
+
214
+ def new_stat_cb(njs_block)
215
+ StatCallback.new(njs_block).tap do |cb|
216
+ cb.check_async_rc(yield(cb))
217
+ end
218
+ end
219
+
220
+ def new_exists_cb(njs_block)
221
+ ExistsCallback.new(njs_block).tap do |cb|
222
+ cb.check_async_rc(yield(cb))
223
+ end
224
+ end
225
+
226
+ def new_set_cb(njs_block)
227
+ SetCallback.new(njs_block).tap do |cb|
228
+ cb.check_async_rc(yield(cb))
229
+ end
230
+ end
231
+
232
+ def new_void_cb(njs_block)
233
+ VoidCallback.new(njs_block).tap do |cb|
234
+ cb.check_async_rc(yield(cb))
235
+ end
236
+ end
237
+ alias :new_delete_cb :new_void_cb
238
+ alias :new_set_acl_cb :new_void_cb
239
+
240
+ def new_children_cb(njs_block)
241
+ ChildrenCallback.new(njs_block).tap do |cb|
242
+ cb.check_async_rc(yield(cb))
243
+ end
244
+ end
245
+
246
+ def new_acl_cb(njs_block)
247
+ ACLCallback.new(njs_block).tap do |cb|
248
+ cb.check_async_rc(yield(cb))
249
+ end
250
+ end
251
+ alias :new_get_acl_cb :new_acl_cb
252
+ end
253
+ end
254
+ end
255
+ end
256
+
@@ -0,0 +1,122 @@
1
+ module ZK
2
+ module ZKEventMachine
3
+ class Client < ZK::Client::Base
4
+ include ZK::Logging
5
+ include Unixisms
6
+
7
+ DEFAULT_TIMEOUT = 10
8
+
9
+ attr_reader :client
10
+
11
+ # Takes same options as ZK::Client::Base
12
+ def initialize(host, opts={})
13
+ @host = host
14
+ @close_deferred = Deferred::Default.new
15
+ @event_handler = EventHandlerEM.new(self)
16
+ end
17
+
18
+ def on_close(&blk)
19
+ @close_deferred.callback(&blk) if blk
20
+ @close_deferred
21
+ end
22
+
23
+ # open a ZK connection, attach it to the reactor.
24
+ # returns an EM::Deferrable that will be called when the connection is
25
+ # ready for use
26
+ def connect(&blk)
27
+ # XXX: maybe move this into initialize, need to figure out how to schedule it properly
28
+ @cnx ||= ZookeeperEM::Client.new(@host, DEFAULT_TIMEOUT, event_handler.get_default_watcher_block).tap do |c|
29
+ c.on_attached { logger.debug { "connection is attached" } }
30
+ end
31
+
32
+ @cnx.on_attached(&blk)
33
+ end
34
+
35
+ def reopen(*a)
36
+ raise NotImplementedError, "reoopen is not implemented for the eventmachine version of the client"
37
+ end
38
+
39
+ def close!(&blk)
40
+ on_close(&blk)
41
+
42
+ if @cnx
43
+ logger.debug { "#{self.class.name}: calling @cnx.close" }
44
+ @cnx.close
45
+ @cnx = nil
46
+
47
+ logger.debug { "#{self.class.name}: @cnx.close callback fired, clearing event_handler" }
48
+ event_handler.clear!
49
+ logger.debug { "firing on_close handler" }
50
+ on_close.succeed
51
+ else
52
+ on_close.succeed
53
+ end
54
+
55
+ on_close
56
+ end
57
+ alias :close :close!
58
+
59
+ # get data at path, optionally enabling a watch on the node
60
+ #
61
+ # @returns [Callback] returns a Callback which is an EM::Deferred (so you
62
+ # can assign callbacks/errbacks) see Callback::Base for discussion
63
+ #
64
+ def get(path, opts={}, &block)
65
+ Callback.new_get_cb(block) do |cb|
66
+ super(path, opts.merge(:callback => cb))
67
+ end
68
+ end
69
+
70
+ def create(path, data='', opts={}, &block)
71
+ Callback.new_create_cb(block) do |cb|
72
+ super(path, data, opts.merge(:callback => cb))
73
+ end
74
+ end
75
+
76
+ def set(path, data, opts={}, &block)
77
+ Callback.new_set_cb(block) do |cb|
78
+ super(path, data, opts.merge(:callback => cb))
79
+ end
80
+ end
81
+
82
+ def stat(path, opts={}, &block)
83
+ cb_style = opts.delete(:cb_style) { |_| 'stat' }
84
+
85
+ meth = :"new_#{cb_style}_cb"
86
+
87
+ Callback.__send__(meth, block) do |cb|
88
+ super(path, opts.merge(:callback => cb))
89
+ end
90
+ end
91
+
92
+ def exists?(path, opts={}, &block)
93
+ stat(path, opts.merge(:cb_style => 'exists'), &block)
94
+ end
95
+
96
+ def delete(path, opts={}, &block)
97
+ Callback.new_delete_cb(block) do |cb|
98
+ super(path, opts.merge(:callback => cb))
99
+ end
100
+ end
101
+
102
+ def children(path, opts={}, &block)
103
+ Callback.new_children_cb(block) do |cb|
104
+ super(path, opts.merge(:callback => cb))
105
+ end
106
+ end
107
+
108
+ def get_acl(path, opts={}, &block)
109
+ Callback.new_get_acl_cb(block) do |cb|
110
+ super(path, opts.merge(:callback => cb))
111
+ end
112
+ end
113
+
114
+ def set_acl(path, acls, opts={}, &block)
115
+ Callback.new_set_acl_cb(block) do |cb|
116
+ super(path, acls, opts.merge(:callback => cb))
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+
@@ -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,34 @@
1
+ module ZK
2
+ module ZKEventMachine
3
+ # a small wrapper around the EventHandler instance, allowing us to
4
+ # deliver the event on the reactor thread, as opposed to calling it directly
5
+ #
6
+ class EventHandlerEM < ZK::EventHandler
7
+ include ZK::Logging
8
+
9
+ def register(path, &block)
10
+ # use the supplied block, but ensure that it gets called on the reactor
11
+ # thread
12
+ new_blk = lambda do |*a|
13
+ EM.schedule { block.call(*a) }
14
+ end
15
+
16
+ super(path, &new_blk)
17
+ end
18
+ alias :subscribe :register
19
+
20
+ def process(event)
21
+ EM.schedule { super(event) }
22
+ end
23
+
24
+ protected
25
+ # we're running on the Reactor, don't need to synchronize (hah, hah, we'll see...)
26
+ #
27
+ def synchronize
28
+ yield
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+