slyphon-zookeeper 0.3.0-java → 0.8.0-java

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.
@@ -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
+