zk 1.5.0 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -3
- data/README.markdown +6 -0
- data/RELEASES.markdown +6 -0
- data/lib/zk.rb +0 -1
- data/lib/zk/client/base.rb +7 -11
- data/lib/zk/client/threaded.rb +121 -45
- data/lib/zk/exceptions.rb +10 -1
- data/lib/zk/extensions.rb +6 -0
- data/lib/zk/fork_hook.rb +9 -13
- data/lib/zk/logging.rb +5 -6
- data/lib/zk/threaded_callback.rb +5 -3
- data/lib/zk/threadpool.rb +1 -1
- data/lib/zk/version.rb +1 -1
- data/spec/shared/client_examples.rb +3 -1
- data/spec/spec_helper.rb +6 -0
- data/spec/support/client_forker.rb +13 -3
- data/spec/support/logging.rb +6 -1
- data/spec/zk/client_spec.rb +74 -0
- data/spec/zk/locker_spec.rb +4 -5
- data/zk.gemspec +1 -1
- metadata +7 -7
data/Gemfile
CHANGED
@@ -28,10 +28,11 @@ platform :mri_19 do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
group :development do
|
31
|
-
gem 'guard',
|
32
|
-
gem 'guard-rspec',
|
33
|
-
gem 'guard-shell',
|
31
|
+
gem 'guard', :require => false
|
32
|
+
gem 'guard-rspec', :require => false
|
33
|
+
gem 'guard-shell', :require => false
|
34
34
|
gem 'guard-bundler', :require => false
|
35
|
+
gem 'growl', :require => false
|
35
36
|
end
|
36
37
|
|
37
38
|
group :test do
|
data/README.markdown
CHANGED
@@ -65,6 +65,12 @@ In addition to all of that, I would like to think that the public API the ZK::Cl
|
|
65
65
|
[zk-eventmachine]: https://github.com/slyphon/zk-eventmachine
|
66
66
|
|
67
67
|
## NEWS ##
|
68
|
+
### v1.5.1 ###
|
69
|
+
|
70
|
+
* Added a `:retry_duration` option to client constructor which will allows the user to specify for how long in the case of a connection loss, should an operation wait for the connection to be re-established before retrying the operation. This can be set at a global level and overridden on a per-call basis. The default is to not retry (which may change at a later date). Generally speaking, a timeout of > 30s is probably excessive, and care should be taken because during a connection loss, the server-side state may change without you being aware of it (i.e. events will not be delivered).
|
71
|
+
|
72
|
+
* Small fork-hook implementation fix. Previously we were using WeakRefs so that hooks would not prevent an object from being garbage collected. This has been replaced with a finalizer which is more deterministic.
|
73
|
+
|
68
74
|
### v1.5.0 ###
|
69
75
|
|
70
76
|
Ok, now seriously this time. I think all of the forking issues are done.
|
data/RELEASES.markdown
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
This file notes feature differences and bugfixes contained between releases.
|
2
2
|
|
3
|
+
### v1.5.1 ###
|
4
|
+
|
5
|
+
* Added a `:retry_duration` option to client constructor which will allows the user to specify for how long in the case of a connection loss, should an operation wait for the connection to be re-established before retrying the operation. This can be set at a global level and overridden on a per-call basis. The default is to not retry (which may change at a later date). Generally speaking, a timeout of > 30s is probably excessive, and care should be taken because during a connection loss, the server-side state may change without you being aware of it (i.e. events will not be delivered).
|
6
|
+
|
7
|
+
* Small fork-hook implementation fix. Previously we were using WeakRefs so that hooks would not prevent an object from being garbage collected. This has been replaced with a finalizer which is more deterministic.
|
8
|
+
|
3
9
|
### v1.5.0 ###
|
4
10
|
|
5
11
|
Ok, now seriously this time. I think all of the forking issues are done.
|
data/lib/zk.rb
CHANGED
data/lib/zk/client/base.rb
CHANGED
@@ -72,6 +72,8 @@ module ZK
|
|
72
72
|
|
73
73
|
# returns true if the connection has been closed
|
74
74
|
def closed?
|
75
|
+
return true if cnx.nil?
|
76
|
+
|
75
77
|
# XXX: should this be *our* idea of closed or ZOO_CLOSED_STATE ?
|
76
78
|
defined?(::JRUBY_VERSION) ? jruby_closed? : mri_closed?
|
77
79
|
end
|
@@ -600,16 +602,7 @@ module ZK
|
|
600
602
|
h = { :path => path }.merge(opts)
|
601
603
|
|
602
604
|
setup_watcher!(:data, h) do
|
603
|
-
|
604
|
-
|
605
|
-
return rv if opts[:callback]
|
606
|
-
|
607
|
-
case rv[:rc]
|
608
|
-
when Zookeeper::ZOK, Zookeeper::ZNONODE
|
609
|
-
rv[:stat]
|
610
|
-
else
|
611
|
-
check_rc(rv, h) # throws the appropriate error
|
612
|
-
end
|
605
|
+
call_and_check_rc(:stat, h.merge(:ignore => :no_node)).fetch(:stat)
|
613
606
|
end
|
614
607
|
end
|
615
608
|
|
@@ -1034,6 +1027,9 @@ module ZK
|
|
1034
1027
|
end
|
1035
1028
|
|
1036
1029
|
def call_and_check_rc(meth, opts)
|
1030
|
+
# TODO: we should not be raising Zookeeper errors, that's not cool.
|
1031
|
+
raise Zookeeper::Exceptions::NotConnected if cnx.nil?
|
1032
|
+
|
1037
1033
|
scrubbed_opts = opts.dup
|
1038
1034
|
scrubbed_opts.delete(:ignore)
|
1039
1035
|
|
@@ -1045,7 +1041,7 @@ module ZK
|
|
1045
1041
|
# @private
|
1046
1042
|
# XXX: make this actually call the method on cnx
|
1047
1043
|
def check_rc(rv_hash, inputs)
|
1048
|
-
code
|
1044
|
+
code = rv_hash[:rc]
|
1049
1045
|
|
1050
1046
|
if code && (code != Zookeeper::ZOK)
|
1051
1047
|
return rv_hash if ignore_set(inputs[:ignore]).include?(code)
|
data/lib/zk/client/threaded.rb
CHANGED
@@ -43,6 +43,15 @@ module ZK
|
|
43
43
|
|
44
44
|
DEFAULT_THREADPOOL_SIZE = 1
|
45
45
|
|
46
|
+
# @private
|
47
|
+
module Constants
|
48
|
+
CLI_RUNNING = :running
|
49
|
+
CLI_PAUSED = :paused
|
50
|
+
CLI_CLOSE_REQ = :close_requested
|
51
|
+
CLI_CLOSED = :closed
|
52
|
+
end
|
53
|
+
include Constants
|
54
|
+
|
46
55
|
# Construct a new threaded client.
|
47
56
|
#
|
48
57
|
# Pay close attention to the `:threaded` option, and have a look at the
|
@@ -84,6 +93,19 @@ module ZK
|
|
84
93
|
# that does something application specific, and you want to avoid a
|
85
94
|
# conflict.
|
86
95
|
#
|
96
|
+
# @option opts [Fixnum] :retry_duration (nil) for how long (in seconds)
|
97
|
+
# should we wait to re-attempt a synchronous operation after we receive a
|
98
|
+
# ZK::Exceptions::Retryable error. This exception (or really, group of
|
99
|
+
# exceptions) is raised when there has been an unintentional network
|
100
|
+
# connection or session loss, so retrying an operation in this situation
|
101
|
+
# is like saying "If we are disconnected, How long should we wait for the
|
102
|
+
# connection to become available before attempthing this operation?"
|
103
|
+
#
|
104
|
+
# The default `nil` means automatic retry is not attempted.
|
105
|
+
#
|
106
|
+
# This is a global option, and will be used for all operations on this
|
107
|
+
# connection, however it can be overridden for any individual operation.
|
108
|
+
#
|
87
109
|
# @option opts [:single,:per_callback] :thread (:single) choose your event
|
88
110
|
# delivery model:
|
89
111
|
#
|
@@ -140,9 +162,14 @@ module ZK
|
|
140
162
|
@reconnect = opts.fetch(:reconnect, true)
|
141
163
|
|
142
164
|
@mutex = Monitor.new
|
143
|
-
@cond
|
165
|
+
@cond = @mutex.new_cond
|
144
166
|
|
145
|
-
@cli_state =
|
167
|
+
@cli_state = CLI_RUNNING # this is to distinguish between *our* state and the underlying connection state
|
168
|
+
|
169
|
+
# this is the last status update we've received from the underlying connection
|
170
|
+
@last_cnx_state = Zookeeper::ZOO_CLOSED_STATE
|
171
|
+
|
172
|
+
@retry_duration = opts.fetch(:retry_duration, nil).to_i
|
146
173
|
|
147
174
|
@fork_subs = [
|
148
175
|
ForkHook.prepare_for_fork(method(:pause_before_fork_in_parent)),
|
@@ -150,22 +177,22 @@ module ZK
|
|
150
177
|
ForkHook.after_fork_in_child(method(:reopen)),
|
151
178
|
]
|
152
179
|
|
180
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(@fork_subs))
|
181
|
+
|
153
182
|
yield self if block_given?
|
154
183
|
|
155
|
-
|
156
|
-
|
157
|
-
|
184
|
+
connect if opts.fetch(:connect, true)
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.finalizer(hooks)
|
188
|
+
proc { hooks.each(&:unregister) }
|
158
189
|
end
|
159
190
|
|
160
191
|
# @option opts [Fixnum] :timeout how long we will wait for the connection
|
161
192
|
# to be established. If timeout is nil, we will wait forever: *use
|
162
193
|
# carefully*.
|
163
194
|
def connect(opts={})
|
164
|
-
@mutex.synchronize
|
165
|
-
return if @cnx
|
166
|
-
timeout = opts.fetch(:timeout, @connection_timeout)
|
167
|
-
@cnx = create_connection(@host, timeout, @event_handler.get_default_watcher_block)
|
168
|
-
end
|
195
|
+
@mutex.synchronize { unlocked_connect(opts) }
|
169
196
|
end
|
170
197
|
|
171
198
|
# (see Base#reopen)
|
@@ -178,9 +205,10 @@ module ZK
|
|
178
205
|
|
179
206
|
logger.debug { "reopening everything, fork detected!" }
|
180
207
|
|
181
|
-
@mutex
|
182
|
-
@
|
183
|
-
@
|
208
|
+
@mutex = Mutex.new
|
209
|
+
@cond = ConditionVariable.new
|
210
|
+
@pid = Process.pid
|
211
|
+
@cli_state = CLI_RUNNING # reset state to running if we were paused
|
184
212
|
|
185
213
|
old_cnx, @cnx = @cnx, nil
|
186
214
|
old_cnx.close! if old_cnx # && !old_cnx.closed?
|
@@ -195,11 +223,11 @@ module ZK
|
|
195
223
|
@event_handler.reopen_after_fork!
|
196
224
|
@threadpool.reopen_after_fork! # prune dead threadpool threads after a fork()
|
197
225
|
|
198
|
-
|
226
|
+
unlocked_connect
|
199
227
|
end
|
200
228
|
else
|
201
229
|
@mutex.synchronize do
|
202
|
-
if @cli_state ==
|
230
|
+
if @cli_state == CLI_PAUSED
|
203
231
|
# XXX: what to do in this case? does it matter?
|
204
232
|
end
|
205
233
|
|
@@ -220,23 +248,32 @@ module ZK
|
|
220
248
|
# before that deadline, or you will have to re-establish your session.
|
221
249
|
#
|
222
250
|
# @raise [InvalidStateError] when called and not in running? state
|
251
|
+
# @private
|
223
252
|
def pause_before_fork_in_parent
|
224
253
|
@mutex.synchronize do
|
225
|
-
raise InvalidStateError, "client must be running? when you call #{__method__}" unless
|
226
|
-
@cli_state =
|
254
|
+
raise InvalidStateError, "client must be running? when you call #{__method__}" unless (@cli_state == CLI_RUNNING)
|
255
|
+
@cli_state = CLI_PAUSED
|
256
|
+
|
257
|
+
logger.debug { "#{self.class}##{__method__}" }
|
258
|
+
|
259
|
+
@cond.broadcast
|
227
260
|
end
|
228
|
-
|
261
|
+
|
229
262
|
[@event_handler, @threadpool, @cnx].each(&:pause_before_fork_in_parent)
|
230
263
|
end
|
231
264
|
|
265
|
+
# @private
|
232
266
|
def resume_after_fork_in_parent
|
233
267
|
@mutex.synchronize do
|
234
|
-
raise InvalidStateError, "client must be paused? when you call #{__method__}" unless
|
235
|
-
@cli_state =
|
236
|
-
|
268
|
+
raise InvalidStateError, "client must be paused? when you call #{__method__}" unless (@cli_state == CLI_PAUSED)
|
269
|
+
@cli_state = CLI_RUNNING
|
270
|
+
|
271
|
+
logger.debug { "#{self.class}##{__method__}" }
|
237
272
|
|
238
|
-
|
239
|
-
|
273
|
+
[@cnx, @event_handler, @threadpool].each(&:resume_after_fork_in_parent)
|
274
|
+
|
275
|
+
@cond.broadcast
|
276
|
+
end
|
240
277
|
end
|
241
278
|
|
242
279
|
# (see Base#close!)
|
@@ -250,8 +287,9 @@ module ZK
|
|
250
287
|
def close!
|
251
288
|
@mutex.synchronize do
|
252
289
|
return if [:closed, :close_requested].include?(@cli_state)
|
253
|
-
|
254
|
-
@cli_state =
|
290
|
+
logger.debug { "moving to :close_requested state" }
|
291
|
+
@cli_state = CLI_CLOSE_REQ
|
292
|
+
@cond.broadcast
|
255
293
|
end
|
256
294
|
|
257
295
|
on_tpool = on_threadpool?
|
@@ -269,8 +307,9 @@ module ZK
|
|
269
307
|
super
|
270
308
|
|
271
309
|
@mutex.synchronize do
|
272
|
-
|
273
|
-
@cli_state =
|
310
|
+
logger.debug { "moving to :closed state" }
|
311
|
+
@cli_state = CLI_CLOSED
|
312
|
+
@cond.broadcast
|
274
313
|
end
|
275
314
|
end
|
276
315
|
|
@@ -298,44 +337,43 @@ module ZK
|
|
298
337
|
# @private
|
299
338
|
def raw_event_handler(event)
|
300
339
|
return unless event.session_event?
|
340
|
+
|
341
|
+
@mutex.synchronize do
|
342
|
+
@last_cnx_state = event.state
|
301
343
|
|
302
|
-
|
303
|
-
|
344
|
+
if event.client_invalid? and @reconnect and not dead_or_dying?
|
345
|
+
logger.error { "Got event #{event.state_name}, calling reopen(0)! things may be messed up until this works itself out!" }
|
304
346
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
# reopen(0) means that we don't want to wait for the connection
|
311
|
-
# to reach the connected state before returning as we're on the
|
312
|
-
# event thread.
|
313
|
-
reopen(0)
|
314
|
-
end
|
347
|
+
# reopen(0) means that we don't want to wait for the connection
|
348
|
+
# to reach the connected state before returning as we're on the
|
349
|
+
# event thread.
|
350
|
+
reopen(0)
|
315
351
|
end
|
352
|
+
|
353
|
+
@cond.broadcast # wake anyone waiting for a connection state update
|
316
354
|
end
|
317
355
|
rescue Exception => e
|
318
356
|
logger.error { "BUG: Exception caught in raw_event_handler: #{e.to_std_format}" }
|
319
357
|
end
|
320
358
|
|
321
359
|
def closed?
|
322
|
-
return true if @mutex.synchronize { @cli_state ==
|
360
|
+
return true if @mutex.synchronize { @cli_state == CLI_CLOSED }
|
323
361
|
super
|
324
362
|
end
|
325
363
|
|
326
364
|
# are we in running (not-paused) state?
|
327
365
|
def running?
|
328
|
-
@mutex.synchronize { @cli_state ==
|
366
|
+
@mutex.synchronize { @cli_state == CLI_RUNNING }
|
329
367
|
end
|
330
368
|
|
331
369
|
# are we in paused state?
|
332
370
|
def paused?
|
333
|
-
@mutex.synchronize { @cli_state ==
|
371
|
+
@mutex.synchronize { @cli_state == CLI_PAUSED }
|
334
372
|
end
|
335
373
|
|
336
374
|
# has shutdown time arrived?
|
337
375
|
def close_requested?
|
338
|
-
@mutex.synchronize { @cli_state ==
|
376
|
+
@mutex.synchronize { @cli_state == CLI_CLOSE_REQ }
|
339
377
|
end
|
340
378
|
|
341
379
|
protected
|
@@ -346,13 +384,51 @@ module ZK
|
|
346
384
|
@mutex.synchronize { @cnx }
|
347
385
|
end
|
348
386
|
|
349
|
-
|
387
|
+
def call_and_check_rc(meth, opts)
|
388
|
+
if retry_duration = (opts.delete(:retry_duration) || @retry_duration)
|
389
|
+
begin
|
390
|
+
super(meth, opts)
|
391
|
+
rescue Exceptions::Retryable => e
|
392
|
+
time_to_stop = Time.now + retry_duration
|
393
|
+
|
394
|
+
wait_until_connected_or_dying(retry_duration)
|
395
|
+
|
396
|
+
if (@last_cnx_state != Zookeeper::ZOO_CONNECTED_STATE) || (Time.now > time_to_stop) || (@cli_state != CLI_RUNNING)
|
397
|
+
raise e
|
398
|
+
else
|
399
|
+
retry
|
400
|
+
end
|
401
|
+
end
|
402
|
+
else
|
403
|
+
super
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
def wait_until_connected_or_dying(timeout)
|
408
|
+
time_to_stop = Time.now + timeout
|
409
|
+
|
410
|
+
@mutex.synchronize do
|
411
|
+
while (@last_cnx_state != Zookeeper::ZOO_CONNECTED_STATE) && (Time.now < time_to_stop) && (@cli_state == CLI_RUNNING)
|
412
|
+
@cond.wait(timeout)
|
413
|
+
end
|
414
|
+
|
415
|
+
logger.debug { "@last_cnx_state: #{@last_cnx_state}, time_left? #{Time.now.to_f < time_to_stop.to_f}, @cli_state: #{@cli_state.inspect}" }
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
350
419
|
def create_connection(*args)
|
351
420
|
::Zookeeper.new(*args)
|
352
421
|
end
|
353
422
|
|
354
423
|
def dead_or_dying?
|
355
|
-
(@cli_state ==
|
424
|
+
(@cli_state == CLI_CLOSE_REQ) || (@cli_state == CLI_CLOSED)
|
425
|
+
end
|
426
|
+
|
427
|
+
private
|
428
|
+
def unlocked_connect(opts={})
|
429
|
+
return if @cnx
|
430
|
+
timeout = opts.fetch(:timeout, @connection_timeout)
|
431
|
+
@cnx = create_connection(@host, timeout, @event_handler.get_default_watcher_block)
|
356
432
|
end
|
357
433
|
end
|
358
434
|
end
|
data/lib/zk/exceptions.rb
CHANGED
@@ -43,12 +43,15 @@ module ZK
|
|
43
43
|
module InterruptedSession
|
44
44
|
end
|
45
45
|
|
46
|
+
# mixed into exceptions that may be retried
|
47
|
+
module Retryable
|
48
|
+
end
|
49
|
+
|
46
50
|
class SystemError < KeeperException; end
|
47
51
|
class RunTimeInconsistency < KeeperException; end
|
48
52
|
class DataInconsistency < KeeperException; end
|
49
53
|
class MarshallingError < KeeperException; end
|
50
54
|
class Unimplemented < KeeperException; end
|
51
|
-
class OperationTimeOut < KeeperException; end
|
52
55
|
class BadArguments < KeeperException; end
|
53
56
|
class ApiError < KeeperException; end
|
54
57
|
class NoNode < KeeperException; end
|
@@ -61,12 +64,18 @@ module ZK
|
|
61
64
|
class InvalidACL < KeeperException; end
|
62
65
|
class AuthFailed < KeeperException; end
|
63
66
|
|
67
|
+
class OperationTimeOut < KeeperException
|
68
|
+
include Retryable
|
69
|
+
end
|
70
|
+
|
64
71
|
class ConnectionLoss < KeeperException
|
65
72
|
include InterruptedSession
|
73
|
+
include Retryable
|
66
74
|
end
|
67
75
|
|
68
76
|
class SessionExpired < KeeperException
|
69
77
|
include InterruptedSession
|
78
|
+
include Retryable
|
70
79
|
end
|
71
80
|
|
72
81
|
# mixes in InterruptedSession, and can be raised on its own
|
data/lib/zk/extensions.rb
CHANGED
data/lib/zk/fork_hook.rb
CHANGED
@@ -16,21 +16,22 @@ module ZK
|
|
16
16
|
# @private
|
17
17
|
def fire_prepare_hooks!
|
18
18
|
@mutex.lock
|
19
|
+
logger.debug { "#{__method__}" }
|
19
20
|
safe_call(@hooks[:prepare])
|
20
21
|
end
|
21
22
|
|
22
23
|
# @private
|
23
24
|
def fire_after_child_hooks!
|
24
|
-
safe_call(@hooks[:after_child])
|
25
|
-
ensure
|
26
25
|
@mutex.unlock rescue nil
|
26
|
+
logger.debug { "#{__method__}" }
|
27
|
+
safe_call(@hooks[:after_child])
|
27
28
|
end
|
28
29
|
|
29
30
|
# @private
|
30
31
|
def fire_after_parent_hooks!
|
31
|
-
safe_call(@hooks[:after_parent])
|
32
|
-
ensure
|
33
32
|
@mutex.unlock rescue nil
|
33
|
+
logger.debug { "#{__method__}" }
|
34
|
+
safe_call(@hooks[:after_parent])
|
34
35
|
end
|
35
36
|
|
36
37
|
# @private
|
@@ -52,15 +53,10 @@ module ZK
|
|
52
53
|
def safe_call(callbacks)
|
53
54
|
cbs = callbacks.dup
|
54
55
|
|
56
|
+
# exceptions in these hooks will be raised normally
|
57
|
+
|
55
58
|
while cb = cbs.shift
|
56
|
-
|
57
|
-
cb.call
|
58
|
-
rescue WeakRef::RefError
|
59
|
-
# clean weakrefs out of the original callback arrays if they're bad
|
60
|
-
callbacks.delete(cb)
|
61
|
-
rescue Exception => e
|
62
|
-
logger.error { e.to_std_format }
|
63
|
-
end
|
59
|
+
cb.call
|
64
60
|
end
|
65
61
|
end
|
66
62
|
|
@@ -76,7 +72,7 @@ module ZK
|
|
76
72
|
|
77
73
|
ForkSubscription.new(hook_type, block).tap do |sub|
|
78
74
|
# use a WeakRef so that the original objects can be GC'd
|
79
|
-
@mutex.synchronize { @hooks[hook_type] <<
|
75
|
+
@mutex.synchronize { @hooks[hook_type] << sub }
|
80
76
|
end
|
81
77
|
end
|
82
78
|
|
data/lib/zk/logging.rb
CHANGED
@@ -14,10 +14,13 @@ module ZK
|
|
14
14
|
::Logging.logger['ZK'].tap do |ch_root|
|
15
15
|
::Logging.appenders.stderr.tap do |serr|
|
16
16
|
serr.layout = ::Logging.layouts.pattern(
|
17
|
-
:pattern => '%.1l, [%d] %c30.30{2}: %m\n',
|
17
|
+
:pattern => '%.1l, [%d #p] %c30.30{2}: %m\n',
|
18
18
|
:date_pattern => '%Y-%m-%d %H:%M:%S.%6N'
|
19
19
|
)
|
20
20
|
|
21
|
+
serr.auto_flushing = 25
|
22
|
+
serr.flush_period = 5
|
23
|
+
|
21
24
|
ch_root.add_appenders(serr)
|
22
25
|
end
|
23
26
|
|
@@ -25,12 +28,8 @@ module ZK
|
|
25
28
|
end
|
26
29
|
end
|
27
30
|
|
28
|
-
# cache the logger at the instance level, as that's where most of the
|
29
|
-
# logging is done, this means that the user should set up the override
|
30
|
-
# of the ZK.logger early, before creating instances.
|
31
|
-
#
|
32
31
|
def logger
|
33
|
-
|
32
|
+
self.class.logger
|
34
33
|
end
|
35
34
|
end
|
36
35
|
end
|
data/lib/zk/threaded_callback.rb
CHANGED
@@ -124,6 +124,7 @@ module ZK
|
|
124
124
|
|
125
125
|
def dispatch_thread_body
|
126
126
|
Thread.current.abort_on_exception = true
|
127
|
+
Thread.current[:callback] = @callback
|
127
128
|
while true
|
128
129
|
args = nil
|
129
130
|
|
@@ -132,7 +133,7 @@ module ZK
|
|
132
133
|
@cond.wait(@mutex) while @array.empty? and @state == :running
|
133
134
|
|
134
135
|
if @state != :running
|
135
|
-
|
136
|
+
logger.warn { "ThreadedCallback, state is #{@state.inspect}, returning" }
|
136
137
|
return
|
137
138
|
end
|
138
139
|
|
@@ -147,8 +148,9 @@ module ZK
|
|
147
148
|
logger.error { e.to_std_format }
|
148
149
|
end
|
149
150
|
end
|
150
|
-
|
151
|
-
|
151
|
+
ensure
|
152
|
+
Thread.current[:callback] = nil
|
153
|
+
logger.debug { "##{__method__} returning" }
|
152
154
|
end
|
153
155
|
end
|
154
156
|
end
|
data/lib/zk/threadpool.rb
CHANGED
@@ -124,9 +124,9 @@ module ZK
|
|
124
124
|
begin
|
125
125
|
raise InvalidStateError, "invalid state, expected to be :running, was #{@state.inspect}" if @state != :running
|
126
126
|
return false if @state == :paused
|
127
|
+
threads = @threadpool.slice!(0, @threadpool.length)
|
127
128
|
@state = :paused
|
128
129
|
@cond.broadcast # wake threads, let them die
|
129
|
-
threads = @threadpool.slice!(0, @threadpool.length)
|
130
130
|
ensure
|
131
131
|
@mutex.unlock rescue nil
|
132
132
|
end
|
data/lib/zk/version.rb
CHANGED
@@ -302,7 +302,9 @@ shared_examples_for 'client' do
|
|
302
302
|
m.should_receive(:reopen).with(0).once
|
303
303
|
end
|
304
304
|
|
305
|
-
|
305
|
+
props = { :session_event? => true, :client_invalid? => true, :state_name => 'ZOO_EXPIRED_SESSION_STATE', :state => Zookeeper::ZOO_EXPIRED_SESSION_STATE }
|
306
|
+
|
307
|
+
bogus_event = flexmock(:expired_session_event, props)
|
306
308
|
|
307
309
|
@zk.raw_event_handler(bogus_event)
|
308
310
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -12,6 +12,7 @@ class ClientForker
|
|
12
12
|
@cnx_args = cnx_args
|
13
13
|
@base_path = base_path
|
14
14
|
@pids_root = "#{@base_path}/pid"
|
15
|
+
@child_latch = Latch.new
|
15
16
|
end
|
16
17
|
|
17
18
|
LBORDER = ('-' * 35) << '< '
|
@@ -63,6 +64,13 @@ class ClientForker
|
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
67
|
+
def start_child_exit_thread(pid)
|
68
|
+
@child_exit_thread ||= Thread.new do
|
69
|
+
_, @stat = Process.wait2(pid)
|
70
|
+
@child_latch.release
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
66
74
|
def run
|
67
75
|
before
|
68
76
|
mark 'BEGIN TEST'
|
@@ -102,7 +110,6 @@ class ClientForker
|
|
102
110
|
|
103
111
|
@pid = fork do
|
104
112
|
Thread.abort_on_exception = true
|
105
|
-
::Logging.reopen
|
106
113
|
|
107
114
|
@zk.wait_until_connected
|
108
115
|
|
@@ -169,6 +176,8 @@ class ClientForker
|
|
169
176
|
end
|
170
177
|
end # forked child
|
171
178
|
|
179
|
+
start_child_exit_thread(@pid)
|
180
|
+
|
172
181
|
# replicates deletion watcher inside child
|
173
182
|
child_pid_path = "#{@pids_root}/#{@pid}"
|
174
183
|
|
@@ -188,9 +197,10 @@ class ClientForker
|
|
188
197
|
|
189
198
|
delete_latch.await if @zk.exists?(child_pid_path, :watch => true)
|
190
199
|
|
191
|
-
|
200
|
+
@child_latch.await(30) # if we don't get a response in 30 seconds, then we're *definately* hosed
|
201
|
+
|
202
|
+
raise "Child did not exit after 30 seconds of waiting, something is very wrong" unless @stat
|
192
203
|
|
193
|
-
# $stderr.puts "#{@pid} exited with status: #{stat.inspect}"
|
194
204
|
ensure
|
195
205
|
mark "END TEST"
|
196
206
|
kill_child!
|
data/spec/support/logging.rb
CHANGED
@@ -9,6 +9,9 @@ layout = Logging.layouts.pattern(
|
|
9
9
|
|
10
10
|
appender = ENV['ZK_DEBUG'] ? Logging.appenders.stderr : Logging.appenders.file(ZK::TEST_LOG_PATH)
|
11
11
|
appender.layout = layout
|
12
|
+
#appender.immediate_at = "debug,info,warn,error,fatal"
|
13
|
+
appender.auto_flushing = 25
|
14
|
+
appender.flush_period = 5
|
12
15
|
|
13
16
|
%w[ZK ClientForker spec Zookeeper].each do |name|
|
14
17
|
::Logging.logger[name].tap do |log|
|
@@ -21,7 +24,9 @@ end
|
|
21
24
|
Logging.logger['ZK::EventHandler'].level = :info
|
22
25
|
|
23
26
|
Zookeeper.logger = Logging.logger['Zookeeper']
|
24
|
-
Zookeeper.logger.level = :
|
27
|
+
Zookeeper.logger.level = ENV['ZOOKEEPER_DEBUG'] ? :debug : :warn
|
28
|
+
|
29
|
+
ZK::ForkHook.after_fork_in_child { ::Logging.reopen }
|
25
30
|
|
26
31
|
# Zookeeper.logger = ZK.logger.clone_new_log(:progname => 'zoo')
|
27
32
|
|
data/spec/zk/client_spec.rb
CHANGED
@@ -40,5 +40,79 @@ describe ZK::Client::Threaded do
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
|
+
|
44
|
+
describe :retry do
|
45
|
+
include_context 'connection opts'
|
46
|
+
|
47
|
+
before do
|
48
|
+
@zk = ZK::Client::Threaded.new(connection_host, :reconect => false, :connect => false)
|
49
|
+
end
|
50
|
+
|
51
|
+
after do
|
52
|
+
@zk.close! unless @zk.closed?
|
53
|
+
end
|
54
|
+
|
55
|
+
it %[should retry a Retryable operation] do
|
56
|
+
# TODO: this is a terrible test. there is no way to guarantee that this
|
57
|
+
# has been retried. the join at the end should not raise an error
|
58
|
+
|
59
|
+
@zk.should_not be_connected
|
60
|
+
|
61
|
+
th = Thread.new do
|
62
|
+
@zk.stat('/path/to/blah', :retry_duration => 30)
|
63
|
+
end
|
64
|
+
|
65
|
+
th.run
|
66
|
+
|
67
|
+
@zk.connect
|
68
|
+
th.join(5).should == th
|
69
|
+
end
|
70
|
+
|
71
|
+
it %[barfs if the connection is closed before the connected event is received] do
|
72
|
+
@zk.should_not be_connected
|
73
|
+
|
74
|
+
exc = nil
|
75
|
+
|
76
|
+
th = Thread.new do
|
77
|
+
# this nonsense is because 1.8.7 is psychotic
|
78
|
+
begin
|
79
|
+
@zk.stat('/path/to/blah', :retry_duration => 300)
|
80
|
+
rescue Exception
|
81
|
+
exc = $!
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
th.run
|
86
|
+
|
87
|
+
@zk.close!
|
88
|
+
|
89
|
+
th.join(5).should == th
|
90
|
+
|
91
|
+
exc.should_not be_nil
|
92
|
+
exc.should be_kind_of(ZK::Exceptions::Retryable)
|
93
|
+
end
|
94
|
+
|
95
|
+
it %[should barf if the timeout expires] do
|
96
|
+
@zk.should_not be_connected
|
97
|
+
|
98
|
+
exc = nil
|
99
|
+
|
100
|
+
th = Thread.new do
|
101
|
+
# this nonsense is because 1.8.7 is psychotic
|
102
|
+
begin
|
103
|
+
@zk.stat('/path/to/blah', :retry_duration => 0.001)
|
104
|
+
rescue Exception
|
105
|
+
exc = $!
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
th.run
|
110
|
+
|
111
|
+
th.join(5).should == th
|
112
|
+
|
113
|
+
exc.should_not be_nil
|
114
|
+
exc.should be_kind_of(ZK::Exceptions::Retryable)
|
115
|
+
end
|
116
|
+
end
|
43
117
|
end # ZK::Client::Threaded
|
44
118
|
|
data/spec/zk/locker_spec.rb
CHANGED
@@ -432,7 +432,7 @@ shared_examples_for 'shared-exclusive interaction' do
|
|
432
432
|
ex_th = Thread.new do
|
433
433
|
begin
|
434
434
|
@ex_lock.lock(true) # blocking lock
|
435
|
-
|
435
|
+
@ex_lock.assert!
|
436
436
|
@array << :ex_lock
|
437
437
|
ensure
|
438
438
|
@ex_lock.unlock
|
@@ -455,7 +455,7 @@ shared_examples_for 'shared-exclusive interaction' do
|
|
455
455
|
sh2_th = Thread.new do
|
456
456
|
begin
|
457
457
|
@sh_lock2.lock(true)
|
458
|
-
|
458
|
+
@sh_lock2.assert!
|
459
459
|
@array << :sh_lock2
|
460
460
|
ensure
|
461
461
|
@sh_lock2.unlock
|
@@ -469,13 +469,12 @@ shared_examples_for 'shared-exclusive interaction' do
|
|
469
469
|
|
470
470
|
logger.debug { "@sh_lock2 is waiting" }
|
471
471
|
|
472
|
+
# ok, now unlock the first in the chain
|
473
|
+
@sh_lock.assert!
|
472
474
|
@sh_lock.unlock.should be_true
|
473
475
|
|
474
476
|
ex_th.join(5).should == ex_th
|
475
|
-
ex_th[:got_lock].should be_true
|
476
|
-
|
477
477
|
sh2_th.join(5).should == sh2_th
|
478
|
-
sh2_th[:got_lock].should be_true
|
479
478
|
|
480
479
|
@array.length.should == 2
|
481
480
|
@array.should == [:ex_lock, :sh_lock2]
|
data/zk.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.summary = %q{A high-level wrapper around the zookeeper driver}
|
13
13
|
s.description = s.summary + "\n"
|
14
14
|
|
15
|
-
s.add_runtime_dependency 'zookeeper', '~> 1.2.
|
15
|
+
s.add_runtime_dependency 'zookeeper', '~> 1.2.3'
|
16
16
|
s.add_runtime_dependency 'backports', '~> 2.5.1'
|
17
17
|
s.add_runtime_dependency 'logging', '~> 1.7.2'
|
18
18
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 1
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 5
|
9
|
-
-
|
10
|
-
version: 1.5.
|
9
|
+
- 1
|
10
|
+
version: 1.5.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jonathan D. Simms
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2012-05-
|
19
|
+
date: 2012-05-23 00:00:00 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: zookeeper
|
@@ -26,12 +26,12 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 25
|
30
30
|
segments:
|
31
31
|
- 1
|
32
32
|
- 2
|
33
|
-
-
|
34
|
-
version: 1.2.
|
33
|
+
- 3
|
34
|
+
version: 1.2.3
|
35
35
|
type: :runtime
|
36
36
|
version_requirements: *id001
|
37
37
|
- !ruby/object:Gem::Dependency
|