zookeeper 1.0.0.beta.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: 3809227699
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: ruby
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
@@ -94,6 +92,8 @@ files:
94
92
  - lib/zookeeper/constants.rb
95
93
  - lib/zookeeper/em_client.rb
96
94
  - lib/zookeeper/exceptions.rb
95
+ - lib/zookeeper/forked.rb
96
+ - lib/zookeeper/latch.rb
97
97
  - lib/zookeeper/rake_tasks.rb
98
98
  - lib/zookeeper/stat.rb
99
99
  - lib/zookeeper/version.rb
@@ -103,6 +103,8 @@ files:
103
103
  - spec/chrooted_connection_spec.rb
104
104
  - spec/default_watcher_spec.rb
105
105
  - spec/em_spec.rb
106
+ - spec/forked_connection_spec.rb
107
+ - spec/latch_spec.rb
106
108
  - spec/log4j.properties
107
109
  - spec/shared/all_success_return_values.rb
108
110
  - spec/shared/connection_examples.rb
@@ -133,14 +135,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
133
135
  required_rubygems_version: !ruby/object:Gem::Requirement
134
136
  none: false
135
137
  requirements:
136
- - - ">"
138
+ - - ">="
137
139
  - !ruby/object:Gem::Version
138
- hash: 25
140
+ hash: 3
139
141
  segments:
140
- - 1
141
- - 3
142
- - 1
143
- version: 1.3.1
142
+ - 0
143
+ version: "0"
144
144
  requirements: []
145
145
 
146
146
  rubyforge_project:
@@ -153,6 +153,8 @@ test_files:
153
153
  - spec/chrooted_connection_spec.rb
154
154
  - spec/default_watcher_spec.rb
155
155
  - spec/em_spec.rb
156
+ - spec/forked_connection_spec.rb
157
+ - spec/latch_spec.rb
156
158
  - spec/log4j.properties
157
159
  - spec/shared/all_success_return_values.rb
158
160
  - spec/shared/connection_examples.rb