slyphon-zookeeper 0.1.7 → 0.2.0

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/lib/zookeeper.rb CHANGED
@@ -19,6 +19,18 @@ end
19
19
  require 'zookeeper_base'
20
20
 
21
21
  class Zookeeper < ZookeeperBase
22
+ unless defined?(@@logger)
23
+ @@logger = Logger.new('/dev/null').tap { |l| l.level = Logger::FATAL } # UNIX: FOR GREAT JUSTICE !!
24
+ end
25
+
26
+ def self.logger
27
+ @@logger
28
+ end
29
+
30
+ def self.logger=(logger)
31
+ @@logger = logger
32
+ end
33
+
22
34
  def reopen(timeout=10, watcher=nil)
23
35
  super
24
36
  end
@@ -130,6 +142,11 @@ class Zookeeper < ZookeeperBase
130
142
  options[:callback] ? rv : rv.merge(:acl => acls, :stat => Stat.new(stat))
131
143
  end
132
144
 
145
+ # close this client and any underyling connections
146
+ def close
147
+ super
148
+ end
149
+
133
150
  def state
134
151
  super
135
152
  end
@@ -146,6 +163,61 @@ class Zookeeper < ZookeeperBase
146
163
  super
147
164
  end
148
165
 
166
+ # for expert use only. set the underlying debug level for the C layer, has no
167
+ # effect in java
168
+ #
169
+ def self.set_debug_level(val)
170
+ super
171
+ end
172
+
173
+ # DEPRECATED: use the class-level method instead
174
+ def set_debug_level(val)
175
+ super
176
+ end
177
+
178
+ # has the underlying connection been closed?
179
+ def closed?
180
+ super
181
+ end
182
+
183
+ # is the event delivery system running?
184
+ def running?
185
+ super
186
+ end
187
+
188
+ # returns an IO object that will be readable when an event is ready for dispatching
189
+ # (for internal use only)
190
+ def selectable_io
191
+ super
192
+ end
193
+
194
+ # closes the underlying connection object
195
+ # (for internal use only)
196
+ def close_handle
197
+ super
198
+ end
199
+
200
+ protected
201
+ # used during shutdown, awaken the event delivery thread if it's blocked
202
+ # waiting for the next event
203
+ def wake_event_loop!
204
+ super
205
+ end
206
+
207
+ # starts the event delivery subsystem going. after calling this method, running? will be true
208
+ def setup_dispatch_thread!
209
+ super
210
+ end
211
+
212
+ # TODO: describe what this does
213
+ def get_default_global_watcher
214
+ super
215
+ end
216
+
217
+ def logger
218
+ Zookeeper.logger
219
+ end
220
+
149
221
  private
150
222
  def setup_call(opts)
151
223
  req_id = nil
@@ -175,6 +247,5 @@ private
175
247
  def assert_open
176
248
  super
177
249
  end
178
-
179
250
  end
180
251
 
@@ -4,18 +4,9 @@ module ZookeeperCommon
4
4
  # sigh, i guess define this here?
5
5
  ZKRB_GLOBAL_CB_REQ = -1
6
6
 
7
- def self.included(mod)
8
- mod.extend(ZookeeperCommon::ClassMethods)
9
- end
10
-
11
- module ClassMethods
12
- def logger
13
- @logger ||= Logger.new('/dev/null') # UNIX: YOU MUST USE IT!
14
- end
15
-
16
- def logger=(logger)
17
- @logger = logger
18
- end
7
+ def get_next_event(blocking=true)
8
+ return nil if closed? # protect against this happening in a callback after close
9
+ super(blocking)
19
10
  end
20
11
 
21
12
  protected
@@ -50,13 +41,12 @@ protected
50
41
  @req_mutex.synchronize { @completion_reqs.delete(req_id) }
51
42
  end
52
43
 
44
+ def dispatch_next_callback(blocking=true)
45
+ hash = get_next_event(blocking)
46
+ # Zookeeper.logger.debug { "get_next_event returned: #{hash.inspect}" }
53
47
 
54
- def dispatch_next_callback
55
- hash = get_next_event
56
48
  return nil unless hash
57
49
 
58
- logger.debug { "dispatch_next_callback got event: #{hash.inspect}" }
59
-
60
50
  is_completion = hash.has_key?(:rc)
61
51
 
62
52
  hash[:stat] = ZookeeperStat::Stat.new(hash[:stat]) if hash.has_key?(:stat)
@@ -81,9 +71,9 @@ protected
81
71
  else
82
72
  logger.warn { "Duplicate event received (no handler for req_id #{hash[:req_id]}, event: #{hash.inspect}" }
83
73
  end
74
+ true
84
75
  end
85
76
 
86
-
87
77
  def assert_supported_keys(args, supported)
88
78
  unless (args.keys - supported).empty?
89
79
  raise ZookeeperExceptions::ZookeeperException::BadArguments, # this heirarchy is kind of retarded
@@ -98,9 +88,5 @@ protected
98
88
  end
99
89
  end
100
90
 
101
- # supplied by parent class impl.
102
- def logger
103
- self.class.logger
104
- end
105
91
  end
106
92
 
@@ -0,0 +1,135 @@
1
+ require 'zookeeper'
2
+ require 'eventmachine'
3
+
4
+ module ZookeeperEM
5
+ class Client < Zookeeper
6
+ # @private
7
+ # the EM Connection instance we receive once we call EM.watch on our selectable_io
8
+ attr_reader :em_connection
9
+
10
+ def initialize(*a, &b)
11
+ @on_close = EM::DefaultDeferrable.new
12
+ @on_attached = EM::DefaultDeferrable.new
13
+ @em_connection = nil
14
+ super(*a, &b)
15
+ end
16
+
17
+ # EM::DefaultDeferrable that will be called back when our em_connection has been detached
18
+ # and we've completed the close operation
19
+ def on_close(&block)
20
+ @on_close.callback(&block) if block
21
+ @on_close
22
+ end
23
+
24
+ # called after we've successfully registered our selectable_io to be
25
+ # managed by the EM reactor
26
+ def on_attached(&block)
27
+ @on_attached.callback(&block) if block
28
+ @on_attached
29
+ end
30
+
31
+ # returns a Deferrable that will be called when the Zookeeper C event loop
32
+ # has been shut down
33
+ #
34
+ # if a block is given, it will be registered as a callback when the
35
+ # connection has been closed
36
+ #
37
+ def close(&block)
38
+ on_close(&block)
39
+
40
+ logger.debug { "close called, closed? #{closed?} running? #{running?}" }
41
+
42
+ if @_running
43
+ @start_stop_mutex.synchronize do
44
+ @_running = false
45
+ end
46
+
47
+ @em_connection.detach if @em_connection
48
+ @em_connection = nil
49
+
50
+ unless @_closed
51
+ @start_stop_mutex.synchronize do
52
+ logger.debug { "closing handle" }
53
+ close_handle
54
+ end
55
+
56
+ selectable_io.close unless selectable_io.closed?
57
+ end
58
+ else
59
+ logger.debug { "we are not running, so returning on_close deferred" }
60
+ end
61
+
62
+ on_close.succeed
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!
73
+ EM.schedule do
74
+ if running? and not closed?
75
+ begin
76
+ @em_connection = EM.watch(selectable_io, ZKConnection, self) { |cnx| cnx.notify_readable = true }
77
+ rescue Exception => e
78
+ $stderr.puts "caught exception from EM.watch(): #{e.inspect}"
79
+ end
80
+ on_attached.succeed
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ # this class is handed to EventMachine.watch to handle event dispatching
87
+ # when the queue has a message waiting. There's a pipe shared between
88
+ # the event thread managed by the queue implementation in C. It's made
89
+ # available to the ruby-space through the Zookeeper#selectable_io method.
90
+ # When the pipe is readable, that means there's an event waiting. We call
91
+ # dispatch_next_event and read a single byte off the pipe.
92
+ #
93
+ class ZKConnection < EM::Connection
94
+
95
+ def initialize(zk_client)
96
+ @zk_client = zk_client
97
+ @attached = true
98
+ end
99
+
100
+ def attached?
101
+ @attached
102
+ end
103
+
104
+ def detach
105
+ return unless @attached
106
+ @attached = false
107
+ super
108
+ logger.debug { "#{self.class.name}: detached" }
109
+ end
110
+
111
+ # we have an event waiting
112
+ def notify_readable
113
+ if @zk_client.running?
114
+ # logger.debug { "#{self.class.name}: dispatching events while #{@zk_client.running?}" }
115
+
116
+ read_io_nb if @zk_client.dispatch_next_callback(false)
117
+
118
+ elsif attached?
119
+ logger.debug { "#{self.class.name}: @zk_client was not running? and attached? #{attached?}, detaching!" }
120
+ detach
121
+ end
122
+ end
123
+
124
+ private
125
+ def read_io_nb(size=1)
126
+ @io.read_nonblock(1)
127
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN, IOError
128
+ end
129
+
130
+ def logger
131
+ Zookeeper.logger
132
+ end
133
+ end
134
+ end
135
+
@@ -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.1.7'
6
+ s.version = '0.2.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"]
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.require_paths = ["lib"]
18
18
 
19
- if ENV['JAVA_GEM']
19
+ if ENV['JAVA_GEM'] or defined?(::JRUBY_VERSION)
20
20
  s.platform = 'java'
21
21
  s.add_runtime_dependency('slyphon-log4j', '= 1.2.15')
22
22
  s.add_runtime_dependency('slyphon-zookeeper_jar', '= 3.3.3')
data/spec/em_spec.rb ADDED
@@ -0,0 +1,138 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+ require 'zookeeper/em_client'
3
+
4
+ gem 'evented-spec', '~> 0.4.1'
5
+ require 'evented-spec'
6
+
7
+
8
+ describe 'ZookeeperEM' do
9
+ describe 'Client' do
10
+ include EventedSpec::SpecHelper
11
+ default_timeout 3.0
12
+
13
+ def setup_zk
14
+ @zk = ZookeeperEM::Client.new('localhost:2181')
15
+ em do
16
+ @zk.on_attached do
17
+ yield
18
+ end
19
+ end
20
+ end
21
+
22
+ def teardown_and_done
23
+ @zk.close { done }
24
+ end
25
+
26
+ describe 'selectable_io' do
27
+ it %[should return an IO object] do
28
+ setup_zk do
29
+ @zk.selectable_io.should be_instance_of(IO)
30
+ teardown_and_done
31
+ end
32
+ end
33
+
34
+ it %[should not be closed] do
35
+ setup_zk do
36
+ @zk.selectable_io.should_not be_closed
37
+ teardown_and_done
38
+ end
39
+ end
40
+
41
+ before do
42
+ @data_cb = ZookeeperCallbacks::DataCallback.new do
43
+ logger.debug { "cb called: #{@data_cb.inspect}" }
44
+ end
45
+ end
46
+
47
+ it %[should be read-ready if there's an event waiting] do
48
+ setup_zk do
49
+ @zk.get(:path => "/", :callback => @data_cb)
50
+
51
+ r, *_ = IO.select([@zk.selectable_io], [], [], 2)
52
+
53
+ r.should be_kind_of(Array)
54
+
55
+ teardown_and_done
56
+ end
57
+ end
58
+
59
+ it %[should not be read-ready if there's no event] do
60
+ pending "get this to work in jruby" if defined?(::JRUBY_VERSION)
61
+ # there's always an initial event after connect
62
+
63
+ # except in jruby
64
+ # if defined?(::JRUBY_VERSION)
65
+ # @zk.get(:path => '/', :callback => @data_cb)
66
+ # end
67
+
68
+ setup_zk do
69
+ events = 0
70
+
71
+ while true
72
+ r, *_ = IO.select([@zk.selectable_io], [], [], 0.2)
73
+
74
+ break unless r
75
+
76
+ h = @zk.get_next_event(false)
77
+ @zk.selectable_io.read(1)
78
+
79
+ events += 1
80
+
81
+ h.should be_kind_of(Hash)
82
+ end
83
+
84
+ events.should == 1
85
+
86
+ teardown_and_done
87
+ end
88
+ end
89
+ end
90
+
91
+ describe 'em_connection' do
92
+ before do
93
+ @zk = ZookeeperEM::Client.new('localhost:2181')
94
+ end
95
+
96
+ it %[should be nil before the reactor is started] do
97
+ @zk.em_connection.should be_nil
98
+
99
+ em do
100
+ teardown_and_done
101
+ end
102
+ end
103
+
104
+ it %[should fire off the on_attached callbacks once the reactor is managing us] do
105
+ @zk.on_attached do |*|
106
+ @zk.em_connection.should_not be_nil
107
+ @zk.em_connection.should be_instance_of(ZookeeperEM::ZKConnection)
108
+ teardown_and_done
109
+ end
110
+
111
+ em do
112
+ EM.reactor_running?.should be_true
113
+ end
114
+ end
115
+ end
116
+
117
+ describe 'callbacks' do
118
+ it %[should be called on the reactor thread] do
119
+ cb = lambda do |h|
120
+ EM.reactor_thread?.should be_true
121
+ logger.debug { "called back on the reactor thread? #{EM.reactor_thread?}" }
122
+ teardown_and_done
123
+ end
124
+
125
+ setup_zk do
126
+ @zk.on_attached do |*|
127
+ logger.debug { "on_attached called" }
128
+ rv = @zk.get(:path => '/', :callback => cb)
129
+ logger.debug { "rv from @zk.get: #{rv.inspect}" }
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ end
136
+ end
137
+
138
+