slyphon-zookeeper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,165 @@
1
+ #ifndef ZOOKEEPER_LIB_H
2
+ #define ZOOKEEPER_LIB_H
3
+
4
+ #include "ruby.h"
5
+ #include "c-client-src/zookeeper.h"
6
+ #include <errno.h>
7
+ #include <stdio.h>
8
+ #include <stdlib.h>
9
+
10
+ #define ZK_TRUE 1
11
+ #define ZK_FALSE 0
12
+ #define ZKRB_GLOBAL_REQ -1
13
+
14
+ #ifndef RSTRING_LEN
15
+ # define RSTRING_LEN(x) RSTRING(x)->len
16
+ #endif
17
+ #ifndef RSTRING_PTR
18
+ # define RSTRING_PTR(x) RSTRING(x)->ptr
19
+ #endif
20
+ #ifndef RARRAY_LEN
21
+ # define RARRAY_LEN(x) RARRAY(x)->len
22
+ #endif
23
+
24
+ extern int ZKRBDebugging;
25
+ extern pthread_mutex_t zkrb_q_mutex;
26
+
27
+ struct zkrb_data_completion {
28
+ char *data;
29
+ int data_len;
30
+ struct Stat *stat;
31
+ };
32
+
33
+ struct zkrb_stat_completion {
34
+ struct Stat *stat;
35
+ };
36
+
37
+ struct zkrb_void_completion {
38
+ };
39
+
40
+ struct zkrb_string_completion {
41
+ char *value;
42
+ };
43
+
44
+ struct zkrb_strings_completion {
45
+ struct String_vector *values;
46
+ };
47
+
48
+ struct zkrb_strings_stat_completion {
49
+ struct String_vector *values;
50
+ struct Stat *stat;
51
+ };
52
+
53
+ struct zkrb_acl_completion {
54
+ struct ACL_vector *acl;
55
+ struct Stat *stat;
56
+ };
57
+
58
+ struct zkrb_watcher_completion {
59
+ int type;
60
+ int state;
61
+ char *path;
62
+ };
63
+
64
+ typedef struct {
65
+ int64_t req_id;
66
+ int rc;
67
+
68
+ enum {
69
+ ZKRB_DATA = 0,
70
+ ZKRB_STAT = 1,
71
+ ZKRB_VOID = 2,
72
+ ZKRB_STRING = 3,
73
+ ZKRB_STRINGS = 4,
74
+ ZKRB_STRINGS_STAT = 5,
75
+ ZKRB_ACL = 6,
76
+ ZKRB_WATCHER = 7
77
+ } type;
78
+
79
+ union {
80
+ struct zkrb_data_completion *data_completion;
81
+ struct zkrb_stat_completion *stat_completion;
82
+ struct zkrb_void_completion *void_completion;
83
+ struct zkrb_string_completion *string_completion;
84
+ struct zkrb_strings_completion *strings_completion;
85
+ struct zkrb_strings_stat_completion *strings_stat_completion;
86
+ struct zkrb_acl_completion *acl_completion;
87
+ struct zkrb_watcher_completion *watcher_completion;
88
+ } completion;
89
+ } zkrb_event_t;
90
+
91
+ struct zkrb_event_ll_t {
92
+ zkrb_event_t *event;
93
+ struct zkrb_event_ll_t *next;
94
+ };
95
+
96
+ typedef struct {
97
+ struct zkrb_event_ll_t *head;
98
+ struct zkrb_event_ll_t *tail;
99
+ int pipe_read;
100
+ int pipe_write;
101
+ } zkrb_queue_t;
102
+
103
+ zkrb_queue_t * zkrb_queue_alloc(void);
104
+ void zkrb_queue_free(zkrb_queue_t *queue);
105
+ zkrb_event_t * zkrb_event_alloc(void);
106
+ void zkrb_event_free(zkrb_event_t *ptr);
107
+
108
+ /* push/pop is a misnomer, this is a queue */
109
+ void zkrb_enqueue(zkrb_queue_t *queue, zkrb_event_t *elt);
110
+ zkrb_event_t * zkrb_peek(zkrb_queue_t *queue);
111
+ zkrb_event_t * zkrb_dequeue(zkrb_queue_t *queue, int need_lock);
112
+
113
+ void zkrb_print_stat(const struct Stat *s);
114
+
115
+ typedef struct {
116
+ int64_t req_id;
117
+ zkrb_queue_t *queue;
118
+ } zkrb_calling_context;
119
+
120
+ void zkrb_print_calling_context(zkrb_calling_context *ctx);
121
+ zkrb_calling_context *zkrb_calling_context_alloc(int64_t req_id, zkrb_queue_t *queue);
122
+
123
+ /*
124
+ default process completions that get queued into the ruby client event queue
125
+ */
126
+
127
+ void zkrb_state_callback(
128
+ zhandle_t *zh, int type, int state, const char *path, void *calling_ctx);
129
+
130
+ void zkrb_data_callback(
131
+ int rc, const char *value, int value_len, const struct Stat *stat, const void *calling_ctx);
132
+
133
+ void zkrb_stat_callback(
134
+ int rc, const struct Stat *stat, const void *calling_ctx);
135
+
136
+ void zkrb_string_callback(
137
+ int rc, const char *string, const void *calling_ctx);
138
+
139
+ void zkrb_strings_callback(
140
+ int rc, const struct String_vector *strings, const void *calling_ctx);
141
+
142
+ void zkrb_strings_stat_callback(
143
+ int rc, const struct String_vector *strings, const struct Stat* stat, const void *calling_ctx);
144
+
145
+ void zkrb_void_callback(
146
+ int rc, const void *calling_ctx);
147
+
148
+ void zkrb_acl_callback(
149
+ int rc, struct ACL_vector *acls, struct Stat *stat, const void *calling_ctx);
150
+
151
+ VALUE zkrb_event_to_ruby(zkrb_event_t *event);
152
+ VALUE zkrb_acl_to_ruby(struct ACL *acl);
153
+ VALUE zkrb_acl_vector_to_ruby(struct ACL_vector *acl_vector);
154
+ VALUE zkrb_id_to_ruby(struct Id *id);
155
+ VALUE zkrb_string_vector_to_ruby(struct String_vector *string_vector);
156
+ VALUE zkrb_stat_to_rarray(const struct Stat *stat);
157
+ VALUE zkrb_stat_to_rhash(const struct Stat* stat);
158
+
159
+ struct ACL_vector * zkrb_ruby_to_aclvector(VALUE acl_ary);
160
+ struct ACL_vector * zkrb_clone_acl_vector(struct ACL_vector * src);
161
+ struct String_vector * zkrb_clone_string_vector(const struct String_vector * src);
162
+ struct ACL zkrb_ruby_to_acl(VALUE rubyacl);
163
+ struct Id zkrb_ruby_to_id(VALUE rubyid);
164
+
165
+ #endif /* ZOOKEEPER_LIB_H */
@@ -0,0 +1,426 @@
1
+ require 'java'
2
+ require 'thread'
3
+ require 'rubygems'
4
+
5
+ gem 'slyphon-log4j', '= 1.2.15'
6
+ gem 'slyphon-zookeeper_jar', '= 3.3.1'
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
+ queue.push({
82
+ :rc => rc,
83
+ :req_id => req_id,
84
+ :path => path,
85
+ :data => String.from_java_bytes(data),
86
+ :stat => stat.to_hash,
87
+ })
88
+ end
89
+ end
90
+
91
+ class StringCallback < Callback
92
+ include JZK::AsyncCallback::StringCallback
93
+
94
+ def processResult(rc, path, queue, str)
95
+ queue.push(:rc => rc, :req_id => req_id, :path => path, :string => str)
96
+ end
97
+ end
98
+
99
+ class StatCallback < Callback
100
+ include JZK::AsyncCallback::StatCallback
101
+
102
+ def processResult(rc, path, queue, stat)
103
+ logger.debug { "StatCallback#processResult rc: #{rc.inspect}, path: #{path.inspect}, queue: #{queue.inspect}, stat: #{stat.inspect}" }
104
+ queue.push(:rc => rc, :req_id => req_id, :stat => (stat and stat.to_hash), :path => path)
105
+ end
106
+ end
107
+
108
+ class Children2Callback < Callback
109
+ include JZK::AsyncCallback::Children2Callback
110
+
111
+ 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)
113
+ end
114
+ end
115
+
116
+ class ACLCallback < Callback
117
+ include JZK::AsyncCallback::ACLCallback
118
+
119
+ def processResult(rc, path, queue, acl, stat)
120
+ logger.debug { "ACLCallback#processResult #{rc.inspect} #{path.inspect} #{queue.inspect} #{acl.inspect} #{stat.inspect}" }
121
+ 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)
123
+ end
124
+ end
125
+
126
+ class VoidCallback < Callback
127
+ include JZK::AsyncCallback::VoidCallback
128
+
129
+ def processResult(rc, path, queue)
130
+ queue.push(:rc => rc, :req_id => req_id, :path => path)
131
+ end
132
+ end
133
+
134
+ class WatcherCallback < Callback
135
+ include JZK::Watcher
136
+
137
+ def initialize(event_queue)
138
+ @event_queue = event_queue
139
+ super(ZookeeperBase::ZKRB_GLOBAL_CB_REQ)
140
+ end
141
+
142
+ def process(event)
143
+ logger.debug { "WatcherCallback got event: #{event.to_hash}" }
144
+ hash = event.to_hash.merge(:req_id => req_id)
145
+ @event_queue.push(hash)
146
+ end
147
+ end
148
+ end
149
+
150
+ def reopen(timeout=10, watcher=nil)
151
+ watcher ||= @default_watcher
152
+
153
+ @req_mutex.synchronize do
154
+ # flushes all outstanding watcher reqs.
155
+ @watcher_req = {}
156
+ set_default_global_watcher(&watcher)
157
+ end
158
+
159
+ @jzk = JZK::ZooKeeper.new(@host, DEFAULT_SESSION_TIMEOUT, JavaCB::WatcherCallback.new(@event_queue))
160
+
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
166
+ end
167
+ end
168
+
169
+ state
170
+ end
171
+
172
+ def initialize(host, timeout=10, watcher=nil)
173
+ @host = host
174
+ @event_queue = Queue.new
175
+ @current_req_id = 0
176
+ @req_mutex = Monitor.new
177
+ @watcher_reqs = {}
178
+ @completion_reqs = {}
179
+
180
+ watcher ||= get_default_global_watcher
181
+
182
+ reopen(timeout, watcher)
183
+ return nil unless connected?
184
+ setup_dispatch_thread!
185
+ end
186
+
187
+ def state
188
+ @jzk.state
189
+ end
190
+
191
+ def connected?
192
+ state == JZK::ZooKeeper::States::CONNECTED
193
+ end
194
+
195
+ def connecting?
196
+ state == JZK::ZooKeeper::States::CONNECTING
197
+ end
198
+
199
+ def associating?
200
+ state == JZK::ZooKeeper::States::ASSOCIATING
201
+ end
202
+
203
+ def get(req_id, path, callback, watcher)
204
+ handle_keeper_exception do
205
+ watch_cb = watcher ? create_watcher(req_id, path) : false
206
+
207
+ if callback
208
+ @jzk.getData(path, watch_cb, JavaCB::DataCallback.new(req_id), @event_queue)
209
+ [Code::Ok, nil, nil] # the 'nil, nil' isn't strictly necessary here
210
+ else # sync
211
+ stat = JZKD::Stat.new
212
+ data = String.from_java_bytes(@jzk.getData(path, watch_cb, stat))
213
+
214
+ [Code::Ok, data, stat.to_hash]
215
+ end
216
+ end
217
+ end
218
+
219
+ def set(req_id, path, data, callback, version)
220
+ handle_keeper_exception do
221
+ version ||= ANY_VERSION
222
+
223
+ if callback
224
+ @jzk.setData(path, data.to_java_bytes, version, JavaCB::StatCallback.new(req_id), @event_queue)
225
+ [Code::Ok, nil]
226
+ else
227
+ stat = @jzk.setData(path, data.to_java_bytes, version).to_hash
228
+ [Code::Ok, stat]
229
+ end
230
+ end
231
+ end
232
+
233
+ def get_children(req_id, path, callback, watcher)
234
+ handle_keeper_exception do
235
+ watch_cb = watcher ? create_watcher(req_id, path) : false
236
+
237
+ if callback
238
+ @jzk.getChildren(path, watch_cb, JavaCB::Children2Callback.new(req_id), @event_queue)
239
+ [Code::Ok, nil, nil]
240
+ else
241
+ stat = JZKD::Stat.new
242
+ children = @jzk.getChildren(path, watch_cb, stat)
243
+ [Code::Ok, children.to_a, stat.to_hash]
244
+ end
245
+ end
246
+ end
247
+
248
+ def create(req_id, path, data, callback, acl, flags)
249
+ handle_keeper_exception do
250
+ acl = Array(acl).map{ |a| JZKD::ACL.from_ruby_acl(a) }
251
+ mode = JZK::CreateMode.fromFlag(flags)
252
+
253
+ data ||= ''
254
+
255
+ if callback
256
+ @jzk.create(path, data.to_java_bytes, acl, mode, JavaCB::StringCallback.new(req_id), @event_queue)
257
+ [Code::Ok, nil]
258
+ else
259
+ new_path = @jzk.create(path, data.to_java_bytes, acl, mode)
260
+ [Code::Ok, new_path]
261
+ end
262
+ end
263
+ end
264
+
265
+ def delete(req_id, path, version, callback)
266
+ handle_keeper_exception do
267
+ if callback
268
+ @jzk.delete(path, version, JavaCB::VoidCallback.new(req_id), @event_queue)
269
+ else
270
+ @jzk.delete(path, version)
271
+ end
272
+
273
+ Code::Ok
274
+ end
275
+ end
276
+
277
+ def set_acl(req_id, path, acl, callback, version)
278
+ handle_keeper_exception do
279
+ logger.debug { "set_acl: acl #{acl.inspect}" }
280
+ acl = Array(acl).flatten.map { |a| JZKD::ACL.from_ruby_acl(a) }
281
+ logger.debug { "set_acl: converted #{acl.inspect}" }
282
+
283
+ if callback
284
+ @jzk.setACL(path, acl, version, JavaCB::ACLCallback.new(req_id), @event_queue)
285
+ else
286
+ @jzk.setACL(path, acl, version)
287
+ end
288
+
289
+ Code::Ok
290
+ end
291
+ end
292
+
293
+ def exists(req_id, path, callback, watcher)
294
+ handle_keeper_exception do
295
+ watch_cb = watcher ? create_watcher(req_id, path) : false
296
+
297
+ if callback
298
+ @jzk.exists(path, watch_cb, JavaCB::StatCallback.new(req_id), @event_queue)
299
+ [Code::Ok, nil, nil]
300
+ else
301
+ stat = @jzk.exists(path, watch_cb)
302
+ [Code::Ok, (stat and stat.to_hash)]
303
+ end
304
+ end
305
+ end
306
+
307
+ def get_acl(req_id, path, callback)
308
+ handle_keeper_exception do
309
+ stat = JZKD::Stat.new
310
+
311
+ if callback
312
+ logger.debug { "calling getACL, path: #{path.inspect}, stat: #{stat.inspect}" }
313
+ @jzk.getACL(path, stat, JavaCB::ACLCallback.new(req_id), @event_queue)
314
+ [Code::Ok, nil, nil]
315
+ else
316
+ acls = @jzk.getACL(path, stat).map { |a| a.to_hash }
317
+
318
+ [Code::Ok, Array(acls).map{|m| m.to_hash}, stat.to_hash]
319
+ end
320
+ end
321
+ end
322
+
323
+ def assert_open
324
+ # XXX don't know how to check for valid session state!
325
+ raise ZookeeperException::ConnectionClosed unless connected?
326
+ end
327
+
328
+ KILL_TOKEN = :__kill_token__
329
+
330
+ class DispatchShutdownException < StandardError; end
331
+
332
+ def close
333
+ @req_mutex.synchronize do
334
+ if @jzk
335
+ @jzk.close
336
+ wait_until { !connected? }
337
+ end
338
+
339
+ if @dispatcher
340
+ @dispatcher[:running] = false
341
+ @event_queue.push(KILL_TOKEN) # ignored by dispatch_next_callback
342
+ end
343
+ end
344
+
345
+ @dispatcher.join
346
+ end
347
+
348
+ # set the watcher object/proc that will receive all global events (such as session/state events)
349
+ #---
350
+ # XXX: this code needs to be duplicated from ext/zookeeper_base.rb because
351
+ # it's called from the initializer, and because of the C impl. we can't have
352
+ # the two decend from a common base, and a module wouldn't work
353
+ def set_default_global_watcher(&block)
354
+ @req_mutex.synchronize do
355
+ @default_watcher = block
356
+ @watcher_reqs[ZKRB_GLOBAL_CB_REQ] = { :watcher => @default_watcher, :watcher_context => nil }
357
+ end
358
+ end
359
+
360
+ protected
361
+ def handle_keeper_exception
362
+ yield
363
+ rescue JZK::KeeperException => e
364
+ e.cause.code.intValue
365
+ end
366
+
367
+ def call_type(callback, watcher)
368
+ if callback
369
+ watcher ? :async_watch : :async
370
+ else
371
+ watcher ? :sync_watch : :sync
372
+ end
373
+ end
374
+
375
+ def create_watcher(req_id, path)
376
+ lambda do |event|
377
+ h = { :req_id => req_id, :type => event.type.int_value, :state => event.state.int_value, :path => path }
378
+ @event_queue.push(h)
379
+ end
380
+ end
381
+
382
+ def get_next_event
383
+ @event_queue.pop.tap do |event|
384
+ raise DispatchShutdownException if event == KILL_TOKEN
385
+ end
386
+ end
387
+
388
+ # method to wait until block passed returns true or timeout (default is 10 seconds) is reached
389
+ def wait_until(timeout=10, &block)
390
+ time_to_stop = Time.now + timeout
391
+ until yield do
392
+ break if Time.now > time_to_stop
393
+ sleep 0.3
394
+ end
395
+ end
396
+
397
+ protected
398
+ # TODO: Make all global puts configurable
399
+ def get_default_global_watcher
400
+ Proc.new { |args|
401
+ logger.debug { "Ruby ZK Global CB called type=#{event_by_value(args[:type])} state=#{state_by_value(args[:state])}" }
402
+ true
403
+ }
404
+ end
405
+
406
+ private
407
+ def setup_dispatch_thread!
408
+ logger.debug { "starting dispatch thread" }
409
+ @dispatcher = Thread.new do
410
+ Thread.current[:running] = true
411
+
412
+ while Thread.current[:running]
413
+ begin
414
+ dispatch_next_callback
415
+ rescue DispatchShutdownException
416
+ logger.info { "dispatch thread exiting, got shutdown exception" }
417
+ break
418
+ rescue Exception => e
419
+ $stderr.puts ["#{e.class}: #{e.message}", e.backtrace.map { |n| "\t#{n}" }.join("\n")].join("\n")
420
+ end
421
+ end
422
+ end
423
+ end
424
+ end
425
+
426
+