slyphon-zookeeper 0.1.7-java → 0.2.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.
@@ -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')