slyphon-zookeeper 0.3.0-java → 0.8.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,6 +13,7 @@ module ZookeeperEM
13
13
  @em_connection = nil
14
14
  logger.debug { "ZookeeperEM::Client obj_id %x: init" % [object_id] }
15
15
  super(*a, &b)
16
+ on_attached.succeed
16
17
  end
17
18
 
18
19
  # EM::DefaultDeferrable that will be called back when our em_connection has been detached
@@ -29,152 +30,26 @@ module ZookeeperEM
29
30
  @on_attached
30
31
  end
31
32
 
32
- # returns a Deferrable that will be called when the Zookeeper C event loop
33
- # has been shut down
34
- #
35
- # if a block is given, it will be registered as a callback when the
36
- # connection has been closed
37
- #
38
- def close(&block)
39
- on_close(&block)
40
-
41
- logger.debug { "#{self.class.name}: close called, closed? #{closed?} running? #{running?}" }
42
-
43
- if @_running
44
- @start_stop_mutex.synchronize do
45
- @_running = false
46
- end
47
-
48
- if @em_connection
49
- EM.next_tick do
50
- @em_connection.detach do
51
- logger.debug { "#{self.class.name}: connection unbound, continuing with shutdown" }
52
- finish_closing
53
- end
54
- end
55
- else
56
- logger.debug { "#{self.class.name}: em_connection was never set up, finish closing" }
57
- finish_closing
58
- end
59
- else
60
- logger.debug { "#{self.class.name}: we are not running, so returning on_close deferred" }
61
- end
62
-
63
- on_close
64
- end
65
-
66
- # make this public as the ZKConnection object needs to call it
67
- public :dispatch_next_callback
68
-
69
- protected
70
- # instead of setting up a dispatch thread here, we instead attach
71
- # the #selectable_io to the event loop
72
- def setup_dispatch_thread!
33
+ def dispatch_next_callback(hash)
73
34
  EM.schedule do
74
35
  if running? and not closed?
75
- begin
76
- logger.debug { "adding EM.watch(#{selectable_io.inspect})" }
77
- @em_connection = EM.watch(selectable_io, ZKConnection, self) { |cnx| cnx.notify_readable = true }
78
- rescue Exception => e
79
- $stderr.puts "caught exception from EM.watch(): #{e.inspect}"
80
- end
36
+ logger.debug { "#{self.class}##{__method__} dispatch_next_callback: #{hash.inspect}: reactor_thread? #{EM.reactor_thread?}, running? #{running?}, closed? #{closed?}" }
37
+ super(hash)
81
38
  end
82
39
  end
83
40
  end
84
41
 
85
- def finish_closing
86
- unless @_closed
87
- @start_stop_mutex.synchronize do
88
- logger.debug { "closing handle" }
89
- close_handle
90
- end
91
-
92
- unless selectable_io.closed?
93
- logger.debug { "calling close on selectable_io: #{selectable_io.inspect}" }
94
- selectable_io.close
95
- end
96
- end
97
-
42
+ def close(&block)
43
+ on_close(&block)
44
+ super()
98
45
  on_close.succeed
99
46
  end
100
- end
101
-
102
- # this class is handed to EventMachine.watch to handle event dispatching
103
- # when the queue has a message waiting. There's a pipe shared between
104
- # the event thread managed by the queue implementation in C. It's made
105
- # available to the ruby-space through the Zookeeper#selectable_io method.
106
- # When the pipe is readable, that means there's an event waiting. We call
107
- # dispatch_next_event and read a single byte off the pipe.
108
- #
109
- class ZKConnection < EM::Connection
110
-
111
- def initialize(zk_client)
112
- @zk_client = zk_client
113
- end
114
47
 
115
- def post_init
116
- logger.debug { "post_init called" }
117
- @attached = true
118
-
119
- @on_unbind = EM::DefaultDeferrable.new.tap do |d|
120
- d.callback do
121
- logger.debug { "on_unbind deferred fired" }
122
- end
123
- end
124
-
125
- # probably because of the way post_init works, unless we fire this
126
- # callback in next_tick @em_connection in the client may not be set
127
- # (which on_attached callbacks may be relying on)
128
- EM.next_tick do
129
- logger.debug { "firing on_attached callback" }
130
- @zk_client.on_attached.succeed
131
- end
132
- end
133
-
134
- # EM::DefaultDeferrable that will be called back when our em_connection has been detached
135
- # and we've completed the close operation
136
- def on_unbind(&block)
137
- @on_unbind.callback(&block) if block
138
- @on_unbind
139
- end
140
-
141
- def attached?
142
- @attached
143
- end
144
-
145
- def unbind
146
- on_unbind.succeed
147
- end
148
-
149
- def detach(&blk)
150
- on_unbind(&blk)
151
- return unless @attached
152
- @attached = false
153
- rval = super()
154
- logger.debug { "#{self.class.name}: detached, rval: #{rval.inspect}" }
48
+ # Because eventmachine is single-threaded, and events are dispatched on the
49
+ # reactor thread we just delegate this to EM.reactor_thread?
50
+ def event_dispatch_thread?
51
+ EM.reactor_thread?
155
52
  end
156
-
157
- # we have an event waiting
158
- def notify_readable
159
- if @zk_client.running?
160
-
161
- read_io_nb if @zk_client.dispatch_next_callback(false)
162
-
163
- elsif attached?
164
- logger.debug { "#{self.class.name}: @zk_client was not running? and attached? #{attached?}, detaching!" }
165
- detach
166
- end
167
- end
168
-
169
- private
170
- def read_io_nb(size=1)
171
- @io.read_nonblock(1)
172
- rescue Errno::EWOULDBLOCK, Errno::EAGAIN, IOError
173
- end
174
-
175
- def logger
176
- Zookeeper.logger
177
- end
178
- end
179
- end
53
+ end # Client
54
+ end # ZookeeperEM
180
55
 
@@ -27,7 +27,7 @@ module ZookeeperExceptions
27
27
  ZNOTHING = -117
28
28
  ZSESSIONMOVED = -118
29
29
 
30
- class ZookeeperException < Exception
30
+ class ZookeeperException < StandardError
31
31
  class EverythingOk < ZookeeperException; end
32
32
  class SystemError < ZookeeperException; end
33
33
  class RunTimeInconsistency < ZookeeperException; end
@@ -58,6 +58,9 @@ module ZookeeperExceptions
58
58
  class NotConnected < ZookeeperException; end
59
59
  class ShuttingDownException < ZookeeperException; end
60
60
  class DataTooLargeException < ZookeeperException; end
61
+
62
+ # yes, make an alias, this is the way zookeeper refers to it
63
+ ExpiredSession = SessionExpired
61
64
 
62
65
  def self.by_code(code)
63
66
  case code
data/lib/zookeeper.rb CHANGED
@@ -14,7 +14,6 @@ if defined?(::JRUBY_VERSION)
14
14
  $LOAD_PATH.unshift(File.expand_path('../java', File.dirname(__FILE__))).uniq!
15
15
  else
16
16
  $LOAD_PATH.unshift(File.expand_path('../ext', File.dirname(__FILE__))).uniq!
17
- require 'zookeeper_c'
18
17
  end
19
18
 
20
19
  require 'zookeeper_base'
@@ -33,6 +32,7 @@ class Zookeeper < ZookeeperBase
33
32
  end
34
33
 
35
34
  def reopen(timeout=10, watcher=nil)
35
+ warn "WARN: ZookeeperBase#reopen watcher argument is now ignored" if watcher
36
36
  super
37
37
  end
38
38
 
@@ -121,6 +121,24 @@ class Zookeeper < ZookeeperBase
121
121
  { :req_id => req_id, :rc => rc }
122
122
  end
123
123
 
124
+ # this method is *only* asynchronous
125
+ #
126
+ # @note There is a discrepancy between the zkc and java versions. zkc takes
127
+ # a string_callback_t, java takes a VoidCallback. You should most likely use
128
+ # the ZookeeperCallbacks::VoidCallback and not rely on the string value.
129
+ #
130
+ def sync(options = {})
131
+ assert_open
132
+ assert_supported_keys(options, [:path, :callback, :callback_context])
133
+ assert_required_keys(options, [:path, :callback])
134
+
135
+ req_id = setup_call(:sync, options)
136
+
137
+ rc = super(req_id, options[:path]) # we don't pass options[:callback] here as this method is *always* async
138
+
139
+ { :req_id => req_id, :rc => rc }
140
+ end
141
+
124
142
  def set_acl(options = {})
125
143
  assert_open
126
144
  assert_supported_keys(options, [:path, :acl, :version, :callback, :callback_context])
@@ -165,12 +183,40 @@ class Zookeeper < ZookeeperBase
165
183
  def associating?
166
184
  super
167
185
  end
186
+
187
+ # There are some operations that are dangerous in the context of the event
188
+ # dispatch thread (because they would block further event delivery). This
189
+ # method allows clients to know if they're currently executing in the context of an
190
+ # event.
191
+ #
192
+ # @returns [true,false] true if the current thread is the event dispatch thread
193
+ def event_dispatch_thread?
194
+ super
195
+ end
168
196
 
169
197
  # for expert use only. set the underlying debug level for the C layer, has no
170
198
  # effect in java
171
199
  #
200
+ # @private
172
201
  def self.set_debug_level(val)
173
- super
202
+ if defined?(::CZookeeper)
203
+ CZookeeper.set_debug_level(val.to_i)
204
+ end
205
+ end
206
+
207
+ # @private
208
+ def self.get_debug_level
209
+ if defined?(::CZookeeper)
210
+ CZookeeper.get_debug_level
211
+ end
212
+ end
213
+
214
+ class << self
215
+ # @private
216
+ alias :debug_level= :set_debug_level
217
+
218
+ # @private
219
+ alias :debug_level :get_debug_level
174
220
  end
175
221
 
176
222
  # DEPRECATED: use the class-level method instead
@@ -188,18 +234,6 @@ class Zookeeper < ZookeeperBase
188
234
  super
189
235
  end
190
236
 
191
- # returns an IO object that will be readable when an event is ready for dispatching
192
- # (for internal use only)
193
- def selectable_io
194
- super
195
- end
196
-
197
- # closes the underlying connection object
198
- # (for internal use only)
199
- def close_handle
200
- super
201
- end
202
-
203
237
  # return the session id of the current connection as an Fixnum
204
238
  def session_id
205
239
  super
@@ -244,8 +278,10 @@ protected
244
278
  private
245
279
  # TODO: Sanitize user mistakes by unregistering watchers from ops that
246
280
  # don't return ZOK (except wexists)? Make users clean up after themselves for now.
281
+ #
282
+ # XXX: is this dead code?
247
283
  def unregister_watcher(req_id)
248
- @req_mutex.synchronize {
284
+ @mutex.synchronize {
249
285
  @watcher_reqs.delete(req_id)
250
286
  }
251
287
  end
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "slyphon-zookeeper"
6
- s.version = '0.3.0'
6
+ s.version = '0.8.0'
7
7
 
8
8
  s.authors = ["Phillip Pearson", "Eric Maland", "Evan Weaver", "Brian Wickman", "Neil Conway", "Jonathan D. Simms"]
9
9
  s.email = ["slyphon@gmail.com"]
@@ -0,0 +1,50 @@
1
+ # tests the CZookeeper, obviously only available when running under MRI
2
+ require 'spec_helper'
3
+
4
+ if Module.const_defined?(:CZookeeper)
5
+ describe CZookeeper do
6
+ def pop_all_events
7
+ [].tap do |rv|
8
+ begin
9
+ rv << @event_queue.pop(non_blocking=true)
10
+ rescue ThreadError
11
+ end
12
+ end
13
+ end
14
+
15
+ def wait_until_connected(timeout=2)
16
+ wait_until(timeout) { @czk.state == ZookeeperConstants::ZOO_CONNECTED_STATE }
17
+ end
18
+
19
+ describe do
20
+ before do
21
+ @event_queue = ZookeeperCommon::QueueWithPipe.new
22
+ @czk = CZookeeper.new('localhost:2181', @event_queue)
23
+ end
24
+
25
+ after do
26
+ @czk.close rescue Exception
27
+ @event_queue.close rescue Exception
28
+ end
29
+
30
+ it %[should be in connected state within a reasonable amount of time] do
31
+ wait_until_connected.should be_true
32
+ end
33
+
34
+ describe :after_connected do
35
+ before do
36
+ wait_until_connected.should be_true
37
+ end
38
+
39
+ it %[should have a connection event after being connected] do
40
+ event = wait_until(2) { @event_queue.pop }
41
+ event.should be
42
+ event[:req_id].should == ZookeeperCommon::ZKRB_GLOBAL_CB_REQ
43
+ event[:type].should == ZookeeperConstants::ZOO_SESSION_EVENT
44
+ event[:state].should == ZookeeperConstants::ZOO_CONNECTED_STATE
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+ require 'shared/connection_examples'
3
+
4
+ describe 'Zookeeper chrooted' do
5
+ let(:path) { "/_zkchroottest_" }
6
+ let(:data) { "underpants" }
7
+ let(:chroot_path) { '/slyphon-zookeeper-chroot' }
8
+
9
+ let(:connection_string) { "localhost:2181#{chroot_path}" }
10
+
11
+ before do
12
+ @zk = Zookeeper.new(connection_string)
13
+ end
14
+
15
+ after do
16
+ @zk and @zk.close
17
+ end
18
+
19
+
20
+ def zk
21
+ @zk
22
+ end
23
+
24
+ def with_open_zk(host='localhost:2181')
25
+ z = Zookeeper.new(host)
26
+ yield z
27
+ ensure
28
+ z.close
29
+
30
+ wait_until do
31
+ begin
32
+ !z.connected?
33
+ rescue RuntimeError
34
+ true
35
+ end
36
+ end
37
+ end
38
+
39
+ # this is not as safe as the one in ZK, just to be used to clean up
40
+ # when we're the only one adjusting a particular path
41
+ def rm_rf(z, path)
42
+ z.get_children(:path => path).tap do |h|
43
+ if h[:rc].zero?
44
+ h[:children].each do |child|
45
+ rm_rf(z, File.join(path, child))
46
+ end
47
+ elsif h[:rc] == ZookeeperExceptions::ZNONODE
48
+ # no-op
49
+ else
50
+ raise "Oh noes! unexpected return value! #{h.inspect}"
51
+ end
52
+ end
53
+
54
+ rv = z.delete(:path => path)
55
+
56
+ unless (rv[:rc].zero? or rv[:rc] == ZookeeperExceptions::ZNONODE)
57
+ raise "oh noes! failed to delete #{path}"
58
+ end
59
+
60
+ path
61
+ end
62
+
63
+ describe 'non-existent' do
64
+ describe 'with existing parent' do
65
+ let(:chroot_path) { '/one-level' }
66
+
67
+ describe 'create' do
68
+ before do
69
+ with_open_zk do |z|
70
+ rm_rf(z, chroot_path)
71
+ end
72
+ end
73
+
74
+ it %[should successfully create the path] do
75
+ rv = zk.create(:path => '/', :data => '')
76
+ rv[:rc].should be_zero
77
+ rv[:path].should == ''
78
+ end
79
+ end
80
+ end
81
+
82
+ describe 'with missing parent' do
83
+ let(:chroot_path) { '/deeply/nested/path' }
84
+
85
+ describe 'create' do
86
+ before do
87
+ with_open_zk do |z|
88
+ rm_rf(z, chroot_path)
89
+ end
90
+ end
91
+
92
+ it %[should return ZNONODE] do
93
+ rv = zk.create(:path => '/', :data => '')
94
+ rv[:rc].should_not be_zero
95
+ rv[:rc].should == ZookeeperExceptions::ZNONODE
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+
102
+ describe do
103
+ before :all do
104
+ Zookeeper.logger.warn "running before :all"
105
+
106
+ with_open_zk do |z|
107
+ z.create(:path => chroot_path, :data => '')
108
+ end
109
+ end
110
+
111
+ after :all do
112
+ with_open_zk do |z|
113
+ rm_rf(z, chroot_path)
114
+ end
115
+ end
116
+
117
+ it_should_behave_like "connection"
118
+ end
119
+ end
120
+
121
+
data/spec/em_spec.rb CHANGED
@@ -28,66 +28,6 @@ describe 'ZookeeperEM' do
28
28
  end
29
29
  end
30
30
 
31
- describe 'selectable_io' do
32
- it %[should return an IO object] do
33
- setup_zk do
34
- @zk.selectable_io.should be_instance_of(IO)
35
- teardown_and_done
36
- end
37
- end
38
-
39
- it %[should not be closed] do
40
- setup_zk do
41
- @zk.selectable_io.should_not be_closed
42
- teardown_and_done
43
- end
44
- end
45
-
46
- before do
47
- @data_cb = ZookeeperCallbacks::DataCallback.new do
48
- logger.debug { "cb called: #{@data_cb.inspect}" }
49
- end
50
- end
51
-
52
- it %[should be read-ready if there's an event waiting] do
53
- setup_zk do
54
- @zk.get(:path => "/", :callback => @data_cb)
55
-
56
- r, *_ = IO.select([@zk.selectable_io], [], [], 2)
57
-
58
- r.should be_kind_of(Array)
59
-
60
- teardown_and_done
61
- end
62
- end
63
- end
64
-
65
- describe 'em_connection' do
66
- before do
67
- @zk = ZookeeperEM::Client.new('localhost:2181')
68
- end
69
-
70
- it %[should be nil before the reactor is started] do
71
- @zk.em_connection.should be_nil
72
-
73
- em do
74
- teardown_and_done
75
- end
76
- end
77
-
78
- it %[should fire off the on_attached callbacks once the reactor is managing us] do
79
- @zk.on_attached do |*|
80
- @zk.em_connection.should_not be_nil
81
- @zk.em_connection.should be_instance_of(ZookeeperEM::ZKConnection)
82
- teardown_and_done
83
- end
84
-
85
- em do
86
- EM.reactor_running?.should be_true
87
- end
88
- end
89
- end
90
-
91
31
  describe 'callbacks' do
92
32
  it %[should be called on the reactor thread] do
93
33
  cb = lambda do |h|
@@ -105,7 +45,6 @@ describe 'ZookeeperEM' do
105
45
  end
106
46
  end
107
47
  end
108
-
109
48
  end
110
49
  end
111
50
 
@@ -0,0 +1,10 @@
1
+ shared_examples_for "all success return values" do
2
+ it %[should have a return code of Zookeeper::ZOK] do
3
+ @rv[:rc].should == Zookeeper::ZOK
4
+ end
5
+
6
+ it %[should have a req_id integer] do
7
+ @rv[:req_id].should be_kind_of(Integer)
8
+ end
9
+ end
10
+