slyphon-zookeeper 0.3.0-java → 0.8.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -59,53 +59,6 @@ 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
-
109
62
  # used for internal dispatching
110
63
  module JavaCB #:nodoc:
111
64
  class Callback
@@ -216,43 +169,48 @@ class ZookeeperBase
216
169
  end
217
170
  end
218
171
 
172
+ attr_reader :event_queue
173
+
219
174
  def reopen(timeout=10, watcher=nil)
220
- watcher ||= @default_watcher
175
+ # watcher ||= @default_watcher
221
176
 
222
- @req_mutex.synchronize do
177
+ @mutex.synchronize do
223
178
  # flushes all outstanding watcher reqs.
224
- @watcher_req = {}
225
- set_default_global_watcher(&watcher)
179
+ @watcher_reqs.clear
180
+ set_default_global_watcher
181
+
182
+ replace_jzk!
183
+ wait_until_connected
226
184
  end
227
185
 
228
- @start_stop_mutex.synchronize do
229
- @jzk = JZK::ZooKeeper.new(@host, DEFAULT_SESSION_TIMEOUT, JavaCB::WatcherCallback.new(@event_queue))
186
+ state
187
+ end
188
+
189
+ def wait_until_connected(timeout=10)
190
+ time_to_stop = timeout ? (Time.now + timeout) : nil
230
191
 
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
237
- end
192
+ until connected? or (time_to_stop and Time.now > time_to_stop)
193
+ Thread.pass
238
194
  end
239
195
 
240
- state
196
+ connected?
241
197
  end
242
198
 
243
199
  def initialize(host, timeout=10, watcher=nil, options={})
244
200
  @host = host
245
201
  @event_queue = QueueWithPipe.new
246
202
  @current_req_id = 0
247
- @req_mutex = Monitor.new
203
+
204
+ @mutex = Monitor.new
205
+ @dispatch_shutdown_cond = @mutex.new_cond
206
+
248
207
  @watcher_reqs = {}
249
208
  @completion_reqs = {}
250
209
  @_running = nil
251
210
  @_closed = false
252
211
  @options = {}
253
- @start_stop_mutex = Mutex.new
254
212
 
255
- watcher ||= get_default_global_watcher
213
+ @default_watcher = (watcher || get_default_global_watcher)
256
214
 
257
215
  # allows connected-state handlers to be registered before
258
216
  yield self if block_given?
@@ -263,8 +221,19 @@ class ZookeeperBase
263
221
  setup_dispatch_thread!
264
222
  end
265
223
 
224
+ def close
225
+ @mutex.synchronize do
226
+ return if @_closed
227
+ @_closed = true # these are probably unnecessary
228
+ @_running = false
229
+
230
+ stop_dispatch_thread!
231
+ @jzk.close if @jzk
232
+ end
233
+ end
234
+
266
235
  def state
267
- @jzk.state
236
+ @mutex.synchronize { @jzk.state }
268
237
  end
269
238
 
270
239
  def connected?
@@ -300,11 +269,11 @@ class ZookeeperBase
300
269
  watch_cb = watcher ? create_watcher(req_id, path) : false
301
270
 
302
271
  if callback
303
- @jzk.getData(path, watch_cb, JavaCB::DataCallback.new(req_id), @event_queue)
272
+ jzk.getData(path, watch_cb, JavaCB::DataCallback.new(req_id), event_queue)
304
273
  [Code::Ok, nil, nil] # the 'nil, nil' isn't strictly necessary here
305
274
  else # sync
306
275
  stat = JZKD::Stat.new
307
- data = String.from_java_bytes(@jzk.getData(path, watch_cb, stat))
276
+ data = String.from_java_bytes(jzk.getData(path, watch_cb, stat))
308
277
 
309
278
  [Code::Ok, data, stat.to_hash]
310
279
  end
@@ -316,10 +285,10 @@ class ZookeeperBase
316
285
  version ||= ANY_VERSION
317
286
 
318
287
  if callback
319
- @jzk.setData(path, data.to_java_bytes, version, JavaCB::StatCallback.new(req_id), @event_queue)
288
+ jzk.setData(path, data.to_java_bytes, version, JavaCB::StatCallback.new(req_id), event_queue)
320
289
  [Code::Ok, nil]
321
290
  else
322
- stat = @jzk.setData(path, data.to_java_bytes, version).to_hash
291
+ stat = jzk.setData(path, data.to_java_bytes, version).to_hash
323
292
  [Code::Ok, stat]
324
293
  end
325
294
  end
@@ -330,11 +299,11 @@ class ZookeeperBase
330
299
  watch_cb = watcher ? create_watcher(req_id, path) : false
331
300
 
332
301
  if callback
333
- @jzk.getChildren(path, watch_cb, JavaCB::Children2Callback.new(req_id), @event_queue)
302
+ jzk.getChildren(path, watch_cb, JavaCB::Children2Callback.new(req_id), event_queue)
334
303
  [Code::Ok, nil, nil]
335
304
  else
336
305
  stat = JZKD::Stat.new
337
- children = @jzk.getChildren(path, watch_cb, stat)
306
+ children = jzk.getChildren(path, watch_cb, stat)
338
307
  [Code::Ok, children.to_a, stat.to_hash]
339
308
  end
340
309
  end
@@ -348,21 +317,28 @@ class ZookeeperBase
348
317
  data ||= ''
349
318
 
350
319
  if callback
351
- @jzk.create(path, data.to_java_bytes, acl, mode, JavaCB::StringCallback.new(req_id), @event_queue)
320
+ jzk.create(path, data.to_java_bytes, acl, mode, JavaCB::StringCallback.new(req_id), event_queue)
352
321
  [Code::Ok, nil]
353
322
  else
354
- new_path = @jzk.create(path, data.to_java_bytes, acl, mode)
323
+ new_path = jzk.create(path, data.to_java_bytes, acl, mode)
355
324
  [Code::Ok, new_path]
356
325
  end
357
326
  end
358
327
  end
359
328
 
329
+ def sync(req_id, path)
330
+ handle_keeper_exception do
331
+ jzk.sync(path, JavaCB::VoidCallback.new(req_id), event_queue)
332
+ Code::Ok
333
+ end
334
+ end
335
+
360
336
  def delete(req_id, path, version, callback)
361
337
  handle_keeper_exception do
362
338
  if callback
363
- @jzk.delete(path, version, JavaCB::VoidCallback.new(req_id), @event_queue)
339
+ jzk.delete(path, version, JavaCB::VoidCallback.new(req_id), event_queue)
364
340
  else
365
- @jzk.delete(path, version)
341
+ jzk.delete(path, version)
366
342
  end
367
343
 
368
344
  Code::Ok
@@ -376,9 +352,9 @@ class ZookeeperBase
376
352
  logger.debug { "set_acl: converted #{acl.inspect}" }
377
353
 
378
354
  if callback
379
- @jzk.setACL(path, acl, version, JavaCB::ACLCallback.new(req_id), @event_queue)
355
+ jzk.setACL(path, acl, version, JavaCB::ACLCallback.new(req_id), event_queue)
380
356
  else
381
- @jzk.setACL(path, acl, version)
357
+ jzk.setACL(path, acl, version)
382
358
  end
383
359
 
384
360
  Code::Ok
@@ -390,10 +366,10 @@ class ZookeeperBase
390
366
  watch_cb = watcher ? create_watcher(req_id, path) : false
391
367
 
392
368
  if callback
393
- @jzk.exists(path, watch_cb, JavaCB::StatCallback.new(req_id), @event_queue)
369
+ jzk.exists(path, watch_cb, JavaCB::StatCallback.new(req_id), event_queue)
394
370
  [Code::Ok, nil, nil]
395
371
  else
396
- stat = @jzk.exists(path, watch_cb)
372
+ stat = jzk.exists(path, watch_cb)
397
373
  [Code::Ok, (stat and stat.to_hash)]
398
374
  end
399
375
  end
@@ -405,10 +381,10 @@ class ZookeeperBase
405
381
 
406
382
  if callback
407
383
  logger.debug { "calling getACL, path: #{path.inspect}, stat: #{stat.inspect}" }
408
- @jzk.getACL(path, stat, JavaCB::ACLCallback.new(req_id), @event_queue)
384
+ jzk.getACL(path, stat, JavaCB::ACLCallback.new(req_id), event_queue)
409
385
  [Code::Ok, nil, nil]
410
386
  else
411
- acls = @jzk.getACL(path, stat).map { |a| a.to_hash }
387
+ acls = jzk.getACL(path, stat).map { |a| a.to_hash }
412
388
 
413
389
  [Code::Ok, Array(acls).map{|m| m.to_hash}, stat.to_hash]
414
390
  end
@@ -420,80 +396,30 @@ class ZookeeperBase
420
396
  raise ZookeeperException::NotConnected unless connected?
421
397
  end
422
398
 
423
- KILL_TOKEN = :__kill_token__
424
-
425
- class DispatchShutdownException < StandardError; end
426
-
427
- def wake_event_loop!
428
- @event_queue.push(KILL_TOKEN) # ignored by dispatch_next_callback
429
- end
430
-
431
- def close
432
- @req_mutex.synchronize do
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
441
-
442
- unless @_closed
443
- @start_stop_mutex.synchronize do
444
- @_closed = true
445
- close_handle
446
- end
447
-
448
- @event_queue.close
449
- end
450
- end
451
-
452
- def close_handle
453
- if @jzk
454
- @jzk.close
455
- wait_until { !connected? }
456
- end
457
- end
458
-
459
399
  # set the watcher object/proc that will receive all global events (such as session/state events)
460
400
  #---
461
401
  # XXX: this code needs to be duplicated from ext/zookeeper_base.rb because
462
402
  # it's called from the initializer, and because of the C impl. we can't have
463
403
  # the two decend from a common base, and a module wouldn't work
464
- def set_default_global_watcher(&block)
465
- @req_mutex.synchronize do
466
- @default_watcher = block
404
+ def set_default_global_watcher
405
+ @mutex.synchronize do
467
406
  @watcher_reqs[ZKRB_GLOBAL_CB_REQ] = { :watcher => @default_watcher, :watcher_context => nil }
468
407
  end
469
408
  end
470
409
 
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
410
  def session_id
480
- @jzk.session_id
411
+ jzk.session_id
481
412
  end
482
413
 
483
414
  def session_passwd
484
- @jzk.session_passwd.to_s
415
+ jzk.session_passwd.to_s
485
416
  end
486
417
 
487
- def get_next_event(blocking=true)
488
- @event_queue.pop(!blocking).tap do |event|
489
- logger.debug { "get_next_event delivering event: #{event.inspect}" }
490
- raise DispatchShutdownException if event == KILL_TOKEN
491
- end
492
- rescue ThreadError
493
- nil
494
- end
495
-
496
418
  protected
419
+ def jzk
420
+ @mutex.synchronize { @jzk }
421
+ end
422
+
497
423
  def handle_keeper_exception
498
424
  yield
499
425
  rescue JZK::KeeperException => e
@@ -513,7 +439,7 @@ class ZookeeperBase
513
439
  lambda do |event|
514
440
  logger.debug { "watcher for req_id #{req_id}, path: #{path} called back" }
515
441
  h = { :req_id => req_id, :type => event.type.int_value, :state => event.state.int_value, :path => path }
516
- @event_queue.push(h)
442
+ event_queue.push(h)
517
443
  end
518
444
  end
519
445
 
@@ -534,20 +460,12 @@ class ZookeeperBase
534
460
  }
535
461
  end
536
462
 
537
- def setup_dispatch_thread!
538
- logger.debug { "starting dispatch thread" }
539
- @dispatcher = Thread.new do
540
- while running?
541
- begin
542
- dispatch_next_callback
543
- rescue DispatchShutdownException
544
- logger.info { "dispatch thread exiting, got shutdown exception" }
545
- break
546
- rescue Exception => e
547
- $stderr.puts ["#{e.class}: #{e.message}", e.backtrace.map { |n| "\t#{n}" }.join("\n")].join("\n")
548
- end
549
- end
550
- end
463
+ private
464
+ def replace_jzk!
465
+ orig_jzk = @jzk
466
+ @jzk = JZK::ZooKeeper.new(@host, DEFAULT_SESSION_TIMEOUT, JavaCB::WatcherCallback.new(event_queue))
467
+ ensure
468
+ orig_jzk.close if orig_jzk
551
469
  end
552
470
  end
553
471
 
@@ -0,0 +1,78 @@
1
+ module ZookeeperCommon
2
+ # Ceci n'est pas une pipe
3
+ class QueueWithPipe
4
+ extend Forwardable
5
+
6
+ def_delegators :@queue, :clear
7
+
8
+ # raised when close has been called, and pop() is performed
9
+ #
10
+ class ShutdownException < StandardError; end
11
+
12
+ # @private
13
+ KILL_TOKEN = Object.new unless defined?(KILL_TOKEN)
14
+
15
+ def initialize
16
+ # r, w = IO.pipe
17
+ # @pipe = { :read => r, :write => w }
18
+ @queue = Queue.new
19
+
20
+ # with the EventMachine client, we want to let EM handle clearing the
21
+ # event pipe, so we set this to false
22
+ # @clear_reads_on_pop = true
23
+
24
+ @mutex = Mutex.new
25
+ @closed = false
26
+ @graceful = false
27
+ end
28
+
29
+ def push(obj)
30
+ logger.debug { "#{self.class}##{__method__} obj: #{obj.inspect}, kill_token? #{obj == KILL_TOKEN}" }
31
+ @queue.push(obj)
32
+ end
33
+
34
+ def pop(non_blocking=false)
35
+ raise ShutdownException if closed? # this may get us in trouble
36
+
37
+ rv = @queue.pop(non_blocking)
38
+
39
+ if rv == KILL_TOKEN
40
+ close
41
+ raise ShutdownException
42
+ end
43
+
44
+ rv
45
+ end
46
+
47
+ # close the queue and causes ShutdownException to be raised on waiting threads
48
+ def graceful_close!
49
+ @mutex.synchronize do
50
+ return if @graceful or @closed
51
+ logger.debug { "#{self.class}##{__method__} gracefully closing" }
52
+ @graceful = true
53
+ push(KILL_TOKEN)
54
+ end
55
+ nil
56
+ end
57
+
58
+ def close
59
+ @mutex.synchronize do
60
+ return if @closed
61
+ @closed = true
62
+ end
63
+ end
64
+
65
+ def closed?
66
+ @mutex.synchronize { !!@closed }
67
+ end
68
+
69
+ private
70
+ def clear_reads_on_pop?
71
+ @clear_reads_on_pop
72
+ end
73
+
74
+ def logger
75
+ Zookeeper.logger
76
+ end
77
+ end
78
+ end
@@ -4,15 +4,22 @@ module ZookeeperCommon
4
4
  # sigh, i guess define this here?
5
5
  ZKRB_GLOBAL_CB_REQ = -1
6
6
 
7
- def get_next_event(blocking=true)
8
- return nil if closed? # protect against this happening in a callback after close
9
- super(blocking)
7
+ def event_dispatch_thread?
8
+ @dispatcher && (@dispatcher == Thread.current)
10
9
  end
11
10
 
12
11
  protected
12
+ def get_next_event(blocking=true)
13
+ @event_queue.pop(!blocking).tap do |event|
14
+ logger.debug { "#{self.class}##{__method__} delivering event #{event.inspect}" }
15
+ end
16
+ rescue ThreadError
17
+ nil
18
+ end
19
+
13
20
  def setup_call(meth_name, opts)
14
21
  req_id = nil
15
- @req_mutex.synchronize {
22
+ @mutex.synchronize {
16
23
  req_id = @current_req_id
17
24
  @current_req_id += 1
18
25
  setup_completion(req_id, meth_name, opts) if opts[:callback]
@@ -38,20 +45,65 @@ protected
38
45
  end
39
46
 
40
47
  def get_watcher(req_id)
41
- @req_mutex.synchronize {
48
+ @mutex.synchronize {
42
49
  (req_id == ZKRB_GLOBAL_CB_REQ) ? @watcher_reqs[req_id] : @watcher_reqs.delete(req_id)
43
50
  }
44
51
  end
45
52
 
46
53
  def get_completion(req_id)
47
- @req_mutex.synchronize { @completion_reqs.delete(req_id) }
54
+ @mutex.synchronize { @completion_reqs.delete(req_id) }
48
55
  end
49
56
 
50
- def dispatch_next_callback(blocking=true)
51
- hash = get_next_event(blocking)
52
- # Zookeeper.logger.debug { "get_next_event returned: #{hash.inspect}" }
57
+ def setup_dispatch_thread!
58
+ logger.debug { "starting dispatch thread" }
59
+ @dispatcher ||= Thread.new do
60
+ while true
61
+ begin
62
+ dispatch_next_callback(get_next_event(true))
63
+ rescue QueueWithPipe::ShutdownException
64
+ logger.info { "dispatch thread exiting, got shutdown exception" }
65
+ break
66
+ rescue Exception => e
67
+ $stderr.puts ["#{e.class}: #{e.message}", e.backtrace.map { |n| "\t#{n}" }.join("\n")].join("\n")
68
+ end
69
+ end
70
+ signal_dispatch_thread_exit!
71
+ end
72
+ end
73
+
74
+ # this method is part of the reopen/close code, and is responsible for
75
+ # shutting down the dispatch thread.
76
+ #
77
+ # @dispatcher will be nil when this method exits
78
+ #
79
+ def stop_dispatch_thread!
80
+ logger.debug { "#{self.class}##{__method__}" }
81
+
82
+ if @dispatcher
83
+ @mutex.synchronize do
84
+ event_queue.graceful_close!
85
+
86
+ # we now release the mutex so that dispatch_next_callback can grab it
87
+ # to do what it needs to do while delivering events
88
+ @dispatch_shutdown_cond.wait
53
89
 
90
+ @dispatcher.join
91
+ @dispatcher = nil
92
+ end
93
+ end
94
+ end
95
+
96
+ def signal_dispatch_thread_exit!
97
+ @mutex.synchronize do
98
+ logger.debug { "dispatch thread exiting!" }
99
+ @dispatch_shutdown_cond.broadcast
100
+ end
101
+ end
102
+
103
+ def dispatch_next_callback(hash)
54
104
  return nil unless hash
105
+
106
+ Zookeeper.logger.debug { "get_next_event returned: #{prettify_event(hash).inspect}" }
55
107
 
56
108
  is_completion = hash.has_key?(:rc)
57
109
 
@@ -96,4 +148,16 @@ protected
96
148
  "Required arguments are: #{required.inspect}, but only the arguments #{args.keys.inspect} were supplied."
97
149
  end
98
150
  end
151
+
152
+ private
153
+ def prettify_event(hash)
154
+ hash.dup.tap do |h|
155
+ # pretty up the event display
156
+ h[:type] = ZookeeperConstants::EVENT_TYPE_NAMES.fetch(h[:type]) if h[:type]
157
+ h[:state] = ZookeeperConstants::STATE_NAMES.fetch(h[:state]) if h[:state]
158
+ h[:req_id] = :global_session if h[:req_id] == -1
159
+ end
160
+ end
99
161
  end
162
+
163
+ require 'zookeeper/common/queue_with_pipe'
@@ -18,6 +18,34 @@ module ZookeeperConstants
18
18
  ZOO_CHILD_EVENT = 4
19
19
  ZOO_SESSION_EVENT = -1
20
20
  ZOO_NOTWATCHING_EVENT = -2
21
+
22
+ # only used by the C extension
23
+ ZOO_LOG_LEVEL_ERROR = 1
24
+ ZOO_LOG_LEVEL_WARN = 2
25
+ ZOO_LOG_LEVEL_INFO = 3
26
+ ZOO_LOG_LEVEL_DEBUG = 4
27
+
28
+ # used to find the name for a numeric event
29
+ # @private
30
+ EVENT_TYPE_NAMES = {
31
+ 1 => :created,
32
+ 2 => :deleted,
33
+ 3 => :changed,
34
+ 4 => :child,
35
+ -1 => :session,
36
+ -2 => :notwatching,
37
+ }
38
+
39
+ # used to pretty print the state name
40
+ # @private
41
+ STATE_NAMES = {
42
+ -112 => :expired_session,
43
+ -113 => :auth_failed,
44
+ 0 => :closed,
45
+ 1 => :connecting,
46
+ 2 => :associating,
47
+ 3 => :connected,
48
+ }
21
49
 
22
50
  def print_events
23
51
  puts "ZK events:"