zookeeper 0.4.4 → 0.9.3

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.
Files changed (48) hide show
  1. data/.gitignore +10 -0
  2. data/CHANGELOG +95 -0
  3. data/Gemfile +17 -0
  4. data/Manifest +8 -2
  5. data/README.markdown +59 -0
  6. data/Rakefile +137 -7
  7. data/ext/.gitignore +6 -0
  8. data/ext/Rakefile +51 -0
  9. data/ext/c_zookeeper.rb +212 -0
  10. data/ext/dbg.h +53 -0
  11. data/ext/depend +5 -0
  12. data/ext/extconf.rb +44 -15
  13. data/ext/generate_gvl_code.rb +316 -0
  14. data/ext/zkc-3.3.5.tar.gz +0 -0
  15. data/ext/zkrb_wrapper.c +731 -0
  16. data/ext/zkrb_wrapper.h +330 -0
  17. data/ext/zkrb_wrapper_compat.c +15 -0
  18. data/ext/zkrb_wrapper_compat.h +11 -0
  19. data/ext/zookeeper_base.rb +211 -0
  20. data/ext/zookeeper_c.c +268 -97
  21. data/ext/zookeeper_lib.c +157 -92
  22. data/ext/zookeeper_lib.h +12 -6
  23. data/java/zookeeper_base.rb +477 -0
  24. data/lib/zookeeper/acls.rb +10 -1
  25. data/lib/zookeeper/callbacks.rb +5 -3
  26. data/lib/zookeeper/common/queue_with_pipe.rb +78 -0
  27. data/lib/zookeeper/common.rb +174 -0
  28. data/lib/zookeeper/constants.rb +31 -28
  29. data/lib/zookeeper/em_client.rb +55 -0
  30. data/lib/zookeeper/exceptions.rb +10 -2
  31. data/lib/zookeeper/stat.rb +11 -2
  32. data/lib/zookeeper/version.rb +6 -0
  33. data/lib/zookeeper.rb +155 -122
  34. data/notes.txt +14 -0
  35. data/spec/c_zookeeper_spec.rb +50 -0
  36. data/spec/chrooted_connection_spec.rb +81 -0
  37. data/spec/default_watcher_spec.rb +41 -0
  38. data/spec/em_spec.rb +51 -0
  39. data/spec/log4j.properties +17 -0
  40. data/spec/shared/all_success_return_values.rb +10 -0
  41. data/spec/shared/connection_examples.rb +1018 -0
  42. data/spec/spec_helper.rb +119 -0
  43. data/spec/support/progress_formatter.rb +15 -0
  44. data/spec/zookeeper_spec.rb +24 -0
  45. data/zookeeper.gemspec +37 -25
  46. metadata +78 -34
  47. data/README +0 -42
  48. data/ext/zkc-3.3.2.tar.gz +0 -0
@@ -0,0 +1,477 @@
1
+ require 'java'
2
+ require 'thread'
3
+ require 'rubygems'
4
+
5
+ gem 'slyphon-log4j', '= 1.2.15'
6
+ gem 'slyphon-zookeeper_jar', '= 3.3.5'
7
+
8
+ require 'log4j'
9
+ require 'zookeeper_jar'
10
+
11
+ # The low-level wrapper-specific methods for the Java lib,
12
+ # subclassed by the top-level Zookeeper class
13
+ class ZookeeperBase
14
+ include Java
15
+ include ZookeeperCommon
16
+ include ZookeeperConstants
17
+ include ZookeeperCallbacks
18
+ include ZookeeperExceptions
19
+ include ZookeeperACLs
20
+ include ZookeeperStat
21
+
22
+ JZK = org.apache.zookeeper
23
+ JZKD = org.apache.zookeeper.data
24
+ Code = JZK::KeeperException::Code
25
+
26
+ ANY_VERSION = -1
27
+ DEFAULT_SESSION_TIMEOUT = 10_000
28
+
29
+ ZKRB_GLOBAL_CB_REQ = -1 unless defined?(ZKRB_GLOBAL_CB_REQ)
30
+
31
+ JZKD::Stat.class_eval do
32
+ MEMBERS = [:version, :czxid, :mzxid, :ctime, :mtime, :cversion, :aversion, :ephemeralOwner, :dataLength, :numChildren, :pzxid]
33
+ def to_hash
34
+ MEMBERS.inject({}) { |h,k| h[k] = __send__(k); h }
35
+ end
36
+ end
37
+
38
+ JZKD::Id.class_eval do
39
+ def to_hash
40
+ { :scheme => getScheme, :id => getId }
41
+ end
42
+ end
43
+
44
+ JZKD::ACL.class_eval do
45
+ def self.from_ruby_acl(acl)
46
+ raise TypeError, "acl must be a ZookeeperACLs::ACL not #{acl.inspect}" unless acl.kind_of?(ZookeeperACLs::ACL)
47
+ id = org.apache.zookeeper.data.Id.new(acl.id.scheme.to_s, acl.id.id.to_s)
48
+ new(acl.perms.to_i, id)
49
+ end
50
+
51
+ def to_hash
52
+ { :perms => getPerms, :id => getId.to_hash }
53
+ end
54
+ end
55
+
56
+ JZK::WatchedEvent.class_eval do
57
+ def to_hash
58
+ { :type => getType.getIntValue, :state => getState.getIntValue, :path => getPath }
59
+ end
60
+ end
61
+
62
+ # used for internal dispatching
63
+ module JavaCB #:nodoc:
64
+ class Callback
65
+ attr_reader :req_id
66
+
67
+ def initialize(req_id)
68
+ @req_id = req_id
69
+ end
70
+
71
+ protected
72
+ def logger
73
+ Zookeeper.logger
74
+ end
75
+ end
76
+
77
+ class DataCallback < Callback
78
+ include JZK::AsyncCallback::DataCallback
79
+
80
+ def processResult(rc, path, queue, data, stat)
81
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, path: #{path}, queue: #{queue.inspect}, data: #{data.inspect}, stat: #{stat.inspect}" }
82
+
83
+ hash = {
84
+ :rc => rc,
85
+ :req_id => req_id,
86
+ :path => path,
87
+ :data => (data && String.from_java_bytes(data)),
88
+ :stat => (stat && stat.to_hash),
89
+ }
90
+
91
+ # if rc == Zookeeper::ZOK
92
+ # hash.merge!({
93
+ # :data => String.from_java_bytes(data),
94
+ # :stat => stat.to_hash,
95
+ # })
96
+ # end
97
+
98
+ queue.push(hash)
99
+ end
100
+ end
101
+
102
+ class StringCallback < Callback
103
+ include JZK::AsyncCallback::StringCallback
104
+
105
+ def processResult(rc, path, queue, str)
106
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, path: #{path}, queue: #{queue.inspect}, str: #{str.inspect}" }
107
+ queue.push(:rc => rc, :req_id => req_id, :path => path, :string => str)
108
+ end
109
+ end
110
+
111
+ class StatCallback < Callback
112
+ include JZK::AsyncCallback::StatCallback
113
+
114
+ def processResult(rc, path, queue, stat)
115
+ logger.debug { "#{self.class.name}#processResult rc: #{rc.inspect}, req_id: #{req_id}, path: #{path.inspect}, queue: #{queue.inspect}, stat: #{stat.inspect}" }
116
+ queue.push(:rc => rc, :req_id => req_id, :stat => (stat and stat.to_hash), :path => path)
117
+ end
118
+ end
119
+
120
+ class Children2Callback < Callback
121
+ include JZK::AsyncCallback::Children2Callback
122
+
123
+ def processResult(rc, path, queue, children, stat)
124
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, path: #{path}, queue: #{queue.inspect}, children: #{children.inspect}, stat: #{stat.inspect}" }
125
+ hash = {
126
+ :rc => rc,
127
+ :req_id => req_id,
128
+ :path => path,
129
+ :strings => (children && children.to_a),
130
+ :stat => (stat and stat.to_hash),
131
+ }
132
+
133
+ queue.push(hash)
134
+ end
135
+ end
136
+
137
+ class ACLCallback < Callback
138
+ include JZK::AsyncCallback::ACLCallback
139
+
140
+ def processResult(rc, path, queue, acl, stat)
141
+ logger.debug { "ACLCallback#processResult rc: #{rc.inspect}, req_id: #{req_id}, path: #{path.inspect}, queue: #{queue.inspect}, acl: #{acl.inspect}, stat: #{stat.inspect}" }
142
+ a = Array(acl).map { |a| a.to_hash }
143
+ queue.push(:rc => rc, :req_id => req_id, :path => path, :acl => a, :stat => (stat && stat.to_hash))
144
+ end
145
+ end
146
+
147
+ class VoidCallback < Callback
148
+ include JZK::AsyncCallback::VoidCallback
149
+
150
+ def processResult(rc, path, queue)
151
+ logger.debug { "#{self.class.name}#processResult rc: #{rc}, req_id: #{req_id}, queue: #{queue.inspect}" }
152
+ queue.push(:rc => rc, :req_id => req_id, :path => path)
153
+ end
154
+ end
155
+
156
+ class WatcherCallback < Callback
157
+ include JZK::Watcher
158
+
159
+ def initialize(event_queue)
160
+ @event_queue = event_queue
161
+ super(ZookeeperBase::ZKRB_GLOBAL_CB_REQ)
162
+ end
163
+
164
+ def process(event)
165
+ logger.debug { "WatcherCallback got event: #{event.to_hash.inspect}" }
166
+ hash = event.to_hash.merge(:req_id => req_id)
167
+ @event_queue.push(hash)
168
+ end
169
+ end
170
+ end
171
+
172
+ attr_reader :event_queue
173
+
174
+ def reopen(timeout=10, watcher=nil)
175
+ # watcher ||= @default_watcher
176
+
177
+ @mutex.synchronize do
178
+ # flushes all outstanding watcher reqs.
179
+ @watcher_reqs.clear
180
+ set_default_global_watcher
181
+
182
+ replace_jzk!
183
+ wait_until_connected
184
+ end
185
+
186
+ state
187
+ end
188
+
189
+ def wait_until_connected(timeout=10)
190
+ time_to_stop = timeout ? (Time.now + timeout) : nil
191
+
192
+ until connected? or (time_to_stop and Time.now > time_to_stop)
193
+ Thread.pass
194
+ end
195
+
196
+ connected?
197
+ end
198
+
199
+ def initialize(host, timeout=10, watcher=nil, options={})
200
+ @host = host
201
+ @event_queue = QueueWithPipe.new
202
+ @current_req_id = 0
203
+
204
+ @mutex = Monitor.new
205
+ @dispatch_shutdown_cond = @mutex.new_cond
206
+
207
+ @watcher_reqs = {}
208
+ @completion_reqs = {}
209
+ @_running = nil
210
+ @_closed = false
211
+ @options = {}
212
+
213
+ @default_watcher = (watcher || get_default_global_watcher)
214
+
215
+ # allows connected-state handlers to be registered before
216
+ yield self if block_given?
217
+
218
+ reopen(timeout)
219
+ return nil unless connected?
220
+ @_running = true
221
+ setup_dispatch_thread!
222
+ end
223
+
224
+ def close
225
+ shutdown_thread = Thread.new do
226
+ @mutex.synchronize do
227
+ unless @_closed
228
+ @_closed = true # these are probably unnecessary
229
+ @_running = false
230
+
231
+ stop_dispatch_thread!
232
+ @jzk.close if @jzk
233
+ end
234
+ end
235
+ end
236
+
237
+ shutdown_thread.join unless event_dispatch_thread?
238
+ end
239
+
240
+ def state
241
+ @mutex.synchronize { @jzk.state }
242
+ end
243
+
244
+ def connected?
245
+ state == JZK::ZooKeeper::States::CONNECTED
246
+ end
247
+
248
+ def connecting?
249
+ state == JZK::ZooKeeper::States::CONNECTING
250
+ end
251
+
252
+ def associating?
253
+ state == JZK::ZooKeeper::States::ASSOCIATING
254
+ end
255
+
256
+ def running?
257
+ @_running
258
+ end
259
+
260
+ def closed?
261
+ @_closed
262
+ end
263
+
264
+ def self.set_debug_level(*a)
265
+ # IGNORED IN JRUBY
266
+ end
267
+
268
+ def set_debug_level(*a)
269
+ # IGNORED IN JRUBY
270
+ end
271
+
272
+ def get(req_id, path, callback, watcher)
273
+ handle_keeper_exception do
274
+ watch_cb = watcher ? create_watcher(req_id, path) : false
275
+
276
+ if callback
277
+ jzk.getData(path, watch_cb, JavaCB::DataCallback.new(req_id), event_queue)
278
+ [Code::Ok, nil, nil] # the 'nil, nil' isn't strictly necessary here
279
+ else # sync
280
+ stat = JZKD::Stat.new
281
+ data = String.from_java_bytes(jzk.getData(path, watch_cb, stat))
282
+
283
+ [Code::Ok, data, stat.to_hash]
284
+ end
285
+ end
286
+ end
287
+
288
+ def set(req_id, path, data, callback, version)
289
+ handle_keeper_exception do
290
+ version ||= ANY_VERSION
291
+
292
+ if callback
293
+ jzk.setData(path, data.to_java_bytes, version, JavaCB::StatCallback.new(req_id), event_queue)
294
+ [Code::Ok, nil]
295
+ else
296
+ stat = jzk.setData(path, data.to_java_bytes, version).to_hash
297
+ [Code::Ok, stat]
298
+ end
299
+ end
300
+ end
301
+
302
+ def get_children(req_id, path, callback, watcher)
303
+ handle_keeper_exception do
304
+ watch_cb = watcher ? create_watcher(req_id, path) : false
305
+
306
+ if callback
307
+ jzk.getChildren(path, watch_cb, JavaCB::Children2Callback.new(req_id), event_queue)
308
+ [Code::Ok, nil, nil]
309
+ else
310
+ stat = JZKD::Stat.new
311
+ children = jzk.getChildren(path, watch_cb, stat)
312
+ [Code::Ok, children.to_a, stat.to_hash]
313
+ end
314
+ end
315
+ end
316
+
317
+ def create(req_id, path, data, callback, acl, flags)
318
+ handle_keeper_exception do
319
+ acl = Array(acl).map{ |a| JZKD::ACL.from_ruby_acl(a) }
320
+ mode = JZK::CreateMode.fromFlag(flags)
321
+
322
+ data ||= ''
323
+
324
+ if callback
325
+ jzk.create(path, data.to_java_bytes, acl, mode, JavaCB::StringCallback.new(req_id), event_queue)
326
+ [Code::Ok, nil]
327
+ else
328
+ new_path = jzk.create(path, data.to_java_bytes, acl, mode)
329
+ [Code::Ok, new_path]
330
+ end
331
+ end
332
+ end
333
+
334
+ def sync(req_id, path)
335
+ handle_keeper_exception do
336
+ jzk.sync(path, JavaCB::VoidCallback.new(req_id), event_queue)
337
+ Code::Ok
338
+ end
339
+ end
340
+
341
+ def delete(req_id, path, version, callback)
342
+ handle_keeper_exception do
343
+ if callback
344
+ jzk.delete(path, version, JavaCB::VoidCallback.new(req_id), event_queue)
345
+ else
346
+ jzk.delete(path, version)
347
+ end
348
+
349
+ Code::Ok
350
+ end
351
+ end
352
+
353
+ def set_acl(req_id, path, acl, callback, version)
354
+ handle_keeper_exception do
355
+ logger.debug { "set_acl: acl #{acl.inspect}" }
356
+ acl = Array(acl).flatten.map { |a| JZKD::ACL.from_ruby_acl(a) }
357
+ logger.debug { "set_acl: converted #{acl.inspect}" }
358
+
359
+ if callback
360
+ jzk.setACL(path, acl, version, JavaCB::ACLCallback.new(req_id), event_queue)
361
+ else
362
+ jzk.setACL(path, acl, version)
363
+ end
364
+
365
+ Code::Ok
366
+ end
367
+ end
368
+
369
+ def exists(req_id, path, callback, watcher)
370
+ handle_keeper_exception do
371
+ watch_cb = watcher ? create_watcher(req_id, path) : false
372
+
373
+ if callback
374
+ jzk.exists(path, watch_cb, JavaCB::StatCallback.new(req_id), event_queue)
375
+ [Code::Ok, nil, nil]
376
+ else
377
+ stat = jzk.exists(path, watch_cb)
378
+ [Code::Ok, (stat and stat.to_hash)]
379
+ end
380
+ end
381
+ end
382
+
383
+ def get_acl(req_id, path, callback)
384
+ handle_keeper_exception do
385
+ stat = JZKD::Stat.new
386
+
387
+ if callback
388
+ logger.debug { "calling getACL, path: #{path.inspect}, stat: #{stat.inspect}" }
389
+ jzk.getACL(path, stat, JavaCB::ACLCallback.new(req_id), event_queue)
390
+ [Code::Ok, nil, nil]
391
+ else
392
+ acls = jzk.getACL(path, stat).map { |a| a.to_hash }
393
+
394
+ [Code::Ok, Array(acls).map{|m| m.to_hash}, stat.to_hash]
395
+ end
396
+ end
397
+ end
398
+
399
+ def assert_open
400
+ # XXX don't know how to check for valid session state!
401
+ raise ZookeeperException::NotConnected unless connected?
402
+ end
403
+
404
+ # set the watcher object/proc that will receive all global events (such as session/state events)
405
+ #---
406
+ # XXX: this code needs to be duplicated from ext/zookeeper_base.rb because
407
+ # it's called from the initializer, and because of the C impl. we can't have
408
+ # the two decend from a common base, and a module wouldn't work
409
+ def set_default_global_watcher
410
+ @mutex.synchronize do
411
+ @watcher_reqs[ZKRB_GLOBAL_CB_REQ] = { :watcher => @default_watcher, :watcher_context => nil }
412
+ end
413
+ end
414
+
415
+ def session_id
416
+ jzk.session_id
417
+ end
418
+
419
+ def session_passwd
420
+ jzk.session_passwd.to_s
421
+ end
422
+
423
+ protected
424
+ def jzk
425
+ @mutex.synchronize { @jzk }
426
+ end
427
+
428
+ def handle_keeper_exception
429
+ yield
430
+ rescue JZK::KeeperException => e
431
+ e.cause.code.intValue
432
+ end
433
+
434
+ def call_type(callback, watcher)
435
+ if callback
436
+ watcher ? :async_watch : :async
437
+ else
438
+ watcher ? :sync_watch : :sync
439
+ end
440
+ end
441
+
442
+ def create_watcher(req_id, path)
443
+ logger.debug { "creating watcher for req_id: #{req_id} path: #{path}" }
444
+ lambda do |event|
445
+ logger.debug { "watcher for req_id #{req_id}, path: #{path} called back" }
446
+ h = { :req_id => req_id, :type => event.type.int_value, :state => event.state.int_value, :path => path }
447
+ event_queue.push(h)
448
+ end
449
+ end
450
+
451
+ # method to wait until block passed returns true or timeout (default is 10 seconds) is reached
452
+ def wait_until(timeout=10, &block)
453
+ time_to_stop = Time.now + timeout
454
+ until yield do
455
+ break if Time.now > time_to_stop
456
+ sleep 0.1
457
+ end
458
+ end
459
+
460
+ # TODO: Make all global puts configurable
461
+ def get_default_global_watcher
462
+ Proc.new { |args|
463
+ logger.debug { "Ruby ZK Global CB called type=#{event_by_value(args[:type])} state=#{state_by_value(args[:state])}" }
464
+ true
465
+ }
466
+ end
467
+
468
+ private
469
+ def replace_jzk!
470
+ orig_jzk = @jzk
471
+ @jzk = JZK::ZooKeeper.new(@host, DEFAULT_SESSION_TIMEOUT, JavaCB::WatcherCallback.new(event_queue))
472
+ ensure
473
+ orig_jzk.close if orig_jzk
474
+ end
475
+ end
476
+
477
+
@@ -5,13 +5,22 @@ module ZookeeperACLs
5
5
  @scheme = hash[:scheme]
6
6
  @id = hash[:id]
7
7
  end
8
+
9
+ def to_hash
10
+ { :id => id, :scheme => scheme }
11
+ end
8
12
  end
9
13
 
10
14
  class ACL
11
15
  attr_reader :perms, :id
12
16
  def initialize(hash)
13
17
  @perms = hash[:perms]
14
- @id = hash[:id]
18
+ v = hash[:id]
19
+ @id = v.kind_of?(Hash) ? Id.new(v) : v
20
+ end
21
+
22
+ def to_hash
23
+ { :perms => perms, :id => id.to_hash }
15
24
  end
16
25
  end
17
26
 
@@ -45,10 +45,12 @@ module ZookeeperCallbacks
45
45
 
46
46
  class StringCallback < Callback
47
47
  ## acreate, async
48
- attr_reader :return_code, :path
48
+ attr_reader :return_code, :string, :context
49
+
50
+ alias path string
49
51
 
50
52
  def initialize_context(hash)
51
- @return_code, @path, @context = hash[:rc], hash[:path], hash[:context]
53
+ @return_code, @string, @context = hash[:rc], hash[:string], hash[:context]
52
54
  end
53
55
  end
54
56
 
@@ -57,7 +59,7 @@ module ZookeeperCallbacks
57
59
  attr_reader :return_code, :children, :stat
58
60
 
59
61
  def initialize_context(hash)
60
- @return_code, @children, @stat, @context = hash[:rc], hash[:children], hash[:stat], hash[:context]
62
+ @return_code, @children, @stat, @context = hash[:rc], hash[:strings], hash[:stat], hash[:context]
61
63
  end
62
64
  end
63
65
 
@@ -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