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 +1 -1
- data/ext/c_zookeeper.rb +35 -12
- data/ext/zookeeper_base.rb +42 -11
- data/ext/zookeeper_c.c +3 -1
- data/ext/zookeeper_lib.c +35 -26
- data/ext/zookeeper_lib.h +5 -2
- data/java/java_base.rb +31 -22
- data/lib/zookeeper.rb +7 -1
- data/lib/zookeeper/common.rb +34 -20
- data/lib/zookeeper/compatibility.rb +2 -1
- data/lib/zookeeper/constants.rb +5 -0
- data/lib/zookeeper/forked.rb +19 -0
- data/lib/zookeeper/latch.rb +34 -0
- data/lib/zookeeper/version.rb +1 -1
- data/spec/default_watcher_spec.rb +1 -1
- data/spec/forked_connection_spec.rb +80 -0
- data/spec/latch_spec.rb +24 -0
- data/spec/spec_helper.rb +2 -2
- data/spec/support/zookeeper_spec_helpers.rb +77 -62
- metadata +14 -12
data/Rakefile
CHANGED
data/ext/c_zookeeper.rb
CHANGED
@@ -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::
|
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
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
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
|
data/ext/zookeeper_base.rb
CHANGED
@@ -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,
|
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
|
-
|
102
|
-
@dispatch_shutdown_cond = @mutex.new_cond
|
103
|
+
update_pid! # from Forked
|
103
104
|
|
104
105
|
@current_req_id = 0
|
105
|
-
|
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
|
-
|
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
|
data/ext/zookeeper_c.c
CHANGED
@@ -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
|
|
data/ext/zookeeper_lib.c
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
67
|
+
pthread_mutex_unlock(&q->mutex);
|
63
68
|
|
64
|
-
|
65
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
171
|
-
|
178
|
+
pthread_mutex_destroy(&queue->mutex);
|
179
|
+
|
180
|
+
free(queue);
|
172
181
|
}
|
173
182
|
|
174
183
|
zkrb_event_t *zkrb_event_alloc(void) {
|
data/ext/zookeeper_lib.h
CHANGED
@@ -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
|
107
|
-
int
|
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);
|
data/java/java_base.rb
CHANGED
@@ -61,7 +61,8 @@ class JavaBase
|
|
61
61
|
end
|
62
62
|
|
63
63
|
# used for internal dispatching
|
64
|
-
|
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
|
-
|
162
|
+
attr_reader :client
|
163
|
+
|
164
|
+
def initialize(event_queue, opts={})
|
161
165
|
@event_queue = event_queue
|
162
|
-
|
166
|
+
@client = opts[:client]
|
167
|
+
super(ZKRB_GLOBAL_CB_REQ)
|
163
168
|
end
|
164
169
|
|
165
170
|
def process(event)
|
166
|
-
|
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
|
-
|
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
|
-
|
453
|
-
|
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
|
data/lib/zookeeper.rb
CHANGED
@@ -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
|
+
|
data/lib/zookeeper/common.rb
CHANGED
@@ -31,8 +31,12 @@ protected
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def setup_watcher(req_id, call_opts)
|
34
|
-
@
|
35
|
-
|
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
|
-
@
|
46
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
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
|
-
|
102
|
-
logger.error { "Dispatch thread did not join cleanly,
|
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
|
data/lib/zookeeper/constants.rb
CHANGED
@@ -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
|
+
|
data/lib/zookeeper/version.rb
CHANGED
@@ -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
|
+
|
data/spec/latch_spec.rb
ADDED
@@ -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
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -32,8 +32,8 @@ end
|
|
32
32
|
|
33
33
|
RSpec.configure do |config|
|
34
34
|
config.mock_with :rspec
|
35
|
-
config.include
|
36
|
-
config.extend
|
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
|
2
|
-
|
1
|
+
module Zookeeper
|
2
|
+
module SpecHeleprs
|
3
|
+
class TimeoutError < StandardError; end
|
4
|
+
include Zookeeper::Constants
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
6
|
+
def logger
|
7
|
+
Zookeeper.logger
|
8
|
+
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
53
|
+
rv = z.delete(:path => path)
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
unless (rv[:rc].zero? or rv[:rc] == ZNONODE)
|
56
|
+
raise "oh noes! failed to delete #{path}"
|
57
|
+
end
|
56
58
|
|
57
|
-
|
58
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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:
|
5
|
-
prerelease:
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 0
|
9
9
|
- 0
|
10
|
-
|
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-
|
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:
|
140
|
+
hash: 3
|
139
141
|
segments:
|
140
|
-
-
|
141
|
-
|
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
|