slyphon-zookeeper 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  Makefile
7
7
  pkg
8
8
  zookeeper.gemspec
9
+ Gemfile.lock
@@ -16,7 +16,8 @@ class ZookeeperBase < CZookeeper
16
16
  ZOO_LOG_LEVEL_WARN = 2
17
17
  ZOO_LOG_LEVEL_INFO = 3
18
18
  ZOO_LOG_LEVEL_DEBUG = 4
19
-
19
+
20
+
20
21
  def reopen(timeout = 10, watcher=nil)
21
22
  watcher ||= @default_watcher
22
23
 
@@ -26,13 +27,15 @@ class ZookeeperBase < CZookeeper
26
27
  set_default_global_watcher(&watcher)
27
28
  end
28
29
 
29
- init(@host)
30
+ @start_stop_mutex.synchronize do
31
+ init(@host)
30
32
 
31
- if timeout > 0
32
- time_to_stop = Time.now + timeout
33
- until state == Zookeeper::ZOO_CONNECTED_STATE
34
- break if Time.now > time_to_stop
35
- sleep 0.1
33
+ if timeout > 0
34
+ time_to_stop = Time.now + timeout
35
+ until state == Zookeeper::ZOO_CONNECTED_STATE
36
+ break if Time.now > time_to_stop
37
+ sleep 0.1
38
+ end
36
39
  end
37
40
  end
38
41
 
@@ -46,9 +49,12 @@ class ZookeeperBase < CZookeeper
46
49
  @current_req_id = 1
47
50
  @host = host
48
51
 
52
+ @start_stop_mutex = Mutex.new
53
+
49
54
  watcher ||= get_default_global_watcher
50
55
 
51
- @_running = nil # used by the C layer
56
+ @_running = nil # used by the C layer
57
+ @_closed = false # also used by the C layer
52
58
 
53
59
  yield self if block_given?
54
60
 
@@ -76,12 +82,32 @@ class ZookeeperBase < CZookeeper
76
82
  end
77
83
 
78
84
  def close
79
- @_running = false
80
- wake_event_loop!
81
-
82
- @dispatcher.join
85
+ @start_stop_mutex.synchronize do
86
+ @_running = false if @_running
87
+ end
88
+
89
+ if @dispatcher
90
+ wake_event_loop! unless @_closed
91
+ @dispatcher.join
92
+ end
93
+
94
+ @start_stop_mutex.synchronize do
95
+ unless @_closed
96
+ close_handle
97
+
98
+ # this is set up in the C init method, but it's easier to
99
+ # do the teardown here
100
+ begin
101
+ @selectable_io.close if @selectable_io
102
+ rescue IOError
103
+ end
104
+ end
105
+ end
106
+ end
83
107
 
84
- super
108
+ def set_debug_level(int)
109
+ warn "DEPRECATION WARNING: #{self.class.name}#set_debug_level, it has moved to the class level and will be removed in a future release"
110
+ self.class.set_debug_level(int)
85
111
  end
86
112
 
87
113
  # set the watcher object/proc that will receive all global events (such as session/state events)
@@ -92,16 +118,20 @@ class ZookeeperBase < CZookeeper
92
118
  end
93
119
  end
94
120
 
95
- protected
121
+ def closed?
122
+ @start_stop_mutex.synchronize { false|@_closed }
123
+ end
124
+
96
125
  def running?
97
- false|@_running
126
+ @start_stop_mutex.synchronize { false|@_running }
98
127
  end
99
128
 
129
+ protected
100
130
  def setup_dispatch_thread!
101
131
  @dispatcher = Thread.new do
102
132
  while running?
103
133
  begin # calling user code, so protect ourselves
104
- dispatch_next_callback
134
+ dispatch_next_callback
105
135
  rescue Exception => e
106
136
  $stderr.puts "Error in dispatch thread, #{e.class}: #{e.message}\n" << e.backtrace.map{|n| "\t#{n}"}.join("\n")
107
137
  end
data/ext/zookeeper_c.c CHANGED
@@ -17,11 +17,16 @@
17
17
  #include <stdio.h>
18
18
  #include <stdlib.h>
19
19
  #include <unistd.h>
20
+ #include <sys/fcntl.h>
21
+ #include <pthread.h>
20
22
 
21
23
  #include "zookeeper_lib.h"
22
24
 
25
+
23
26
  static VALUE Zookeeper = Qnil;
24
27
 
28
+ // slyphon: possibly add a lock to this for synchronizing during get_next_event
29
+
25
30
  struct zkrb_instance_data {
26
31
  zhandle_t *zh;
27
32
  clientid_t myid;
@@ -69,9 +74,53 @@ static void print_zkrb_instance_data(struct zkrb_instance_data* ptr) {
69
74
  fprintf(stderr, "}\n");
70
75
  }
71
76
 
77
+ // cargo culted from io.c
78
+ static VALUE zkrb_new_instance _((VALUE));
79
+
80
+ static VALUE zkrb_new_instance(VALUE args) {
81
+ return rb_class_new_instance(2, (VALUE*)args+1, *(VALUE*)args);
82
+ }
83
+
84
+ static int zkrb_dup(int orig) {
85
+ int fd;
86
+
87
+ fd = dup(orig);
88
+ if (fd < 0) {
89
+ if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
90
+ rb_gc();
91
+ fd = dup(orig);
92
+ }
93
+ if (fd < 0) {
94
+ rb_sys_fail(0);
95
+ }
96
+ }
97
+ return fd;
98
+ }
99
+
100
+ static VALUE create_selectable_io(zkrb_queue_t *q) {
101
+ // rb_cIO is the ruby IO class?
102
+
103
+ int pipe, state, read_fd;
104
+ VALUE args[3], reader;
105
+
106
+ read_fd = zkrb_dup(q->pipe_read);
107
+
108
+ args[0] = rb_cIO;
109
+ args[1] = INT2NUM(read_fd);
110
+ args[2] = INT2FIX(O_RDONLY);
111
+ reader = rb_protect(zkrb_new_instance, (VALUE)args, &state);
112
+
113
+ if (state) {
114
+ rb_jump_tag(state);
115
+ }
116
+
117
+ return reader;
118
+ }
119
+
72
120
  static VALUE method_init(int argc, VALUE* argv, VALUE self) {
73
121
  VALUE hostPort;
74
122
  VALUE options;
123
+
75
124
  rb_scan_args(argc, argv, "11", &hostPort, &options);
76
125
 
77
126
  if (NIL_P(options)) {
@@ -121,8 +170,10 @@ static VALUE method_init(int argc, VALUE* argv, VALUE self) {
121
170
  }
122
171
 
123
172
  rb_iv_set(self, "@data", data);
173
+ rb_iv_set(self, "@selectable_io", create_selectable_io(zk_local_ctx->queue));
124
174
  rb_iv_set(self, "@_running", Qtrue);
125
175
 
176
+
126
177
  return Qnil;
127
178
  }
128
179
 
@@ -421,7 +472,10 @@ static int is_running(VALUE self) {
421
472
  return RTEST(rval);
422
473
  }
423
474
 
424
- static VALUE method_get_next_event(VALUE self) {
475
+
476
+ /* slyphon: NEED TO PROTECT THIS AGAINST SHUTDOWN */
477
+
478
+ static VALUE method_get_next_event(VALUE self, VALUE blocking) {
425
479
  char buf[64];
426
480
  FETCH_DATA_PTR(self, zk);
427
481
 
@@ -439,19 +493,35 @@ static VALUE method_get_next_event(VALUE self) {
439
493
 
440
494
  /* Wait for an event using rb_thread_select() on the queue's pipe */
441
495
  if (event == NULL) {
442
- int fd = zk->queue->pipe_read;
443
- fd_set rset;
496
+ if (NIL_P(blocking) || (blocking == Qfalse)) {
497
+ return Qnil; // no event for us
498
+ }
499
+ else {
500
+ int fd = zk->queue->pipe_read;
501
+ ssize_t bytes_read = 0;
502
+
503
+ fd_set rset; // a file descriptor set for use w/ select()
504
+
505
+ FD_ZERO(&rset); // FD_ZERO clears the set
506
+ FD_SET(fd, &rset); // FD_SET adds fd to the rset
444
507
 
445
- FD_ZERO(&rset);
446
- FD_SET(fd, &rset);
508
+ // first arg is nfds: "the highest-numbered file descriptor in any of the three sets, plus 1"
509
+ // why? F*** you, that's why!
447
510
 
448
- if (rb_thread_select(fd + 1, &rset, NULL, NULL, NULL) == -1)
449
- rb_raise(rb_eRuntimeError, "select failed: %d", errno);
511
+ if (rb_thread_select(fd + 1, &rset, NULL, NULL, NULL) == -1)
512
+ rb_raise(rb_eRuntimeError, "select failed: %d", errno);
450
513
 
451
- if (read(fd, buf, sizeof(buf)) == -1)
452
- rb_raise(rb_eRuntimeError, "read failed: %d", errno);
514
+ bytes_read = read(fd, buf, sizeof(buf));
453
515
 
454
- continue;
516
+ if (bytes_read == -1) {
517
+ rb_raise(rb_eRuntimeError, "read failed: %d", errno);
518
+ }
519
+ else if (ZKRBDebugging) {
520
+ fprintf(stderr, "read %d bytes from the queue's pipe\n", bytes_read);
521
+ }
522
+
523
+ continue;
524
+ }
455
525
  }
456
526
 
457
527
  VALUE hash = zkrb_event_to_ruby(event);
@@ -493,11 +563,22 @@ static VALUE method_wake_event_loop_bang(VALUE self) {
493
563
  // return Qnil;
494
564
  // }
495
565
 
496
- static VALUE method_close(VALUE self) {
566
+ static VALUE method_close_handle(VALUE self) {
497
567
  FETCH_DATA_PTR(self, zk);
498
568
 
569
+ if (ZKRBDebugging) {
570
+ fprintf(stderr, "CLOSING ZK INSTANCE:");
571
+ print_zkrb_instance_data(zk);
572
+ }
573
+
574
+ // this is a value on the ruby side we can check to see if destroy_zkrb_instance
575
+ // has been called
576
+ rb_iv_set(self, "@_closed", Qtrue);
577
+
578
+
499
579
  /* Note that after zookeeper_close() returns, ZK handle is invalid */
500
580
  int rc = destroy_zkrb_instance(zk);
581
+
501
582
  return INT2FIX(rc);
502
583
  }
503
584
 
@@ -521,8 +602,7 @@ static VALUE method_recv_timeout(VALUE self) {
521
602
  return INT2NUM(zoo_recv_timeout(zk->zh));
522
603
  }
523
604
 
524
- // how do you make a class method??
525
- static VALUE method_set_debug_level(VALUE self, VALUE level) {
605
+ static VALUE klass_method_set_debug_level(VALUE klass, VALUE level) {
526
606
  Check_Type(level, T_FIXNUM);
527
607
  ZKRBDebugging = (FIX2INT(level) == ZOO_LOG_LEVEL_DEBUG);
528
608
  zoo_set_debug_level(FIX2INT(level));
@@ -549,7 +629,7 @@ static void zkrb_define_methods(void) {
549
629
  DEFINE_METHOD(set_acl, 5);
550
630
  DEFINE_METHOD(get_acl, 3);
551
631
  DEFINE_METHOD(client_id, 0);
552
- DEFINE_METHOD(close, 0);
632
+ DEFINE_METHOD(close_handle, 0);
553
633
  DEFINE_METHOD(deterministic_conn_order, 1);
554
634
  DEFINE_METHOD(is_unrecoverable, 0);
555
635
  DEFINE_METHOD(recv_timeout, 1);
@@ -559,13 +639,16 @@ static void zkrb_define_methods(void) {
559
639
  // DEFINE_METHOD(async, 1);
560
640
 
561
641
  // methods for the ruby-side event manager
562
- DEFINE_METHOD(get_next_event, 0);
642
+ DEFINE_METHOD(get_next_event, 1);
563
643
  DEFINE_METHOD(has_events, 0);
564
644
 
565
645
  // Make these class methods?
566
- DEFINE_METHOD(set_debug_level, 1);
567
646
  DEFINE_METHOD(zerror, 1);
568
647
 
648
+ rb_define_singleton_method(Zookeeper, "set_debug_level", klass_method_set_debug_level, 1);
649
+
650
+ rb_attr(Zookeeper, rb_intern("selectable_io"), 1, 0, Qtrue);
651
+
569
652
  rb_define_method(Zookeeper, "wake_event_loop!", method_wake_event_loop_bang, 0);
570
653
  }
571
654
 
data/ext/zookeeper_lib.c CHANGED
@@ -140,18 +140,26 @@ void zkrb_event_free(zkrb_event_t *event) {
140
140
  }
141
141
  case ZKRB_STRINGS: {
142
142
  struct zkrb_strings_completion *strings_ctx = event->completion.strings_completion;
143
- int k;
144
- for (k = 0; k < strings_ctx->values->count; ++k) free(strings_ctx->values->data[k]);
145
- free(strings_ctx->values);
143
+ if (strings_ctx->values != NULL) {
144
+ int k;
145
+ for (k = 0; k < strings_ctx->values->count; ++k) free(strings_ctx->values->data[k]);
146
+ free(strings_ctx->values);
147
+ }
146
148
  free(strings_ctx);
147
149
  break;
148
150
  }
149
151
  case ZKRB_STRINGS_STAT: {
150
152
  struct zkrb_strings_stat_completion *strings_stat_ctx = event->completion.strings_stat_completion;
151
- int k;
152
- for (k = 0; k < strings_stat_ctx->values->count; ++k) free(strings_stat_ctx->values->data[k]);
153
- free(strings_stat_ctx->values);
154
- free(strings_stat_ctx->stat);
153
+ if (strings_stat_ctx->values != NULL) {
154
+ int k;
155
+ for (k = 0; k < strings_stat_ctx->values->count; ++k) free(strings_stat_ctx->values->data[k]);
156
+ free(strings_stat_ctx->values);
157
+ }
158
+
159
+ if (strings_stat_ctx->stat != NULL) {
160
+ free(strings_stat_ctx->stat);
161
+ }
162
+
155
163
  free(strings_stat_ctx);
156
164
  break;
157
165
  }
@@ -200,33 +208,40 @@ VALUE zkrb_event_to_ruby(zkrb_event_t *event) {
200
208
  break;
201
209
  }
202
210
  case ZKRB_STAT: {
211
+ if (ZKRBDebugging) fprintf(stderr, "zkrb_event_to_ruby ZKRB_STAT\n");
203
212
  struct zkrb_stat_completion *stat_ctx = event->completion.stat_completion;
204
213
  rb_hash_aset(hash, GET_SYM("stat"), stat_ctx->stat ? zkrb_stat_to_rarray(stat_ctx->stat) : Qnil);
205
214
  break;
206
215
  }
207
216
  case ZKRB_STRING: {
217
+ if (ZKRBDebugging) fprintf(stderr, "zkrb_event_to_ruby ZKRB_STRING\n");
208
218
  struct zkrb_string_completion *string_ctx = event->completion.string_completion;
209
219
  rb_hash_aset(hash, GET_SYM("string"), string_ctx->value ? rb_str_new2(string_ctx->value) : Qnil);
210
220
  break;
211
221
  }
212
222
  case ZKRB_STRINGS: {
223
+ if (ZKRBDebugging) fprintf(stderr, "zkrb_event_to_ruby ZKRB_STRINGS\n");
213
224
  struct zkrb_strings_completion *strings_ctx = event->completion.strings_completion;
214
225
  rb_hash_aset(hash, GET_SYM("strings"), strings_ctx->values ? zkrb_string_vector_to_ruby(strings_ctx->values) : Qnil);
215
226
  break;
216
227
  }
217
228
  case ZKRB_STRINGS_STAT: {
229
+ if (ZKRBDebugging) fprintf(stderr, "zkrb_event_to_ruby ZKRB_STRINGS_STAT\n");
218
230
  struct zkrb_strings_stat_completion *strings_stat_ctx = event->completion.strings_stat_completion;
219
231
  rb_hash_aset(hash, GET_SYM("strings"), strings_stat_ctx->values ? zkrb_string_vector_to_ruby(strings_stat_ctx->values) : Qnil);
220
232
  rb_hash_aset(hash, GET_SYM("stat"), strings_stat_ctx->stat ? zkrb_stat_to_rarray(strings_stat_ctx->stat) : Qnil);
221
233
  break;
222
234
  }
223
235
  case ZKRB_ACL: {
236
+ if (ZKRBDebugging) fprintf(stderr, "zkrb_event_to_ruby ZKRB_ACL\n");
224
237
  struct zkrb_acl_completion *acl_ctx = event->completion.acl_completion;
225
238
  rb_hash_aset(hash, GET_SYM("acl"), acl_ctx->acl ? zkrb_acl_vector_to_ruby(acl_ctx->acl) : Qnil);
226
239
  rb_hash_aset(hash, GET_SYM("stat"), acl_ctx->stat ? zkrb_stat_to_rarray(acl_ctx->stat) : Qnil);
227
240
  break;
228
241
  }
229
242
  case ZKRB_WATCHER: {
243
+ if (ZKRBDebugging) fprintf(stderr, "zkrb_event_to_ruby ZKRB_WATCHER\n");
244
+ struct zkrb_acl_completion *acl_ctx = event->completion.acl_completion;
230
245
  struct zkrb_watcher_completion *watcher_ctx = event->completion.watcher_completion;
231
246
  rb_hash_aset(hash, GET_SYM("type"), INT2FIX(watcher_ctx->type));
232
247
  rb_hash_aset(hash, GET_SYM("state"), INT2FIX(watcher_ctx->state));
@@ -422,6 +437,7 @@ void zkrb_strings_stat_callback(
422
437
  struct zkrb_strings_stat_completion *sc = malloc(sizeof(struct zkrb_strings_stat_completion));
423
438
  sc->stat = NULL;
424
439
  if (stat != NULL) { sc->stat = malloc(sizeof(struct Stat)); memcpy(sc->stat, stat, sizeof(struct Stat)); }
440
+
425
441
  sc->values = (strings != NULL) ? zkrb_clone_string_vector(strings) : NULL;
426
442
 
427
443
  ZKH_SETUP_EVENT(queue, event);
@@ -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