slyphon-zookeeper 0.1.7 → 0.2.0

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.
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