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 +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: 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-
|
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:
|
172
|
+
hash: 3
|
171
173
|
segments:
|
172
|
-
-
|
173
|
-
|
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
|