slyphon-zookeeper 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+