zookeeper 1.0.0.beta.1-java → 1.0.0-java

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/Rakefile CHANGED
@@ -49,7 +49,7 @@ if File.exists?(release_ops_path)
49
49
  end
50
50
  end
51
51
 
52
- task :push => :build do
52
+ task :push do
53
53
  gems = FileList['*.gem']
54
54
  raise "No gemfiles to push!" if gems.empty?
55
55
 
@@ -8,7 +8,7 @@ require_relative 'zookeeper_c'
8
8
  # when we're garbage collected
9
9
  module Zookeeper
10
10
  class CZookeeper
11
- include Zookeeper::Common
11
+ include Zookeeper::Forked
12
12
  include Zookeeper::Constants
13
13
  include Zookeeper::Exceptions
14
14
 
@@ -16,6 +16,8 @@ class CZookeeper
16
16
 
17
17
  class GotNilEventException < StandardError; end
18
18
 
19
+ attr_accessor :original_pid
20
+
19
21
  # assume we're at debug level
20
22
  def self.get_debug_level
21
23
  @debug_level ||= ZOO_LOG_LEVEL_INFO
@@ -29,6 +31,9 @@ class CZookeeper
29
31
  def initialize(host, event_queue, opts={})
30
32
  @host = host
31
33
  @event_queue = event_queue
34
+
35
+ # keep track of the pid that created us
36
+ update_pid!
32
37
 
33
38
  # used by the C layer. CZookeeper sets this to true when the init method
34
39
  # has completed. once this is set to true, it stays true.
@@ -52,6 +57,9 @@ class CZookeeper
52
57
 
53
58
  # used to signal that we're running
54
59
  @running_cond = @start_stop_mutex.new_cond
60
+
61
+ # used to signal we've received the connected event
62
+ @connected_cond = @start_stop_mutex.new_cond
55
63
 
56
64
  @event_thread = nil
57
65
 
@@ -89,15 +97,21 @@ class CZookeeper
89
97
  def close
90
98
  return if closed?
91
99
 
92
- shut_down!
93
- stop_event_thread!
94
-
95
- @start_stop_mutex.synchronize do
100
+ fn_close = proc do
96
101
  if !@_closed and @_data
102
+ logger.debug { "CALLING CLOSE HANDLE!!" }
97
103
  close_handle
98
104
  end
99
105
  end
100
106
 
107
+ if forked?
108
+ fn_close.call
109
+ else
110
+ shut_down!
111
+ stop_event_thread!
112
+ @start_stop_mutex.synchronize(&fn_close)
113
+ end
114
+
101
115
  nil
102
116
  end
103
117
 
@@ -114,12 +128,9 @@ class CZookeeper
114
128
  # if timeout is nil, we never time out, and wait forever for CONNECTED state
115
129
  #
116
130
  def wait_until_connected(timeout=10)
117
- return false unless wait_until_running(timeout)
118
-
119
- time_to_stop = timeout ? (Time.now + timeout) : nil
120
-
121
- until connected? or (time_to_stop and Time.now > time_to_stop)
122
- Thread.pass
131
+ @start_stop_mutex.synchronize do
132
+ wait_until_running(timeout)
133
+ @connected_cond.wait(timeout) unless connected?
123
134
  end
124
135
 
125
136
  connected?
@@ -156,7 +167,7 @@ class CZookeeper
156
167
 
157
168
  logger.debug { "event_thread running: #{@_running}" }
158
169
 
159
- while true
170
+ until @_shutting_down
160
171
  begin
161
172
  _iterate_event_delivery
162
173
  rescue GotNilEventException
@@ -169,6 +180,12 @@ class CZookeeper
169
180
  def _iterate_event_delivery
170
181
  get_next_event(true).tap do |hash|
171
182
  raise GotNilEventException if hash.nil?
183
+
184
+ # TODO: should push notify_connected! down so that it's common to both java and C impl.
185
+ if hash.values_at(:req_id, :type, :state) == CONNECTED_EVENT_VALUES
186
+ notify_connected!
187
+ end
188
+
172
189
  @event_queue.push(hash)
173
190
  end
174
191
  end
@@ -212,5 +229,11 @@ class CZookeeper
212
229
  @running_cond.broadcast
213
230
  end
214
231
  end
232
+
233
+ def notify_connected!
234
+ @start_stop_mutex.synchronize do
235
+ @connected_cond.broadcast
236
+ end
237
+ end
215
238
  end
216
239
  end
@@ -8,12 +8,15 @@ require 'forwardable'
8
8
  module Zookeeper
9
9
  class ZookeeperBase
10
10
  extend Forwardable
11
+ include Zookeeper::Forked
11
12
  include Zookeeper::Common # XXX: clean this up, no need to include *everything*
12
13
  include Zookeeper::Callbacks
13
14
  include Zookeeper::Constants
14
15
  include Zookeeper::Exceptions
15
16
  include Zookeeper::ACLs
16
17
 
18
+ attr_accessor :original_pid
19
+
17
20
  # @private
18
21
  class ClientShutdownException < StandardError; end
19
22
 
@@ -50,7 +53,8 @@ class ZookeeperBase
50
53
  end
51
54
  end
52
55
 
53
- synchronized_delegation :@czk, :get_children, :exists, :delete, :get, :set, :set_acl, :get_acl, :client_id, :sync
56
+ synchronized_delegation :@czk, :get_children, :exists, :delete, :get, :set,
57
+ :set_acl, :get_acl, :client_id, :sync, :wait_until_connected
54
58
 
55
59
  # some state methods need to be more paranoid about locking to ensure the correct
56
60
  # state is returned
@@ -73,12 +77,10 @@ class ZookeeperBase
73
77
  if watcher and (watcher != @default_watcher)
74
78
  raise "You cannot set the watcher to a different value this way anymore!"
75
79
  end
76
-
77
- @mutex.synchronize do
78
- # keep track of what process we were in to protect
79
- # against reuse after fork()
80
- @pid = Process.pid
81
80
 
81
+ reopen_after_fork! if forked?
82
+
83
+ @mutex.synchronize do
82
84
  # flushes all outstanding watcher reqs.
83
85
  @watcher_reqs.clear
84
86
  set_default_global_watcher
@@ -98,11 +100,13 @@ class ZookeeperBase
98
100
  @watcher_reqs = {}
99
101
  @completion_reqs = {}
100
102
 
101
- @mutex = Monitor.new
102
- @dispatch_shutdown_cond = @mutex.new_cond
103
+ update_pid! # from Forked
103
104
 
104
105
  @current_req_id = 0
105
- @event_queue = QueueWithPipe.new
106
+
107
+ # set up state that also needs to be re-setup after a fork()
108
+ reopen_after_fork!
109
+
106
110
  @czk = nil
107
111
 
108
112
  # approximate the java behavior of raising java.lang.IllegalArgumentException if the host
@@ -117,13 +121,13 @@ class ZookeeperBase
117
121
 
118
122
  reopen(timeout)
119
123
  end
120
-
124
+
121
125
  # if either of these happen, the user will need to renegotiate a connection via reopen
122
126
  def assert_open
123
127
  @mutex.synchronize do
124
128
  raise Exceptions::SessionExpired if state == ZOO_EXPIRED_SESSION_STATE
125
129
  raise Exceptions::NotConnected unless connected?
126
- unless Process.pid == @pid
130
+ if forked?
127
131
  raise InheritedConnectionError, <<-EOS.gsub(/(?:^|\n)\s*/, ' ').strip
128
132
  You tried to use a connection inherited from another process [#{@pid}]
129
133
  You need to call reopen() after forking
@@ -132,6 +136,15 @@ class ZookeeperBase
132
136
  end
133
137
  end
134
138
 
139
+ # do not lock, do not mutex, just close the underlying handle this is
140
+ # potentially dangerous and should only be called after a fork() to close
141
+ # this instance
142
+ def close!
143
+ @czk && @czk.close
144
+ end
145
+
146
+ # close the connection normally, stops the dispatch thread and closes the
147
+ # underlying connection cleanly
135
148
  def close
136
149
  shutdown_thread = Thread.new do
137
150
  @mutex.synchronize do
@@ -189,6 +202,24 @@ class ZookeeperBase
189
202
  end
190
203
 
191
204
  protected
205
+ # this method may be called in either the fork case, or from the constructor
206
+ # to set up this state initially (so all of this is in one location). we rely
207
+ # on the forked? method to determine which it is
208
+ def reopen_after_fork!
209
+ logger.debug { "#{self.class}##{__method__}" }
210
+ @mutex = Monitor.new
211
+ @dispatch_shutdown_cond = @mutex.new_cond
212
+ @event_queue = QueueWithPipe.new
213
+
214
+ if @dispatcher and not @dispatcher.alive?
215
+ logger.debug { "#{self.class}##{__method__} re-starting dispatch thread" }
216
+ @dispatcher = nil
217
+ setup_dispatch_thread!
218
+ end
219
+
220
+ update_pid!
221
+ end
222
+
192
223
  # this is a hack: to provide consistency between the C and Java drivers when
193
224
  # using a chrooted connection, we wrap the callback in a block that will
194
225
  # strip the chroot path from the returned path (important in an async create
@@ -38,6 +38,7 @@ struct zkrb_instance_data {
38
38
  clientid_t myid;
39
39
  zkrb_queue_t *queue;
40
40
  long object_id; // the ruby object this instance data is associated with
41
+ pid_t orig_pid;
41
42
  };
42
43
 
43
44
  typedef enum {
@@ -159,7 +160,6 @@ static VALUE method_zkrb_init(int argc, VALUE* argv, VALUE self) {
159
160
  zoo_set_debug_level((int)log_level);
160
161
  }
161
162
 
162
-
163
163
  VALUE data;
164
164
  struct zkrb_instance_data *zk_local_ctx;
165
165
  data = Data_Make_Struct(CZookeeper, struct zkrb_instance_data, 0, free_zkrb_instance_data, zk_local_ctx);
@@ -189,6 +189,8 @@ static VALUE method_zkrb_init(int argc, VALUE* argv, VALUE self) {
189
189
  rb_raise(rb_eRuntimeError, "error connecting to zookeeper: %d", errno);
190
190
  }
191
191
 
192
+ zk_local_ctx->orig_pid = getpid();
193
+
192
194
  rb_iv_set(self, "@_data", data);
193
195
  rb_funcall(self, rb_intern("zkc_set_running_and_notify!"), 0);
194
196
 
@@ -44,25 +44,31 @@ pthread_mutex_t zkrb_q_mutex = PTHREAD_MUTEX_INITIALIZER;
44
44
 
45
45
 
46
46
  void zkrb_enqueue(zkrb_queue_t *q, zkrb_event_t *elt) {
47
- GLOBAL_MUTEX_LOCK("zkrb_enqueue");
47
+ if (q == NULL) {
48
+ zkrb_debug("zkrb_enqueue, queue ptr was NULL");
49
+ return;
50
+ }
51
+
52
+ if (q->tail == NULL) {
53
+ zkrb_debug("zkrb_enqeue, q->tail was NULL");
54
+ return;
55
+ }
48
56
 
49
- check_debug(q != NULL && q->tail != NULL, "zkrb_enqueue: queue ptr or tail was NULL\n");
57
+ pthread_mutex_lock(&q->mutex);
50
58
 
51
59
  q->tail->event = elt;
52
60
  q->tail->next = (zkrb_event_ll_t *) malloc(sizeof(zkrb_event_ll_t));
53
61
  q->tail = q->tail->next;
54
62
  q->tail->event = NULL;
55
63
  q->tail->next = NULL;
56
- ssize_t ret = write(q->pipe_write, "0", 1); /* Wake up Ruby listener */
57
64
 
58
- // XXX(slyphon): can't raise a ruby exception here as we may not be calling
59
- // this from a ruby thread. Calling into the interpreter from a non-ruby
60
- // thread is bad, mm'kay?
65
+ ssize_t ret = write(q->pipe_write, "0", 1); /* Wake up Ruby listener */
61
66
 
62
- check(ret != -1, "write to queue (%p) pipe failed!\n", q);
67
+ pthread_mutex_unlock(&q->mutex);
63
68
 
64
- error:
65
- GLOBAL_MUTEX_UNLOCK("zkrb_enqueue");
69
+ if (ret < 0) {
70
+ log_err("write to queue (%p) pipe failed!\n", q);
71
+ }
66
72
  }
67
73
 
68
74
  // NOTE: the zkrb_event_t* returned *is* the same pointer that's part of the
@@ -73,13 +79,16 @@ error:
73
79
  zkrb_event_t * zkrb_peek(zkrb_queue_t *q) {
74
80
  zkrb_event_t *event = NULL;
75
81
 
76
- GLOBAL_MUTEX_LOCK("zkrb_peek");
82
+ if (!q) return NULL;
83
+
84
+ pthread_mutex_lock(&q->mutex);
77
85
 
78
86
  if (q != NULL && q->head != NULL && q->head->event != NULL) {
79
87
  event = q->head->event;
80
88
  }
81
89
 
82
- GLOBAL_MUTEX_UNLOCK("zkrb_peek");
90
+ pthread_mutex_unlock(&q->mutex);
91
+
83
92
  return event;
84
93
  }
85
94
 
@@ -90,7 +99,7 @@ zkrb_event_t* zkrb_dequeue(zkrb_queue_t *q, int need_lock) {
90
99
  zkrb_event_ll_t *old_root = NULL;
91
100
 
92
101
  if (need_lock)
93
- GLOBAL_MUTEX_LOCK("zkrb_dequeue");
102
+ pthread_mutex_lock(&q->mutex);
94
103
 
95
104
  if (!ZKRB_QUEUE_EMPTY(q)) {
96
105
  old_root = q->head;
@@ -99,19 +108,21 @@ zkrb_event_t* zkrb_dequeue(zkrb_queue_t *q, int need_lock) {
99
108
  }
100
109
 
101
110
  if (need_lock)
102
- GLOBAL_MUTEX_UNLOCK("zkrb_dequeue");
111
+ pthread_mutex_unlock(&q->mutex);
103
112
 
104
113
  free(old_root);
105
114
  return rv;
106
115
  }
107
116
 
108
117
  void zkrb_signal(zkrb_queue_t *q) {
109
- GLOBAL_MUTEX_LOCK("zkrb_signal");
118
+ if (!q) return;
119
+
120
+ pthread_mutex_lock(&q->mutex);
110
121
 
111
122
  if (!write(q->pipe_write, "0", 1)) /* Wake up Ruby listener */
112
123
  log_err("zkrb_signal: write to pipe failed, could not wake");
113
124
 
114
- GLOBAL_MUTEX_UNLOCK("zkrb_signal");
125
+ pthread_mutex_unlock(&q->mutex);
115
126
  }
116
127
 
117
128
  zkrb_event_ll_t *zkrb_event_ll_t_alloc(void) {
@@ -126,10 +137,6 @@ zkrb_event_ll_t *zkrb_event_ll_t_alloc(void) {
126
137
  }
127
138
 
128
139
  zkrb_queue_t *zkrb_queue_alloc(void) {
129
- // some of the locking is a little coarse, but it
130
- // eases the logic of releasing in case of error.
131
- GLOBAL_MUTEX_LOCK("zkrb_queue_alloc");
132
-
133
140
  int pfd[2];
134
141
  zkrb_queue_t *rq = NULL;
135
142
 
@@ -138,6 +145,10 @@ zkrb_queue_t *zkrb_queue_alloc(void) {
138
145
  rq = malloc(sizeof(zkrb_queue_t));
139
146
  check_mem(rq);
140
147
 
148
+ rq->orig_pid = getpid();
149
+
150
+ check(pthread_mutex_init(&rq->mutex, NULL) == 0, "pthread_mutex_init failed");
151
+
141
152
  rq->head = zkrb_event_ll_t_alloc();
142
153
  check_mem(rq->head);
143
154
 
@@ -145,30 +156,28 @@ zkrb_queue_t *zkrb_queue_alloc(void) {
145
156
  rq->pipe_read = pfd[0];
146
157
  rq->pipe_write = pfd[1];
147
158
 
148
- GLOBAL_MUTEX_UNLOCK("zkrb_queue_alloc");
149
159
  return rq;
150
160
 
151
161
  error:
152
- GLOBAL_MUTEX_UNLOCK("zkrb_queue_alloc");
153
162
  free(rq);
154
163
  return NULL;
155
164
  }
156
165
 
157
166
  void zkrb_queue_free(zkrb_queue_t *queue) {
158
- GLOBAL_MUTEX_LOCK("zkrb_queue_free");
159
- check_debug(queue != NULL, "zkrb_queue_free: queue was NULL");
167
+ if (!queue) return;
160
168
 
161
169
  zkrb_event_t *elt;
162
170
  while ((elt = zkrb_dequeue(queue, 0)) != NULL) {
163
171
  zkrb_event_free(elt);
164
172
  }
173
+
165
174
  free(queue->head);
166
175
  close(queue->pipe_read);
167
176
  close(queue->pipe_write);
168
- free(queue);
169
177
 
170
- error:
171
- GLOBAL_MUTEX_UNLOCK("zkrb_queue_free");
178
+ pthread_mutex_destroy(&queue->mutex);
179
+
180
+ free(queue);
172
181
  }
173
182
 
174
183
  zkrb_event_t *zkrb_event_alloc(void) {
@@ -6,6 +6,7 @@
6
6
  #include <errno.h>
7
7
  #include <stdio.h>
8
8
  #include <stdlib.h>
9
+ #include <unistd.h>
9
10
 
10
11
  #define ZK_TRUE 1
11
12
  #define ZK_FALSE 0
@@ -103,8 +104,10 @@ typedef struct zkrb_event_ll zkrb_event_ll_t;
103
104
  typedef struct {
104
105
  zkrb_event_ll_t *head;
105
106
  zkrb_event_ll_t *tail;
106
- int pipe_read;
107
- int pipe_write;
107
+ int pipe_read;
108
+ int pipe_write;
109
+ pthread_mutex_t mutex;
110
+ pid_t orig_pid;
108
111
  } zkrb_queue_t;
109
112
 
110
113
  zkrb_queue_t * zkrb_queue_alloc(void);
@@ -61,7 +61,8 @@ class JavaBase
61
61
  end
62
62
 
63
63
  # used for internal dispatching
64
- module JavaCB #:nodoc:
64
+ # @private
65
+ module JavaCB
65
66
  class Callback
66
67
  attr_reader :req_id
67
68
 
@@ -156,14 +157,25 @@ class JavaBase
156
157
 
157
158
  class WatcherCallback < Callback
158
159
  include JZK::Watcher
160
+ include Zookeeper::Constants
159
161
 
160
- def initialize(event_queue)
162
+ attr_reader :client
163
+
164
+ def initialize(event_queue, opts={})
161
165
  @event_queue = event_queue
162
- super(Zookeeper::Constants::ZKRB_GLOBAL_CB_REQ)
166
+ @client = opts[:client]
167
+ super(ZKRB_GLOBAL_CB_REQ)
163
168
  end
164
169
 
165
170
  def process(event)
166
- logger.debug { "WatcherCallback got event: #{event.to_hash.inspect}" }
171
+ hash = event.to_hash
172
+ logger.debug { "WatcherCallback got event: #{hash.inspect}" }
173
+
174
+ if client && (hash[:type] == ZOO_SESSION_EVENT) && (hash[:state] == ZOO_CONNECTED_STATE)
175
+ logger.debug { "#{self.class}##{__method__} notifying client of connected state!" }
176
+ client.notify_connected!
177
+ end
178
+
167
179
  hash = event.to_hash.merge(:req_id => req_id)
168
180
  @event_queue.push(hash)
169
181
  end
@@ -188,12 +200,7 @@ class JavaBase
188
200
  end
189
201
 
190
202
  def wait_until_connected(timeout=10)
191
- time_to_stop = timeout ? (Time.now + timeout) : nil
192
-
193
- until connected? or (time_to_stop and Time.now > time_to_stop)
194
- Thread.pass
195
- end
196
-
203
+ @connected_latch.await(timeout) unless connected?
197
204
  connected?
198
205
  end
199
206
 
@@ -204,6 +211,7 @@ class JavaBase
204
211
 
205
212
  @mutex = Monitor.new
206
213
  @dispatch_shutdown_cond = @mutex.new_cond
214
+ @connected_latch = Latch.new
207
215
 
208
216
  @watcher_reqs = {}
209
217
  @completion_reqs = {}
@@ -407,6 +415,8 @@ class JavaBase
407
415
  # XXX: this code needs to be duplicated from ext/zookeeper_base.rb because
408
416
  # it's called from the initializer, and because of the C impl. we can't have
409
417
  # the two decend from a common base, and a module wouldn't work
418
+ #
419
+ # XXX: this is probably a relic?
410
420
  def set_default_global_watcher
411
421
  @mutex.synchronize do
412
422
  @watcher_reqs[ZKRB_GLOBAL_CB_REQ] = { :watcher => @default_watcher, :watcher_context => nil }
@@ -421,6 +431,12 @@ class JavaBase
421
431
  jzk.session_passwd.to_s
422
432
  end
423
433
 
434
+ # called from watcher when we are connected
435
+ # @private
436
+ def notify_connected!
437
+ @connected_latch.release
438
+ end
439
+
424
440
  protected
425
441
  def jzk
426
442
  @mutex.synchronize { @jzk }
@@ -443,22 +459,15 @@ class JavaBase
443
459
  def create_watcher(req_id, path)
444
460
  logger.debug { "creating watcher for req_id: #{req_id} path: #{path}" }
445
461
  lambda do |event|
462
+ ev_type, ev_state = event.type.int_value, event.state.int_value
463
+
446
464
  logger.debug { "watcher for req_id #{req_id}, path: #{path} called back" }
447
- h = { :req_id => req_id, :type => event.type.int_value, :state => event.state.int_value, :path => path }
448
- event_queue.push(h)
449
- end
450
- end
451
465
 
452
- # method to wait until block passed returns true or timeout (default is 10 seconds) is reached
453
- def wait_until(timeout=10, &block)
454
- time_to_stop = Time.now + timeout
455
- until yield do
456
- break if Time.now > time_to_stop
457
- sleep 0.1
466
+ h = { :req_id => req_id, :type => ev_type, :state => ev_state, :path => path }
467
+ event_queue.push(h)
458
468
  end
459
469
  end
460
470
 
461
- # TODO: Make all global puts configurable
462
471
  def get_default_global_watcher
463
472
  Proc.new { |args|
464
473
  logger.debug { "Ruby ZK Global CB called type=#{event_by_value(args[:type])} state=#{state_by_value(args[:state])}" }
@@ -469,7 +478,7 @@ class JavaBase
469
478
  private
470
479
  def replace_jzk!
471
480
  orig_jzk = @jzk
472
- @jzk = JZK::ZooKeeper.new(@host, DEFAULT_SESSION_TIMEOUT, JavaCB::WatcherCallback.new(event_queue))
481
+ @jzk = JZK::ZooKeeper.new(@host, DEFAULT_SESSION_TIMEOUT, JavaCB::WatcherCallback.new(event_queue, :client => self))
473
482
  ensure
474
483
  orig_jzk.close if orig_jzk
475
484
  end
@@ -11,6 +11,8 @@ module Zookeeper
11
11
  # establishes the namespace
12
12
  end
13
13
 
14
+ require_relative 'zookeeper/forked'
15
+ require_relative 'zookeeper/latch'
14
16
  require_relative 'zookeeper/acls'
15
17
  require_relative 'zookeeper/constants'
16
18
  require_relative 'zookeeper/exceptions'
@@ -27,7 +29,7 @@ module Zookeeper
27
29
  include Constants
28
30
 
29
31
  unless defined?(@@logger)
30
- @@logger = Logger.new($stderr).tap { |l| l.level = Logger::ERROR }
32
+ @@logger = Logger.new($stderr).tap { |l| l.level = ENV['ZOOKEEPER_DEBUG'] ? Logger::DEBUG : Logger::ERROR }
31
33
  end
32
34
 
33
35
  def self.logger
@@ -83,3 +85,7 @@ end
83
85
  # just for first test, get rid of this soon
84
86
  require_relative 'zookeeper/compatibility'
85
87
 
88
+ if ENV['ZKRB_DEBUG']
89
+ Zookeeper.debug_level = Zookeeper::Constants::ZOO_LOG_LEVEL_DEBUG
90
+ end
91
+
@@ -31,8 +31,12 @@ protected
31
31
  end
32
32
 
33
33
  def setup_watcher(req_id, call_opts)
34
- @watcher_reqs[req_id] = { :watcher => call_opts[:watcher],
35
- :context => call_opts[:watcher_context] }
34
+ @mutex.synchronize do
35
+ @watcher_reqs[req_id] = {
36
+ :watcher => call_opts[:watcher],
37
+ :context => call_opts[:watcher_context]
38
+ }
39
+ end
36
40
  end
37
41
 
38
42
  # as a hack, to provide consistency between the java implementation and the C
@@ -42,8 +46,12 @@ protected
42
46
  # we don't use meth_name here, but we need it in the C implementation
43
47
  #
44
48
  def setup_completion(req_id, meth_name, call_opts)
45
- @completion_reqs[req_id] = { :callback => call_opts[:callback],
46
- :context => call_opts[:callback_context] }
49
+ @mutex.synchronize do
50
+ @completion_reqs[req_id] = {
51
+ :callback => call_opts[:callback],
52
+ :context => call_opts[:callback_context]
53
+ }
54
+ end
47
55
  end
48
56
 
49
57
  def get_watcher(req_id)
@@ -57,19 +65,27 @@ protected
57
65
  end
58
66
 
59
67
  def setup_dispatch_thread!
60
- logger.debug { "starting dispatch thread" }
61
- @dispatcher ||= Thread.new do
62
- while true
63
- begin
64
- dispatch_next_callback(get_next_event(true))
65
- rescue QueueWithPipe::ShutdownException
66
- logger.info { "dispatch thread exiting, got shutdown exception" }
67
- break
68
- rescue Exception => e
69
- $stderr.puts ["#{e.class}: #{e.message}", e.backtrace.map { |n| "\t#{n}" }.join("\n")].join("\n")
68
+ @mutex.synchronize do
69
+ if @dispatcher
70
+ logger.debug { "dispatcher already running" }
71
+ return
72
+ end
73
+
74
+ logger.debug { "starting dispatch thread" }
75
+
76
+ @dispatcher = Thread.new do
77
+ while true
78
+ begin
79
+ dispatch_next_callback(get_next_event(true))
80
+ rescue QueueWithPipe::ShutdownException
81
+ logger.info { "dispatch thread exiting, got shutdown exception" }
82
+ break
83
+ rescue Exception => e
84
+ $stderr.puts ["#{e.class}: #{e.message}", e.backtrace.map { |n| "\t#{n}" }.join("\n")].join("\n")
85
+ end
70
86
  end
87
+ signal_dispatch_thread_exit!
71
88
  end
72
- signal_dispatch_thread_exit!
73
89
  end
74
90
  end
75
91
 
@@ -93,13 +109,11 @@ protected
93
109
  # we now release the mutex so that dispatch_next_callback can grab it
94
110
  # to do what it needs to do while delivering events
95
111
  #
96
- # wait for a maximum of 2 sec for dispatcher to signal exit (should be
97
- # fast)
98
- @dispatch_shutdown_cond.wait(2)
112
+ @dispatch_shutdown_cond.wait
99
113
 
100
114
  # wait for another 2 sec for the thread to join
101
- unless @dispatcher.join(2)
102
- logger.error { "Dispatch thread did not join cleanly, continuing" }
115
+ until @dispatcher.join(2)
116
+ logger.error { "Dispatch thread did not join cleanly, waiting" }
103
117
  end
104
118
  @dispatcher = nil
105
119
  end
@@ -28,7 +28,8 @@ module Zookeeper
28
28
  end
29
29
  end
30
30
 
31
- Zookeeper.warn_about_compatability_once!
31
+ # at request of @eric
32
+ #Zookeeper.warn_about_compatability_once!
32
33
 
33
34
  module Zookeeper
34
35
  module Compatibility
@@ -58,6 +58,11 @@ module Constants
58
58
 
59
59
  ZKRB_GLOBAL_CB_REQ = -1
60
60
 
61
+ # @private
62
+ CONNECTED_EVENT_VALUES = [Constants::ZKRB_GLOBAL_CB_REQ,
63
+ Constants::ZOO_SESSION_EVENT,
64
+ Constants::ZOO_CONNECTED_STATE].freeze
65
+
61
66
  # used to find the name for a numeric event
62
67
  # @private
63
68
  EVENT_TYPE_NAMES = {
@@ -0,0 +1,19 @@
1
+ module Zookeeper
2
+ module Forked
3
+ # the includer provides an 'original_pid' method, which is set
4
+ # when the original 'owning' process creates the object.
5
+ #
6
+ # @return [true] if the current PID differs from the original_pid value
7
+ # @return [false] if the current PID matches the original_pid value
8
+ #
9
+ def forked?
10
+ Process.pid != original_pid
11
+ end
12
+
13
+ # sets the `original_pid` to the current value
14
+ def update_pid!
15
+ self.original_pid = Process.pid
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,34 @@
1
+ module Zookeeper
2
+ # a cross-thread gate of sorts.
3
+ class Latch
4
+ def initialize(count = 1)
5
+ @count = count
6
+ @mutex = Monitor.new
7
+ @cond = @mutex.new_cond
8
+ end
9
+
10
+ def release
11
+ @mutex.synchronize {
12
+ @count -= 1 if @count > 0
13
+ @cond.broadcast if @count.zero?
14
+ }
15
+ end
16
+
17
+ def await(timeout=nil)
18
+ @mutex.synchronize do
19
+ if timeout
20
+ time_to_stop = Time.now + timeout
21
+
22
+ while @count > 0
23
+ @cond.wait(timeout)
24
+
25
+ break if (Time.now > time_to_stop)
26
+ end
27
+ else
28
+ @cond.wait_while { @count > 0 }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -1,4 +1,4 @@
1
1
  module Zookeeper
2
- VERSION = '1.0.0.beta.1'
2
+ VERSION = '1.0.0'
3
3
  DRIVER_VERSION = '3.3.5'
4
4
  end
@@ -1,4 +1,4 @@
1
- require File.expand_path('../spec_helper', __FILE__)
1
+ require 'spec_helper'
2
2
 
3
3
  describe Zookeeper do
4
4
  describe :initialize, 'with watcher block' do
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ unless defined?(::JRUBY_VERSION)
4
+ describe %[forked connection] do
5
+ let(:path) { "/_zktest_" }
6
+ let(:pids_root) { "#{path}/pids" }
7
+ let(:data) { "underpants" }
8
+ let(:connection_string) { Zookeeper.default_cnx_str }
9
+
10
+ def process_alive?(pid)
11
+ Process.kill(0, @pid)
12
+ true
13
+ rescue Errno::ESRCH
14
+ false
15
+ end
16
+
17
+ before do
18
+ if defined?(::Rubinius)
19
+ pending("this test is currently broken in rbx")
20
+ elsif ENV['TRAVIS']
21
+ pending("this test is currently hanging in travis")
22
+ else
23
+ @zk = Zookeeper.new(connection_string)
24
+ rm_rf(@zk, path)
25
+ end
26
+ end
27
+
28
+ after do
29
+ if @pid and process_alive?(@pid)
30
+ Process.kill('TERM', @pid)
31
+ p Process.wait2(@pid)
32
+ end
33
+
34
+ @zk.close if @zk and !@zk.closed?
35
+ with_open_zk(connection_string) { |z| rm_rf(z, path) }
36
+ end
37
+
38
+ it %[should do the right thing and not fail] do
39
+ @zk.wait_until_connected
40
+
41
+ mkdir_p(@zk, pids_root)
42
+
43
+ # the parent's pid path
44
+ @zk.create(:path => "#{pids_root}/#{$$}", :data => $$.to_s)
45
+
46
+ @latch = Zookeeper::Latch.new
47
+ @event = nil
48
+
49
+ cb = proc do |h|
50
+ logger.debug { "watcher called back: #{h.inspect}" }
51
+ @event = h
52
+ @latch.release
53
+ end
54
+
55
+ @zk.stat(:path => "#{pids_root}/child", :watcher => cb)
56
+
57
+ @pid = fork do
58
+ logger.debug { "reopening connection in child: #{$$}" }
59
+ @zk.reopen
60
+ logger.debug { "creating path" }
61
+ rv = @zk.create(:path => "#{pids_root}/child", :data => $$.to_s)
62
+ logger.debug { "created path #{rv}" }
63
+ @zk.close
64
+
65
+ logger.debug { "close finished" }
66
+ exit!(0)
67
+ end
68
+
69
+ logger.debug { "waiting on child #{@pid}" }
70
+
71
+ @latch.await until @event
72
+ p @event
73
+
74
+ _, status = Process.wait2(@pid)
75
+
76
+ status.should be_success
77
+ end
78
+ end
79
+ end
80
+
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ # this is a simple sanity check of my timeout addition
4
+
5
+ describe Zookeeper::Latch do
6
+ subject { described_class.new }
7
+
8
+ describe %[await] do
9
+ describe %[with timeout] do
10
+ it %[should return after waiting until timeout if not released] do
11
+ other_latch = described_class.new
12
+
13
+ th = Thread.new do
14
+ subject.await(0.01)
15
+ other_latch.release
16
+ end
17
+
18
+ other_latch.await
19
+ th.join(1).should == th
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -32,8 +32,8 @@ end
32
32
 
33
33
  RSpec.configure do |config|
34
34
  config.mock_with :rspec
35
- config.include ZookeeperSpecHeleprs
36
- config.extend ZookeeperSpecHeleprs
35
+ config.include Zookeeper::SpecHeleprs
36
+ config.extend Zookeeper::SpecHeleprs
37
37
 
38
38
  if Zookeeper.spawn_zookeeper?
39
39
  require 'zk-server'
@@ -1,84 +1,99 @@
1
- module ZookeeperSpecHeleprs
2
- class TimeoutError < StandardError; end
1
+ module Zookeeper
2
+ module SpecHeleprs
3
+ class TimeoutError < StandardError; end
4
+ include Zookeeper::Constants
3
5
 
4
- def logger
5
- Zookeeper.logger
6
- end
6
+ def logger
7
+ Zookeeper.logger
8
+ end
7
9
 
8
- def ensure_node(zk, path, data)
9
- return if zk.closed?
10
- if zk.stat(:path => path)[:stat].exists?
11
- zk.set(:path => path, :data => data)
12
- else
13
- zk.create(:path => path, :data => data)
10
+ def ensure_node(zk, path, data)
11
+ return if zk.closed?
12
+ if zk.stat(:path => path)[:stat].exists?
13
+ zk.set(:path => path, :data => data)
14
+ else
15
+ zk.create(:path => path, :data => data)
16
+ end
14
17
  end
15
- end
16
18
 
17
- def with_open_zk(host=nil)
18
- z = Zookeeper.new(Zookeeper.default_cnx_str)
19
- yield z
20
- ensure
21
- if z
22
- unless z.closed?
23
- z.close
19
+ def with_open_zk(host=nil)
20
+ z = Zookeeper.new(Zookeeper.default_cnx_str)
21
+ yield z
22
+ ensure
23
+ if z
24
+ unless z.closed?
25
+ z.close
24
26
 
25
- wait_until do
26
- begin
27
- !z.connected?
28
- rescue RuntimeError
29
- true
27
+ wait_until do
28
+ begin
29
+ !z.connected?
30
+ rescue RuntimeError
31
+ true
32
+ end
30
33
  end
31
34
  end
32
35
  end
33
36
  end
34
- end
35
37
 
36
- # this is not as safe as the one in ZK, just to be used to clean up
37
- # when we're the only one adjusting a particular path
38
- def rm_rf(z, path)
39
- z.get_children(:path => path).tap do |h|
40
- if h[:rc].zero?
41
- h[:children].each do |child|
42
- rm_rf(z, File.join(path, child))
38
+ # this is not as safe as the one in ZK, just to be used to clean up
39
+ # when we're the only one adjusting a particular path
40
+ def rm_rf(z, path)
41
+ z.get_children(:path => path).tap do |h|
42
+ if h[:rc].zero?
43
+ h[:children].each do |child|
44
+ rm_rf(z, File.join(path, child))
45
+ end
46
+ elsif h[:rc] == ZNONODE
47
+ # no-op
48
+ else
49
+ raise "Oh noes! unexpected return value! #{h.inspect}"
43
50
  end
44
- elsif h[:rc] == Zookeeper::Exceptions::ZNONODE
45
- # no-op
46
- else
47
- raise "Oh noes! unexpected return value! #{h.inspect}"
48
51
  end
49
- end
50
52
 
51
- rv = z.delete(:path => path)
53
+ rv = z.delete(:path => path)
52
54
 
53
- unless (rv[:rc].zero? or rv[:rc] == Zookeeper::Exceptions::ZNONODE)
54
- raise "oh noes! failed to delete #{path}"
55
- end
55
+ unless (rv[:rc].zero? or rv[:rc] == ZNONODE)
56
+ raise "oh noes! failed to delete #{path}"
57
+ end
56
58
 
57
- path
58
- end
59
+ path
60
+ end
59
61
 
62
+ def mkdir_p(zk, path)
63
+ while true # loop because we can't retry
64
+ h = zk.create(:path => path, :data => '')
65
+ case h[:rc]
66
+ when ZOK, ZNODEEXISTS
67
+ return
68
+ when ZNONODE
69
+ parent = File.dirname(path)
70
+ raise "WTF? we wound up trying to create '/', something is screwed!" if parent == '/'
71
+ mkdir_p(zk, parent)
72
+ end
73
+ end
74
+ end
60
75
 
61
- # method to wait until block passed returns true or timeout (default is 10 seconds) is reached
62
- # raises TiemoutError on timeout
63
- def wait_until(timeout=10)
64
- time_to_stop = Time.now + timeout
65
- while true
66
- rval = yield
67
- return rval if rval
68
- raise TimeoutError, "timeout of #{timeout}s exceeded" if Time.now > time_to_stop
69
- Thread.pass
76
+ # method to wait until block passed returns true or timeout (default is 10 seconds) is reached
77
+ # raises TiemoutError on timeout
78
+ def wait_until(timeout=10)
79
+ time_to_stop = Time.now + timeout
80
+ while true
81
+ rval = yield
82
+ return rval if rval
83
+ raise TimeoutError, "timeout of #{timeout}s exceeded" if Time.now > time_to_stop
84
+ Thread.pass
85
+ end
70
86
  end
71
- end
72
87
 
73
- # inverse of wait_until
74
- def wait_while(timeout=10)
75
- time_to_stop = Time.now + timeout
76
- while true
77
- rval = yield
78
- return rval unless rval
79
- raise TimeoutError, "timeout of #{timeout}s exceeded" if Time.now > time_to_stop
80
- Thread.pass
88
+ # inverse of wait_until
89
+ def wait_while(timeout=10)
90
+ time_to_stop = Time.now + timeout
91
+ while true
92
+ rval = yield
93
+ return rval unless rval
94
+ raise TimeoutError, "timeout of #{timeout}s exceeded" if Time.now > time_to_stop
95
+ Thread.pass
96
+ end
81
97
  end
82
98
  end
83
99
  end
84
-
metadata CHANGED
@@ -1,15 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zookeeper
3
3
  version: !ruby/object:Gem::Version
4
- hash: 1547489243
5
- prerelease: 6
4
+ hash: 23
5
+ prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 0
9
9
  - 0
10
- - beta
11
- - 1
12
- version: 1.0.0.beta.1
10
+ version: 1.0.0
13
11
  platform: java
14
12
  authors:
15
13
  - Phillip Pearson
@@ -22,7 +20,7 @@ autorequire:
22
20
  bindir: bin
23
21
  cert_chain: []
24
22
 
25
- date: 2012-05-07 00:00:00 Z
23
+ date: 2012-05-09 00:00:00 Z
26
24
  dependencies:
27
25
  - !ruby/object:Gem::Dependency
28
26
  name: backports
@@ -126,6 +124,8 @@ files:
126
124
  - lib/zookeeper/constants.rb
127
125
  - lib/zookeeper/em_client.rb
128
126
  - lib/zookeeper/exceptions.rb
127
+ - lib/zookeeper/forked.rb
128
+ - lib/zookeeper/latch.rb
129
129
  - lib/zookeeper/rake_tasks.rb
130
130
  - lib/zookeeper/stat.rb
131
131
  - lib/zookeeper/version.rb
@@ -135,6 +135,8 @@ files:
135
135
  - spec/chrooted_connection_spec.rb
136
136
  - spec/default_watcher_spec.rb
137
137
  - spec/em_spec.rb
138
+ - spec/forked_connection_spec.rb
139
+ - spec/latch_spec.rb
138
140
  - spec/log4j.properties
139
141
  - spec/shared/all_success_return_values.rb
140
142
  - spec/shared/connection_examples.rb
@@ -165,14 +167,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
165
167
  required_rubygems_version: !ruby/object:Gem::Requirement
166
168
  none: false
167
169
  requirements:
168
- - - ">"
170
+ - - ">="
169
171
  - !ruby/object:Gem::Version
170
- hash: 25
172
+ hash: 3
171
173
  segments:
172
- - 1
173
- - 3
174
- - 1
175
- version: 1.3.1
174
+ - 0
175
+ version: "0"
176
176
  requirements: []
177
177
 
178
178
  rubyforge_project:
@@ -185,6 +185,8 @@ test_files:
185
185
  - spec/chrooted_connection_spec.rb
186
186
  - spec/default_watcher_spec.rb
187
187
  - spec/em_spec.rb
188
+ - spec/forked_connection_spec.rb
189
+ - spec/latch_spec.rb
188
190
  - spec/log4j.properties
189
191
  - spec/shared/all_success_return_values.rb
190
192
  - spec/shared/connection_examples.rb