slyphon-zookeeper 0.1.7-java → 0.2.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -59,6 +59,53 @@ class ZookeeperBase
59
59
  end
60
60
  end
61
61
 
62
+ class QueueWithPipe
63
+ attr_writer :clear_reads_on_pop
64
+
65
+ def initialize
66
+ r, w = IO.pipe
67
+ @pipe = { :read => r, :write => w }
68
+ @queue = Queue.new
69
+
70
+ # with the EventMachine client, we want to let EM handle clearing the
71
+ # event pipe, so we set this to false
72
+ @clear_reads_on_pop = true
73
+ end
74
+
75
+ def push(obj)
76
+ rv = @queue.push(obj)
77
+ @pipe[:write].write('0')
78
+ logger.debug { "pushed #{obj.inspect} onto queue and wrote to pipe" }
79
+ rv
80
+ end
81
+
82
+ def pop(non_blocking=false)
83
+ rv = @queue.pop(non_blocking)
84
+
85
+ # if non_blocking is true and an exception is raised, this won't get called
86
+ @pipe[:read].read(1) if clear_reads_on_pop?
87
+
88
+ rv
89
+ end
90
+
91
+ def close
92
+ @pipe.values.each { |io| io.close unless io.closed? }
93
+ end
94
+
95
+ def selectable_io
96
+ @pipe[:read]
97
+ end
98
+
99
+ private
100
+ def clear_reads_on_pop?
101
+ @clear_reads_on_pop
102
+ end
103
+
104
+ def logger
105
+ Zookeeper.logger
106
+ end
107
+ end
108
+
62
109
  # used for internal dispatching
63
110
  module JavaCB #:nodoc:
64
111
  class Callback
@@ -78,13 +125,24 @@ class ZookeeperBase
78
125
  include JZK::AsyncCallback::DataCallback
79
126
 
80
127
  def processResult(rc, path, queue, data, stat)
81
- queue.push({
128
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, path: #{path}, queue: #{queue.inspect}, data: #{data.inspect}, stat: #{stat.inspect}" }
129
+
130
+ hash = {
82
131
  :rc => rc,
83
132
  :req_id => req_id,
84
133
  :path => path,
85
- :data => String.from_java_bytes(data),
86
- :stat => stat.to_hash,
87
- })
134
+ :data => (data && String.from_java_bytes(data)),
135
+ :stat => (stat && stat.to_hash),
136
+ }
137
+
138
+ # if rc == Zookeeper::ZOK
139
+ # hash.merge!({
140
+ # :data => String.from_java_bytes(data),
141
+ # :stat => stat.to_hash,
142
+ # })
143
+ # end
144
+
145
+ queue.push(hash)
88
146
  end
89
147
  end
90
148
 
@@ -92,6 +150,7 @@ class ZookeeperBase
92
150
  include JZK::AsyncCallback::StringCallback
93
151
 
94
152
  def processResult(rc, path, queue, str)
153
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, path: #{path}, queue: #{queue.inspect}, str: #{str.inspect}" }
95
154
  queue.push(:rc => rc, :req_id => req_id, :path => path, :string => str)
96
155
  end
97
156
  end
@@ -100,7 +159,7 @@ class ZookeeperBase
100
159
  include JZK::AsyncCallback::StatCallback
101
160
 
102
161
  def processResult(rc, path, queue, stat)
103
- logger.debug { "StatCallback#processResult rc: #{rc.inspect}, path: #{path.inspect}, queue: #{queue.inspect}, stat: #{stat.inspect}" }
162
+ logger.debug { "#{self.class.name}#processResult rc: #{rc.inspect}, req_id: #{req_id}, path: #{path.inspect}, queue: #{queue.inspect}, stat: #{stat.inspect}" }
104
163
  queue.push(:rc => rc, :req_id => req_id, :stat => (stat and stat.to_hash), :path => path)
105
164
  end
106
165
  end
@@ -109,7 +168,16 @@ class ZookeeperBase
109
168
  include JZK::AsyncCallback::Children2Callback
110
169
 
111
170
  def processResult(rc, path, queue, children, stat)
112
- queue.push(:rc => rc, :req_id => req_id, :path => path, :strings => children.to_a, :stat => stat.to_hash)
171
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, path: #{path}, queue: #{queue.inspect}, children: #{children.inspect}, stat: #{stat.inspect}" }
172
+ hash = {
173
+ :rc => rc,
174
+ :req_id => req_id,
175
+ :path => path,
176
+ :strings => (children && children.to_a),
177
+ :stat => (stat and stat.to_hash),
178
+ }
179
+
180
+ queue.push(hash)
113
181
  end
114
182
  end
115
183
 
@@ -117,9 +185,9 @@ class ZookeeperBase
117
185
  include JZK::AsyncCallback::ACLCallback
118
186
 
119
187
  def processResult(rc, path, queue, acl, stat)
120
- logger.debug { "ACLCallback#processResult #{rc.inspect} #{path.inspect} #{queue.inspect} #{acl.inspect} #{stat.inspect}" }
188
+ logger.debug { "ACLCallback#processResult rc: #{rc.inspect}, req_id: #{req_id}, path: #{path.inspect}, queue: #{queue.inspect}, acl: #{acl.inspect}, stat: #{stat.inspect}" }
121
189
  a = Array(acl).map { |a| a.to_hash }
122
- queue.push(:rc => rc, :req_id => req_id, :path => path, :acl => a, :stat => stat.to_hash)
190
+ queue.push(:rc => rc, :req_id => req_id, :path => path, :acl => a, :stat => (stat && stat.to_hash))
123
191
  end
124
192
  end
125
193
 
@@ -127,6 +195,7 @@ class ZookeeperBase
127
195
  include JZK::AsyncCallback::VoidCallback
128
196
 
129
197
  def processResult(rc, path, queue)
198
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, queue: #{queue.inspect}" }
130
199
  queue.push(:rc => rc, :req_id => req_id, :path => path)
131
200
  end
132
201
  end
@@ -140,7 +209,7 @@ class ZookeeperBase
140
209
  end
141
210
 
142
211
  def process(event)
143
- logger.debug { "WatcherCallback got event: #{event.to_hash}" }
212
+ logger.debug { "WatcherCallback got event: #{event.to_hash.inspect}" }
144
213
  hash = event.to_hash.merge(:req_id => req_id)
145
214
  @event_queue.push(hash)
146
215
  end
@@ -156,26 +225,32 @@ class ZookeeperBase
156
225
  set_default_global_watcher(&watcher)
157
226
  end
158
227
 
159
- @jzk = JZK::ZooKeeper.new(@host, DEFAULT_SESSION_TIMEOUT, JavaCB::WatcherCallback.new(@event_queue))
228
+ @start_stop_mutex.synchronize do
229
+ @jzk = JZK::ZooKeeper.new(@host, DEFAULT_SESSION_TIMEOUT, JavaCB::WatcherCallback.new(@event_queue))
160
230
 
161
- if timeout > 0
162
- time_to_stop = Time.now + timeout
163
- until connected?
164
- break if Time.now > time_to_stop
165
- sleep 0.1
231
+ if timeout > 0
232
+ time_to_stop = Time.now + timeout
233
+ until connected?
234
+ break if Time.now > time_to_stop
235
+ sleep 0.1
236
+ end
166
237
  end
167
238
  end
168
239
 
169
240
  state
170
241
  end
171
242
 
172
- def initialize(host, timeout=10, watcher=nil)
243
+ def initialize(host, timeout=10, watcher=nil, options={})
173
244
  @host = host
174
- @event_queue = Queue.new
245
+ @event_queue = QueueWithPipe.new
175
246
  @current_req_id = 0
176
247
  @req_mutex = Monitor.new
177
248
  @watcher_reqs = {}
178
249
  @completion_reqs = {}
250
+ @_running = nil
251
+ @_closed = false
252
+ @options = {}
253
+ @start_stop_mutex = Mutex.new
179
254
 
180
255
  watcher ||= get_default_global_watcher
181
256
 
@@ -184,6 +259,7 @@ class ZookeeperBase
184
259
 
185
260
  reopen(timeout, watcher)
186
261
  return nil unless connected?
262
+ @_running = true
187
263
  setup_dispatch_thread!
188
264
  end
189
265
 
@@ -203,6 +279,22 @@ class ZookeeperBase
203
279
  state == JZK::ZooKeeper::States::ASSOCIATING
204
280
  end
205
281
 
282
+ def running?
283
+ @_running
284
+ end
285
+
286
+ def closed?
287
+ @_closed
288
+ end
289
+
290
+ def self.set_debug_level(*a)
291
+ # IGNORED IN JRUBY
292
+ end
293
+
294
+ def set_debug_level(*a)
295
+ # IGNORED IN JRUBY
296
+ end
297
+
206
298
  def get(req_id, path, callback, watcher)
207
299
  handle_keeper_exception do
208
300
  watch_cb = watcher ? create_watcher(req_id, path) : false
@@ -332,20 +424,36 @@ class ZookeeperBase
332
424
 
333
425
  class DispatchShutdownException < StandardError; end
334
426
 
427
+ def wake_event_loop!
428
+ @event_queue.push(KILL_TOKEN) # ignored by dispatch_next_callback
429
+ end
430
+
335
431
  def close
336
432
  @req_mutex.synchronize do
337
- if @jzk
338
- @jzk.close
339
- wait_until { !connected? }
340
- end
433
+ @_running = false if @_running
434
+ end
435
+
436
+ # XXX: why is wake_event_loop! here?
437
+ if @dispatcher
438
+ wake_event_loop!
439
+ @dispatcher.join
440
+ end
341
441
 
342
- if @dispatcher
343
- @dispatcher[:running] = false
344
- @event_queue.push(KILL_TOKEN) # ignored by dispatch_next_callback
442
+ unless @_closed
443
+ @start_stop_mutex.synchronize do
444
+ @_closed = true
445
+ close_handle
345
446
  end
447
+
448
+ @event_queue.close
346
449
  end
450
+ end
347
451
 
348
- @dispatcher.join
452
+ def close_handle
453
+ if @jzk
454
+ @jzk.close
455
+ wait_until { !connected? }
456
+ end
349
457
  end
350
458
 
351
459
  # set the watcher object/proc that will receive all global events (such as session/state events)
@@ -360,6 +468,23 @@ class ZookeeperBase
360
468
  end
361
469
  end
362
470
 
471
+ # by accessing this selectable_io you indicate that you intend to clear it
472
+ # when you have delivered an event by reading one byte per event.
473
+ #
474
+ def selectable_io
475
+ @event_queue.clear_reads_on_pop = false
476
+ @event_queue.selectable_io
477
+ end
478
+
479
+ def get_next_event(blocking=true)
480
+ @event_queue.pop(!blocking).tap do |event|
481
+ logger.debug { "get_next_event delivering event: #{event.inspect}" }
482
+ raise DispatchShutdownException if event == KILL_TOKEN
483
+ end
484
+ rescue ThreadError
485
+ nil
486
+ end
487
+
363
488
  protected
364
489
  def handle_keeper_exception
365
490
  yield
@@ -376,28 +501,23 @@ class ZookeeperBase
376
501
  end
377
502
 
378
503
  def create_watcher(req_id, path)
504
+ logger.debug { "creating watcher for req_id: #{req_id} path: #{path}" }
379
505
  lambda do |event|
506
+ logger.debug { "watcher for req_id #{req_id}, path: #{path} called back" }
380
507
  h = { :req_id => req_id, :type => event.type.int_value, :state => event.state.int_value, :path => path }
381
508
  @event_queue.push(h)
382
509
  end
383
510
  end
384
511
 
385
- def get_next_event
386
- @event_queue.pop.tap do |event|
387
- raise DispatchShutdownException if event == KILL_TOKEN
388
- end
389
- end
390
-
391
512
  # method to wait until block passed returns true or timeout (default is 10 seconds) is reached
392
513
  def wait_until(timeout=10, &block)
393
514
  time_to_stop = Time.now + timeout
394
515
  until yield do
395
516
  break if Time.now > time_to_stop
396
- sleep 0.3
517
+ sleep 0.1
397
518
  end
398
519
  end
399
520
 
400
- protected
401
521
  # TODO: Make all global puts configurable
402
522
  def get_default_global_watcher
403
523
  Proc.new { |args|
@@ -406,13 +526,10 @@ class ZookeeperBase
406
526
  }
407
527
  end
408
528
 
409
- private
410
529
  def setup_dispatch_thread!
411
530
  logger.debug { "starting dispatch thread" }
412
531
  @dispatcher = Thread.new do
413
- Thread.current[:running] = true
414
-
415
- while Thread.current[:running]
532
+ while running?
416
533
  begin
417
534
  dispatch_next_callback
418
535
  rescue DispatchShutdownException
@@ -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
+
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
 
@@ -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')