zk 1.5.3 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Guardfile +9 -1
- data/README.markdown +32 -14
- data/RELEASES.markdown +23 -0
- data/lib/zk/client/base.rb +33 -15
- data/lib/zk/client/state_mixin.rb +8 -0
- data/lib/zk/client/threaded.rb +61 -36
- data/lib/zk/client/unixisms.rb +17 -5
- data/lib/zk/event.rb +6 -0
- data/lib/zk/event_handler.rb +33 -23
- data/lib/zk/locker.rb +47 -16
- data/lib/zk/locker/exclusive_locker.rb +9 -2
- data/lib/zk/locker/locker_base.rb +20 -13
- data/lib/zk/node_deletion_watcher.rb +20 -21
- data/lib/zk/subscription.rb +1 -0
- data/lib/zk/version.rb +1 -1
- data/spec/shared/client_contexts.rb +5 -1
- data/spec/shared/client_examples.rb +103 -13
- data/spec/support/logging.rb +6 -3
- data/spec/zk/locker/exclusive_locker_spec.rb +4 -1
- data/spec/zk/locker_spec.rb +23 -0
- metadata +7 -5
data/Guardfile
CHANGED
@@ -13,6 +13,11 @@ end
|
|
13
13
|
guard 'rspec', :version => 2 do
|
14
14
|
watch(%r{^spec/.+_spec\.rb$})
|
15
15
|
|
16
|
+
# run all specs when the support files change
|
17
|
+
watch(%r{^spec/support/.+\.rb$}) { 'spec' }
|
18
|
+
|
19
|
+
watch('spec/shared/client_examples.rb') { 'spec/zk/client_spec.rb' }
|
20
|
+
|
16
21
|
watch(%r%^spec/support/client_forker.rb$%) { 'spec/zk/00_forked_client_integration_spec.rb' }
|
17
22
|
|
18
23
|
watch(%r{^lib/(.+)\.rb$}) do |m|
|
@@ -23,10 +28,13 @@ guard 'rspec', :version => 2 do
|
|
23
28
|
when 'zk/client/threaded'
|
24
29
|
["spec/zk/client_spec.rb", "spec/zk/zookeeper_spec.rb"]
|
25
30
|
|
31
|
+
when 'zk/locker'
|
32
|
+
'spec/zk/locker_spec.rb'
|
33
|
+
|
26
34
|
when %r{^(?:zk/locker/locker_base|spec/shared/locker)}
|
27
35
|
Dir["spec/zk/locker/*_spec.rb"]
|
28
36
|
|
29
|
-
when %r{^zk/client/(?:base|state_mixin)}
|
37
|
+
when %r{^zk/client/(?:base|state_mixin|unixisms)}
|
30
38
|
Dir['spec/zk/{client,client/*,zookeeper}_spec.rb']
|
31
39
|
|
32
40
|
when 'zk' # .rb
|
data/README.markdown
CHANGED
@@ -65,6 +65,38 @@ 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.6.0 ###
|
69
|
+
|
70
|
+
* Locker cleanup code!
|
71
|
+
|
72
|
+
When a session is lost, it's likely that the locker's node name was left behind. so for `zk.locker('foo')` if the session is interrupted, it's very likely that the `/_zklocking/foo` znode has been left behind. A method has been added to allow you to safely clean up these stale znodes:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
ZK.open('localhost:2181') do |zk|
|
76
|
+
ZK::Locker.cleanup(zk)
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
Will go through your locker nodes one by one and try to lock and unlock them. If it succeeds, the lock is naturally cleaned up (as part of the normal teardown code), if it doesn't acquire the lock, then no harm, it knows that lock is still in use.
|
81
|
+
|
82
|
+
* Added `create('/path', 'data', :or => :set)` which will create a node (and all parent paths) with the given data or set its contents if it already exists. It's intended as a convenience when you just want a node to exist with a particular value.
|
83
|
+
|
84
|
+
* Added a bunch of shorter aliases on `ZK::Event`, so you can say `event.deleted?`, `event.changed?`, etc.
|
85
|
+
|
86
|
+
### v1.5.3 ###
|
87
|
+
|
88
|
+
* Fixed reconnect code. There was an occasional race/deadlock condition caused because the reopen call was done on the underlying connection's dispatch thread. Closing the dispatch thread is part of reopen, so this would cause a deadlock in real-world use. Moved the reconnect logic to a separate, single-purpose thread on ZK::Client::Threaded that watches for connection state changes.
|
89
|
+
|
90
|
+
* 'private' is not 'protected'. I've been writing ruby for several years now, and apparently I'd forgotten that 'protected' does not work like how it does in java. The visibility of these methods has been corrected, and all specs pass, so I don't expect issues...but please report if this change causes any bugs in user code.
|
91
|
+
|
92
|
+
### v1.5.2 ###
|
93
|
+
|
94
|
+
* Fix locker cleanup code to avoid a nasty race when a session is lost, see [issue #34](https://github.com/slyphon/zk/issues/34)
|
95
|
+
|
96
|
+
* Fix potential deadlock in ForkHook code so the mutex is unlocked in the case of an exception
|
97
|
+
|
98
|
+
* Do not hang forever when shutting down and the shutdown thread does not exit (wait 30 seconds).
|
99
|
+
|
68
100
|
### v1.5.1 ###
|
69
101
|
|
70
102
|
* Added a `:retry_duration` option to the Threaded 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).
|
@@ -118,20 +150,6 @@ zk.delete('/some/path', :ignore => :no_node)
|
|
118
150
|
* MASSIVE fork/parent/child test around event delivery and much greater stability expected for linux (with the zookeeper-1.0.3 gem). Again, please see the documentation on the wiki about [proper fork procedure](http://github.com/slyphon/zk/wiki/Forking).
|
119
151
|
|
120
152
|
|
121
|
-
### v1.3.1 ###
|
122
|
-
|
123
|
-
* [fix a bug][bug 1.3.1] where a forked client would not have its 'outstanding watches' cleared, so some events would never be delivered
|
124
|
-
|
125
|
-
[bug 1.3.1]: https://github.com/slyphon/zk/compare/release/1.3.0...9f68cee958fdaad8d32b6d042bf0a2c9ab5ec9b0
|
126
|
-
|
127
|
-
### v1.3.0 ###
|
128
|
-
|
129
|
-
Phusion Passenger and Unicorn users are encouraged to upgrade!
|
130
|
-
|
131
|
-
* __fork()__: ZK should now work reliably after a fork() if you call `reopen()` ASAP in the child process (before continuing any ZK work). Additionally, your event-handler (blocks set up with `zk.register`) will still work in the child. You will have to make calls like `zk.stat(path, :watch => true)` to tell ZooKeeper to notify you of events (as the child will have a new session), but everything should work.
|
132
|
-
|
133
|
-
* See the fork-handling documentation [on the wiki](http://github.com/slyphon/zk/wiki/Forking).
|
134
|
-
|
135
153
|
|
136
154
|
## Caveats
|
137
155
|
|
data/RELEASES.markdown
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
This file notes feature differences and bugfixes contained between releases.
|
2
2
|
|
3
|
+
### v1.6.0 ###
|
4
|
+
|
5
|
+
* Locker cleanup code!
|
6
|
+
|
7
|
+
When a session is lost, it's likely that the locker's node name was left behind. so for `zk.locker('foo')` if the session is interrupted, it's very likely that the `/_zklocking/foo` znode has been left behind. A method has been added to allow you to safely clean up these stale znodes:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
ZK.open('localhost:2181') do |zk|
|
11
|
+
ZK::Locker.cleanup(zk)
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
Will go through your locker nodes one by one and try to lock and unlock them. If it succeeds, the lock is naturally cleaned up (as part of the normal teardown code), if it doesn't acquire the lock, then no harm, it knows that lock is still in use.
|
16
|
+
|
17
|
+
* Added `create('/path', 'data', :or => :set)` which will create a node (and all parent paths) with the given data or set its contents if it already exists. It's intended as a convenience when you just want a node to exist with a particular value.
|
18
|
+
|
19
|
+
### v1.5.3 ###
|
20
|
+
|
21
|
+
* Fixed reconnect code. There was an occasional race/deadlock condition caused because the reopen call was done on the underlying connection's dispatch thread. Closing the dispatch thread is part of reopen, so this would cause a deadlock in real-world use. Moved the reconnect logic to a separate, single-purpose thread on ZK::Client::Threaded that watches for connection state changes.
|
22
|
+
|
23
|
+
* 'private' is not 'protected'. I've been writing ruby for several years now, and apparently I'd forgotten that 'protected' does not work like how it does in java. The visibility of these methods has been corrected, and all specs pass, so I don't expect issues...but please report if this change causes any bugs in user code.
|
24
|
+
|
25
|
+
|
3
26
|
### v1.5.2 ###
|
4
27
|
|
5
28
|
* Fix locker cleanup code to avoid a nasty race when a session is lost, see [issue #34](https://github.com/slyphon/zk/issues/34)
|
data/lib/zk/client/base.rb
CHANGED
@@ -128,10 +128,6 @@ module ZK
|
|
128
128
|
#
|
129
129
|
# @return [Symbol] state of connection after operation
|
130
130
|
def reopen(timeout=nil)
|
131
|
-
# timeout ||= @session_timeout # XXX: @session_timeout ?
|
132
|
-
# cnx.reopen(timeout)
|
133
|
-
# @threadpool.start!
|
134
|
-
# state
|
135
131
|
end
|
136
132
|
|
137
133
|
# close the underlying connection and clear all pending events.
|
@@ -202,6 +198,14 @@ module ZK
|
|
202
198
|
# @option opts [Object] :context (nil) an object passed to the `:callback`
|
203
199
|
# given as the `context` param
|
204
200
|
#
|
201
|
+
# @option opts [:create,nil] :or (nil) syntactic sugar to say 'if this
|
202
|
+
# path already exists, then set its contents.' Note that this will
|
203
|
+
# also create all intermediate paths as it delegates to
|
204
|
+
# {ZK::Client::Unixisms#mkdir_p}. Note that this option can only be
|
205
|
+
# used to create or set persistent, non-sequential paths. If an
|
206
|
+
# option is used to specify either, an ArgumentError will be raised.
|
207
|
+
# (note: not available for zk-eventmachine)
|
208
|
+
#
|
205
209
|
# @option opts [:ephemeral_sequential, :persistent_sequential, :persistent, :ephemeral] :mode (nil)
|
206
210
|
# may be specified instead of :ephemeral and :sequence options. If `:mode` *and* either of
|
207
211
|
# the `:ephermeral` or `:sequential` options are given, the `:mode` option will win
|
@@ -229,6 +233,14 @@ module ZK
|
|
229
233
|
# @option opts [Object] :context (nil) an object passed to the `:callback`
|
230
234
|
# given as the `context` param
|
231
235
|
#
|
236
|
+
# @option opts [:create,nil] :or (nil) syntactic sugar to say 'if this
|
237
|
+
# path already exists, then set its contents.' Note that this will
|
238
|
+
# also create all intermediate paths as it delegates to
|
239
|
+
# {ZK::Client::Unixisms#mkdir_p}. Note that this option can only be
|
240
|
+
# used to create or set persistent, non-sequential paths. If an
|
241
|
+
# option is used to specify either, an ArgumentError will be raised.
|
242
|
+
# (note: not available for zk-eventmachine)
|
243
|
+
#
|
232
244
|
# @option opts [:ephemeral_sequential, :persistent_sequential, :persistent, :ephemeral] :mode (nil)
|
233
245
|
# may be specified instead of :ephemeral and :sequence options. If `:mode` *and* either of
|
234
246
|
# the `:ephermeral` or `:sequential` options are given, the `:mode` option will win
|
@@ -287,7 +299,6 @@ module ZK
|
|
287
299
|
# zk.create("/path", '', :mode => :persistent_sequential)
|
288
300
|
# # => "/path0"
|
289
301
|
#
|
290
|
-
#
|
291
302
|
# @example create ephemeral and sequential node
|
292
303
|
#
|
293
304
|
# zk.create("/path", '', :sequence => true, :ephemeral => true)
|
@@ -337,6 +348,14 @@ module ZK
|
|
337
348
|
# zk.create("/path", "foo", :callback => callback, :context => context)
|
338
349
|
#
|
339
350
|
def create(path, *args)
|
351
|
+
h = parse_create_args(path, *args)
|
352
|
+
rv = call_and_check_rc(:create, h)
|
353
|
+
h[:callback] ? rv : rv[:path]
|
354
|
+
end
|
355
|
+
|
356
|
+
# parses the arguments and returns a hash for passing to
|
357
|
+
# call_and_check_rc. this is so subclasses can override easily
|
358
|
+
def parse_create_args(path, *args)
|
340
359
|
opts = args.extract_options!
|
341
360
|
|
342
361
|
# be somewhat strict about how many arguments we accept.
|
@@ -355,30 +374,29 @@ module ZK
|
|
355
374
|
|
356
375
|
data = args.first || ''
|
357
376
|
|
358
|
-
|
377
|
+
rval = { :path => path, :data => data, :ephemeral => false, :sequence => false }.merge(opts)
|
359
378
|
|
360
|
-
if mode =
|
379
|
+
if mode = rval.delete(:mode)
|
361
380
|
mode = mode.to_sym
|
362
381
|
|
363
382
|
case mode
|
364
383
|
when :ephemeral_sequential
|
365
|
-
|
384
|
+
rval[:ephemeral] = rval[:sequence] = true
|
366
385
|
when :persistent_sequential
|
367
|
-
|
368
|
-
|
386
|
+
rval[:ephemeral] = false
|
387
|
+
rval[:sequence] = true
|
369
388
|
when :persistent
|
370
|
-
|
389
|
+
rval[:ephemeral] = false
|
371
390
|
when :ephemeral
|
372
|
-
|
391
|
+
rval[:ephemeral] = true
|
373
392
|
else
|
374
393
|
raise ArgumentError, "Unknown mode: #{mode.inspect}"
|
375
394
|
end
|
376
395
|
end
|
377
396
|
|
378
|
-
|
379
|
-
|
380
|
-
h[:callback] ? rv : rv[:path]
|
397
|
+
rval
|
381
398
|
end
|
399
|
+
private :parse_create_args
|
382
400
|
|
383
401
|
# Return the data and stat of the node of the given path.
|
384
402
|
#
|
@@ -48,6 +48,14 @@ module ZK
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
# Register a block to be called when *any* connection event occurs
|
52
|
+
#
|
53
|
+
# @yield [event] yields the connection event to the block
|
54
|
+
# @yieldparam event [ZK::Event] the event that occurred
|
55
|
+
def on_state_change(&block)
|
56
|
+
watcher.register_state_handler(:all, &block)
|
57
|
+
end
|
58
|
+
|
51
59
|
# Register a block to be called on connection, when the client has
|
52
60
|
# connected.
|
53
61
|
#
|
data/lib/zk/client/threaded.rb
CHANGED
@@ -347,7 +347,7 @@ module ZK
|
|
347
347
|
on_tpool ? shutdown_thread : shutdown_thread.join(30)
|
348
348
|
end
|
349
349
|
|
350
|
-
# {see Base#close}
|
350
|
+
# {see ZK::Client::Base#close}
|
351
351
|
def close
|
352
352
|
super
|
353
353
|
subs, @fork_subs = @fork_subs, []
|
@@ -365,6 +365,33 @@ module ZK
|
|
365
365
|
@threadpool.on_exception(&blk)
|
366
366
|
end
|
367
367
|
|
368
|
+
def closed?
|
369
|
+
return true if @mutex.synchronize { @client_state == CLOSED }
|
370
|
+
super
|
371
|
+
end
|
372
|
+
|
373
|
+
# this is where the :on option is implemented for {Base#create}
|
374
|
+
def create(path, *args)
|
375
|
+
opts = args.extract_options!
|
376
|
+
|
377
|
+
or_opt = opts.delete(:or)
|
378
|
+
args << opts
|
379
|
+
|
380
|
+
if or_opt
|
381
|
+
hash = parse_create_args(path, *args)
|
382
|
+
|
383
|
+
raise ArgumentError, "valid options for :or are nil or :set, not #{or_opt.inspect}" unless or_opt == :set
|
384
|
+
raise ArgumentError, "you cannot create an ephemeral node when using the :or option" if hash[:ephemeral]
|
385
|
+
raise ArgumentError, "you cannot create an sequence node when using the :or option" if hash[:sequence]
|
386
|
+
|
387
|
+
mkdir_p(path, :data => hash[:data])
|
388
|
+
path
|
389
|
+
else
|
390
|
+
# ok, none of our business, hand it up to mangement
|
391
|
+
super(path, *args)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
368
395
|
# @private
|
369
396
|
def raw_event_handler(event)
|
370
397
|
return unless event.session_event?
|
@@ -378,43 +405,47 @@ module ZK
|
|
378
405
|
logger.error { "BUG: Exception caught in raw_event_handler: #{e.to_std_format}" }
|
379
406
|
end
|
380
407
|
|
381
|
-
def closed?
|
382
|
-
return true if @mutex.synchronize { @client_state == CLOSED }
|
383
|
-
super
|
384
|
-
end
|
385
|
-
|
386
|
-
# are we in running (not-paused) state?
|
387
|
-
# @private
|
388
|
-
def running?
|
389
|
-
@mutex.synchronize { @client_state == RUNNING }
|
390
|
-
end
|
391
|
-
|
392
|
-
# are we in paused state?
|
393
|
-
# @private
|
394
|
-
def paused?
|
395
|
-
@mutex.synchronize { @client_state == PAUSED }
|
396
|
-
end
|
397
|
-
|
398
|
-
# has shutdown time arrived?
|
399
|
-
# @private
|
400
|
-
def close_requested?
|
401
|
-
@mutex.synchronize { @client_state == CLOSE_REQ }
|
402
|
-
end
|
403
|
-
|
404
408
|
# @private
|
405
409
|
def wait_until_connected_or_dying(timeout)
|
406
410
|
time_to_stop = Time.now + timeout
|
407
411
|
|
408
412
|
@mutex.synchronize do
|
409
|
-
while
|
410
|
-
|
413
|
+
while true
|
414
|
+
now = Time.now
|
415
|
+
break if (@last_cnx_state == Zookeeper::ZOO_CONNECTED_STATE) || (now > time_to_stop) || (@client_state != RUNNING)
|
416
|
+
deadline = time_to_stop.to_f - now.to_f
|
417
|
+
@cond.wait(deadline)
|
411
418
|
end
|
412
419
|
|
413
|
-
logger.debug { "@last_cnx_state: #{@last_cnx_state.inspect}, time_left? #{Time.now.to_f < time_to_stop.to_f}, @client_state: #{@client_state.inspect}" }
|
420
|
+
logger.debug { "#{__method__} @last_cnx_state: #{@last_cnx_state.inspect}, time_left? #{Time.now.to_f < time_to_stop.to_f}, @client_state: #{@client_state.inspect}" }
|
414
421
|
end
|
415
422
|
end
|
416
423
|
|
424
|
+
# @private
|
425
|
+
def client_state
|
426
|
+
@mutex.synchronize { @client_state }
|
427
|
+
end
|
428
|
+
|
417
429
|
private
|
430
|
+
# are we in running (not-paused) state?
|
431
|
+
def running?
|
432
|
+
@client_state == RUNNING
|
433
|
+
end
|
434
|
+
|
435
|
+
# are we in paused state?
|
436
|
+
def paused?
|
437
|
+
@client_state == PAUSED
|
438
|
+
end
|
439
|
+
|
440
|
+
# has shutdown time arrived?
|
441
|
+
def close_requested?
|
442
|
+
@client_state == CLOSE_REQ
|
443
|
+
end
|
444
|
+
|
445
|
+
def dead_or_dying?
|
446
|
+
(@client_state == CLOSE_REQ) || (@client_state == CLOSED)
|
447
|
+
end
|
448
|
+
|
418
449
|
# this is just here so we can see it in stack traces
|
419
450
|
def reopen_after_session_expired
|
420
451
|
reopen
|
@@ -433,11 +464,11 @@ module ZK
|
|
433
464
|
@mutex.synchronize do
|
434
465
|
# either we havne't seen a valid session update from this
|
435
466
|
# connection yet, or we're doing fine, so just wait
|
436
|
-
@cond.wait_while { !seen_session_state_event? or (valid_session_state? and
|
467
|
+
@cond.wait_while { !seen_session_state_event? or (valid_session_state? and running?) }
|
437
468
|
|
438
469
|
# we've entered into a non-running state, so we exit
|
439
470
|
# note: need to restart this thread after a fork in parent
|
440
|
-
|
471
|
+
unless running?
|
441
472
|
logger.debug { "session failure watcher thread exiting, @client_state: #{@client_state}" }
|
442
473
|
return
|
443
474
|
end
|
@@ -457,8 +488,6 @@ module ZK
|
|
457
488
|
end
|
458
489
|
end
|
459
490
|
end
|
460
|
-
ensure
|
461
|
-
logger.debug { "reconnect thread exiting" }
|
462
491
|
end
|
463
492
|
|
464
493
|
def join_and_clear_reconnect_thread
|
@@ -488,7 +517,7 @@ module ZK
|
|
488
517
|
|
489
518
|
wait_until_connected_or_dying(retry_duration)
|
490
519
|
|
491
|
-
if (@last_cnx_state != Zookeeper::ZOO_CONNECTED_STATE) || (Time.now > time_to_stop) ||
|
520
|
+
if (@last_cnx_state != Zookeeper::ZOO_CONNECTED_STATE) || (Time.now > time_to_stop) || !running?
|
492
521
|
raise e
|
493
522
|
else
|
494
523
|
retry
|
@@ -519,10 +548,6 @@ module ZK
|
|
519
548
|
::Zookeeper.new(*args)
|
520
549
|
end
|
521
550
|
|
522
|
-
def dead_or_dying?
|
523
|
-
(@client_state == CLOSE_REQ) || (@client_state == CLOSED)
|
524
|
-
end
|
525
|
-
|
526
551
|
def unlocked_connect(opts={})
|
527
552
|
return if @cnx
|
528
553
|
timeout = opts.fetch(:timeout, @connection_timeout)
|
data/lib/zk/client/unixisms.rb
CHANGED
@@ -8,6 +8,8 @@ module ZK
|
|
8
8
|
# zero data.
|
9
9
|
#
|
10
10
|
# @param [String] path An absolute znode path to create
|
11
|
+
#
|
12
|
+
# @option opts [String] :data ('') The data to place at path
|
11
13
|
#
|
12
14
|
# @example
|
13
15
|
#
|
@@ -17,12 +19,20 @@ module ZK
|
|
17
19
|
# zk.mkdir_p('/path/to/blah')
|
18
20
|
# # => "/path/to/blah"
|
19
21
|
#
|
20
|
-
def mkdir_p(path)
|
21
|
-
|
22
|
-
|
22
|
+
def mkdir_p(path, opts={})
|
23
|
+
data = ''
|
24
|
+
|
25
|
+
# if we haven't recursed, or we recursed and now we're back at the top
|
26
|
+
if !opts.has_key?(:orig_path) or (path == opts[:orig_path])
|
27
|
+
data = opts.fetch(:data, '') # only put the data at the leaf node
|
28
|
+
end
|
23
29
|
|
24
|
-
create(path,
|
30
|
+
create(path, data, :mode => :persistent)
|
25
31
|
rescue NodeExists
|
32
|
+
if !opts.has_key?(:orig_path) or (path == opts[:orig_path]) # we're at the leaf node
|
33
|
+
set(path, data)
|
34
|
+
end
|
35
|
+
|
26
36
|
return
|
27
37
|
rescue NoNode
|
28
38
|
if File.dirname(path) == '/'
|
@@ -30,7 +40,9 @@ module ZK
|
|
30
40
|
raise NonExistentRootError, "could not create '/', are you chrooted into a non-existent path?", caller
|
31
41
|
end
|
32
42
|
|
33
|
-
|
43
|
+
opts[:orig_path] ||= path
|
44
|
+
|
45
|
+
mkdir_p(File.dirname(path), opts)
|
34
46
|
retry
|
35
47
|
end
|
36
48
|
|
data/lib/zk/event.rb
CHANGED
@@ -102,21 +102,25 @@ module ZK
|
|
102
102
|
def node_created?
|
103
103
|
@type == ZOO_CREATED_EVENT
|
104
104
|
end
|
105
|
+
alias created? node_created?
|
105
106
|
|
106
107
|
# Has a node been deleted?
|
107
108
|
def node_deleted?
|
108
109
|
@type == ZOO_DELETED_EVENT
|
109
110
|
end
|
111
|
+
alias deleted? node_deleted?
|
110
112
|
|
111
113
|
# Has a node changed?
|
112
114
|
def node_changed?
|
113
115
|
@type == ZOO_CHANGED_EVENT
|
114
116
|
end
|
117
|
+
alias changed? node_changed?
|
115
118
|
|
116
119
|
# Has a node's list of children changed?
|
117
120
|
def node_child?
|
118
121
|
@type == ZOO_CHILD_EVENT
|
119
122
|
end
|
123
|
+
alias child? node_child?
|
120
124
|
|
121
125
|
# Is this a session-related event?
|
122
126
|
#
|
@@ -148,12 +152,14 @@ module ZK
|
|
148
152
|
@type == ZOO_SESSION_EVENT
|
149
153
|
end
|
150
154
|
alias state_event? session_event?
|
155
|
+
alias session? session_event?
|
151
156
|
|
152
157
|
# has this watcher been called because of a change to a zookeeper node?
|
153
158
|
# `node_event?` and `session_event?` are mutually exclusive.
|
154
159
|
def node_event?
|
155
160
|
path and not path.empty?
|
156
161
|
end
|
162
|
+
alias node? node_event?
|
157
163
|
|
158
164
|
# according to [the programmer's guide](http://zookeeper.apache.org/doc/r3.3.4/zookeeperProgrammers.html#Java+Binding)
|
159
165
|
#
|
data/lib/zk/event_handler.rb
CHANGED
@@ -14,6 +14,9 @@ module ZK
|
|
14
14
|
# @private
|
15
15
|
ALL_NODE_EVENTS_KEY = :all_node_events
|
16
16
|
|
17
|
+
# @private
|
18
|
+
ALL_STATE_EVENTS_KEY = :all_state_events
|
19
|
+
|
17
20
|
# @private
|
18
21
|
ZOOKEEPER_WATCH_TYPE_MAP = {
|
19
22
|
Zookeeper::ZOO_CREATED_EVENT => :data,
|
@@ -100,7 +103,13 @@ module ZK
|
|
100
103
|
# or when there's a temporary loss in connection and Zookeeper recommends
|
101
104
|
# you go into 'safe mode'.
|
102
105
|
#
|
103
|
-
#
|
106
|
+
# Note that these callbacks are *not* one-shot like the path callbacks,
|
107
|
+
# these will be called back with every relative state event, there is
|
108
|
+
# no need to re-register
|
109
|
+
#
|
110
|
+
# @param [String,:all] state The state you want to register for or :all
|
111
|
+
# to be called back with every state change
|
112
|
+
#
|
104
113
|
# @param [Block] block the block to execute on state changes
|
105
114
|
# @yield [event] yields your block with
|
106
115
|
#
|
@@ -156,8 +165,8 @@ module ZK
|
|
156
165
|
cb_keys =
|
157
166
|
if event.node_event?
|
158
167
|
[event.path, ALL_NODE_EVENTS_KEY]
|
159
|
-
elsif event.
|
160
|
-
[state_key(event.state)]
|
168
|
+
elsif event.session_event?
|
169
|
+
[state_key(event.state), ALL_STATE_EVENTS_KEY]
|
161
170
|
else
|
162
171
|
raise ZKError, "don't know how to process event: #{event.inspect}"
|
163
172
|
end
|
@@ -180,22 +189,21 @@ module ZK
|
|
180
189
|
safe_call(cb_ary, event)
|
181
190
|
end
|
182
191
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
@outstanding_watches[watch_type].delete(event.path)
|
195
|
-
end
|
192
|
+
# happens inside the lock, clears the restriction on setting new watches
|
193
|
+
# for a given path/event type combination
|
194
|
+
#
|
195
|
+
def clear_watch_restrictions(event)
|
196
|
+
return unless event.node_event?
|
197
|
+
|
198
|
+
if watch_type = ZOOKEEPER_WATCH_TYPE_MAP[event.type]
|
199
|
+
#logger.debug { "re-allowing #{watch_type.inspect} watches on path #{event.path.inspect}" }
|
200
|
+
|
201
|
+
# we recieved a watch event for this path, now we allow code to set new watchers
|
202
|
+
@outstanding_watches[watch_type].delete(event.path)
|
196
203
|
end
|
204
|
+
end
|
205
|
+
private :clear_watch_restrictions
|
197
206
|
|
198
|
-
public
|
199
207
|
# used during shutdown to clear registered listeners
|
200
208
|
# @private
|
201
209
|
def clear! #:nodoc:
|
@@ -237,11 +245,6 @@ module ZK
|
|
237
245
|
@callbacks.values.flatten.each(&:resume_after_fork_in_parent)
|
238
246
|
end
|
239
247
|
|
240
|
-
# @private
|
241
|
-
def synchronize
|
242
|
-
@mutex.synchronize { yield }
|
243
|
-
end
|
244
|
-
|
245
248
|
# @private
|
246
249
|
def get_default_watcher_block
|
247
250
|
@default_watcher_block ||= lambda do |hash|
|
@@ -303,6 +306,10 @@ module ZK
|
|
303
306
|
end
|
304
307
|
|
305
308
|
private
|
309
|
+
def synchronize
|
310
|
+
@mutex.synchronize { yield }
|
311
|
+
end
|
312
|
+
|
306
313
|
def watcher_callback
|
307
314
|
Zookeeper::Callbacks::WatcherCallback.create { |event| process(event) }
|
308
315
|
end
|
@@ -310,12 +317,15 @@ module ZK
|
|
310
317
|
def state_key(arg)
|
311
318
|
int =
|
312
319
|
case arg
|
320
|
+
when :all
|
321
|
+
# XXX: this is a nasty side-exit
|
322
|
+
return ALL_STATE_EVENTS_KEY
|
313
323
|
when String, Symbol
|
314
324
|
Zookeeper::Constants.const_get(:"ZOO_#{arg.to_s.upcase}_STATE")
|
315
325
|
when Integer
|
316
326
|
arg
|
317
327
|
else
|
318
|
-
raise NameError # ugh lame
|
328
|
+
raise NameError, "unrecognized state: #{arg.inspect}" # ugh lame
|
319
329
|
end
|
320
330
|
|
321
331
|
"state_#{int}"
|
data/lib/zk/locker.rb
CHANGED
@@ -99,24 +99,55 @@ module ZK
|
|
99
99
|
# the default root path we will use when a value is not given to a
|
100
100
|
# constructor
|
101
101
|
attr_accessor :default_root_lock_node
|
102
|
-
end
|
103
102
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
103
|
+
# Create a {SharedLocker} instance
|
104
|
+
#
|
105
|
+
# @param client (see LockerBase#initialize)
|
106
|
+
# @param name (see LockerBase#initialize)
|
107
|
+
# @return [SharedLocker]
|
108
|
+
def shared_locker(client, name, *args)
|
109
|
+
SharedLocker.new(client, name, *args)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Create an {ExclusiveLocker} instance
|
113
|
+
#
|
114
|
+
# @param client (see LockerBase#initialize)
|
115
|
+
# @param name (see LockerBase#initialize)
|
116
|
+
# @return [ExclusiveLocker]
|
117
|
+
def exclusive_locker(client, name, *args)
|
118
|
+
ExclusiveLocker.new(client, name, *args)
|
119
|
+
end
|
112
120
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
121
|
+
# Clean up dead locker directories. There are situations (particularly
|
122
|
+
# session expiration) where a lock's directory will never be cleaned up.
|
123
|
+
#
|
124
|
+
# It is intened to be run periodically (perhaps from cron).
|
125
|
+
#
|
126
|
+
#
|
127
|
+
# This implementation goes through each lock directory and attempts to
|
128
|
+
# acquire an exclusive lock. If the lock is acquired then when it unlocks
|
129
|
+
# it will remove the locker directory. This is safe because the unlock
|
130
|
+
# code is designed to deal with the inherent race conditions.
|
131
|
+
#
|
132
|
+
# @example
|
133
|
+
#
|
134
|
+
# ZK.open do |zk|
|
135
|
+
# ZK::Locker.cleanup!(zk)
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# @param client [ZK::Client::Threaded] the client connection to use
|
139
|
+
#
|
140
|
+
# @param root_lock_node [String] if given, use an alternate root lock node to base
|
141
|
+
# each Locker's path on. You probably don't need to touch this. Uses
|
142
|
+
# {Locker.default_root_lock_node} by default (if value is nil)
|
143
|
+
#
|
144
|
+
def cleanup(client, root_lock_node=default_root_lock_node)
|
145
|
+
client.children(root_lock_node).each do |name|
|
146
|
+
exclusive_locker(client, name, root_lock_node).tap do |locker|
|
147
|
+
locker.unlock if locker.lock
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
120
151
|
end
|
121
152
|
|
122
153
|
# @private
|
@@ -75,16 +75,23 @@ module ZK
|
|
75
75
|
path = "#{root_lock_path}/#{next_lowest_node}"
|
76
76
|
logger.debug { "#{self.class}##{__method__} path=#{path.inspect}" }
|
77
77
|
|
78
|
-
synchronize do
|
78
|
+
@mutex.synchronize do
|
79
|
+
logger.debug { "assigning the @node_deletion_watcher" }
|
79
80
|
@node_deletion_watcher = NodeDeletionWatcher.new(zk, path)
|
81
|
+
logger.debug { "broadcasting" }
|
80
82
|
@cond.broadcast
|
81
83
|
end
|
82
84
|
|
85
|
+
logger.debug { "calling block_until_deleted" }
|
86
|
+
Thread.pass
|
87
|
+
|
83
88
|
@node_deletion_watcher.block_until_deleted
|
84
89
|
rescue WeAreTheLowestLockNumberException
|
90
|
+
ensure
|
91
|
+
logger.debug { "block_until_deleted returned" }
|
85
92
|
end
|
86
93
|
|
87
|
-
synchronize { @locked = true }
|
94
|
+
@mutex.synchronize { @locked = true }
|
88
95
|
end
|
89
96
|
end # ExclusiveLocker
|
90
97
|
end # Locker
|
@@ -129,11 +129,13 @@ module ZK
|
|
129
129
|
#
|
130
130
|
def unlock
|
131
131
|
rval = false
|
132
|
-
synchronize do
|
132
|
+
@mutex.synchronize do
|
133
133
|
if @locked
|
134
|
+
logger.debug { "unlocking" }
|
134
135
|
rval = cleanup_lock_path!
|
135
136
|
@locked = false
|
136
137
|
@node_deletion_watcher = nil
|
138
|
+
@cond.broadcast
|
137
139
|
end
|
138
140
|
end
|
139
141
|
rval
|
@@ -176,7 +178,7 @@ module ZK
|
|
176
178
|
#
|
177
179
|
# @private
|
178
180
|
def waiting?
|
179
|
-
synchronize do
|
181
|
+
@mutex.synchronize do
|
180
182
|
!!(@node_deletion_watcher and @node_deletion_watcher.blocked?)
|
181
183
|
end
|
182
184
|
end
|
@@ -184,9 +186,19 @@ module ZK
|
|
184
186
|
# blocks the caller until this lock is blocked
|
185
187
|
# @private
|
186
188
|
def wait_until_blocked(timeout=nil)
|
187
|
-
|
188
|
-
|
189
|
+
time_to_stop = timeout ? (Time.now + timeout) : nil
|
190
|
+
|
191
|
+
@mutex.synchronize do
|
192
|
+
if @node_deletion_watcher
|
193
|
+
logger.debug { "@node_deletion_watcher already assigned, not waiting" }
|
194
|
+
else
|
195
|
+
logger.debug { "going to wait up to #{timeout} sec for a @node_deletion_watcher to be assigned" }
|
196
|
+
|
197
|
+
@cond.wait(timeout)
|
198
|
+
raise "Timeout waiting for @node_deletion_watcher" unless @node_deletion_watcher
|
199
|
+
end
|
189
200
|
end
|
201
|
+
logger.debug { "ok, @node_deletion_watcher: #{@node_deletion_watcher}, going to call wait_until_blocked" }
|
190
202
|
|
191
203
|
@node_deletion_watcher.wait_until_blocked(timeout)
|
192
204
|
end
|
@@ -220,7 +232,7 @@ module ZK
|
|
220
232
|
# end
|
221
233
|
#
|
222
234
|
def assert!
|
223
|
-
synchronize do
|
235
|
+
@mutex.synchronize do
|
224
236
|
raise LockAssertionFailedError, "have not obtained the lock yet" unless locked?
|
225
237
|
raise LockAssertionFailedError, "not connected" unless zk.connected?
|
226
238
|
raise LockAssertionFailedError, "lock_path was #{lock_path.inspect}" unless lock_path
|
@@ -239,11 +251,6 @@ module ZK
|
|
239
251
|
self.class.digit_from_lock_path(path)
|
240
252
|
end
|
241
253
|
|
242
|
-
# possibly lighter weight check to see if the lock path has any children
|
243
|
-
# (using stat, rather than getting the list of children).
|
244
|
-
def any_lock_children?
|
245
|
-
end
|
246
|
-
|
247
254
|
def lock_children(watch=false)
|
248
255
|
zk.children(root_lock_path, :watch => watch)
|
249
256
|
end
|
@@ -275,7 +282,7 @@ module ZK
|
|
275
282
|
# [rule #34](https://github.com/slyphon/zk/issues/34)...er, *issue* #34.
|
276
283
|
#
|
277
284
|
def create_lock_path!(prefix='lock')
|
278
|
-
synchronize do
|
285
|
+
@mutex.synchronize do
|
279
286
|
@lock_path = @zk.create("#{root_lock_path}/#{prefix}", :mode => :ephemeral_sequential)
|
280
287
|
@parent_stat = @zk.stat(root_lock_path)
|
281
288
|
end
|
@@ -294,7 +301,7 @@ module ZK
|
|
294
301
|
# see [issue #34](https://github.com/slyphon/zk/issues/34)
|
295
302
|
#
|
296
303
|
def root_lock_path_same?
|
297
|
-
synchronize do
|
304
|
+
@mutex.synchronize do
|
298
305
|
return false unless @parent_stat
|
299
306
|
|
300
307
|
cur_stat = zk.stat(root_lock_path)
|
@@ -311,7 +318,7 @@ module ZK
|
|
311
318
|
def cleanup_lock_path!
|
312
319
|
rval = false
|
313
320
|
|
314
|
-
synchronize do
|
321
|
+
@mutex.synchronize do
|
315
322
|
if root_lock_path_same?
|
316
323
|
logger.debug { "removing lock path #{@lock_path}" }
|
317
324
|
|
@@ -24,7 +24,7 @@ module ZK
|
|
24
24
|
@mutex = Monitor.new # ffs, 1.8.7 compatibility w/ timeouts
|
25
25
|
@cond = @mutex.new_cond
|
26
26
|
|
27
|
-
@blocked =
|
27
|
+
@blocked = NOT_YET
|
28
28
|
@result = nil
|
29
29
|
end
|
30
30
|
|
@@ -55,6 +55,7 @@ module ZK
|
|
55
55
|
start = Time.now
|
56
56
|
time_to_stop = timeout ? (start + timeout) : nil
|
57
57
|
|
58
|
+
logger.debug { "#{__method__} @blocked: #{@blocked.inspect} about to wait" }
|
58
59
|
@cond.wait(timeout)
|
59
60
|
|
60
61
|
if (time_to_stop and (Time.now > time_to_stop)) and (@blocked == NOT_YET)
|
@@ -100,27 +101,25 @@ module ZK
|
|
100
101
|
|
101
102
|
logger.debug { "ok, going to block: #{path}" }
|
102
103
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
@blocked = NOT_ANYMORE
|
104
|
+
@blocked = BLOCKED
|
105
|
+
@cond.broadcast # wake threads waiting for @blocked to change
|
106
|
+
@cond.wait_until { @result } # wait until we get a result
|
107
|
+
@blocked = NOT_ANYMORE
|
108
108
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
end
|
109
|
+
case @result
|
110
|
+
when :deleted
|
111
|
+
logger.debug { "path #{path} was deleted" }
|
112
|
+
return true
|
113
|
+
when INTERRUPTED
|
114
|
+
raise ZK::Exceptions::WakeUpException
|
115
|
+
when ZOO_EXPIRED_SESSION_STATE
|
116
|
+
raise Zookeeper::Exceptions::SessionExpired
|
117
|
+
when ZOO_CONNECTING_STATE
|
118
|
+
raise Zookeeper::Exceptions::NotConnected
|
119
|
+
when ZOO_CLOSED_STATE
|
120
|
+
raise Zookeeper::Exceptions::ConnectionClosed
|
121
|
+
else
|
122
|
+
raise "Hit unexpected case in block_until_node_deleted, result was: #{@result.inspect}"
|
124
123
|
end
|
125
124
|
end
|
126
125
|
ensure
|
data/lib/zk/subscription.rb
CHANGED
@@ -18,6 +18,7 @@ module ZK
|
|
18
18
|
|
19
19
|
def initialize(parent, block)
|
20
20
|
raise ArgumentError, "block must repsond_to?(:call)" unless block.respond_to?(:call)
|
21
|
+
raise ArgumentError, "parent must respond_to?(:unregister)" unless parent.respond_to?(:unregister)
|
21
22
|
@parent = parent
|
22
23
|
@callable = block
|
23
24
|
@mutex = Monitor.new
|
data/lib/zk/version.rb
CHANGED
@@ -9,7 +9,6 @@ shared_context 'threaded client connection' do
|
|
9
9
|
|
10
10
|
before do
|
11
11
|
# logger.debug { "threaded client connection - begin before hook" }
|
12
|
-
|
13
12
|
@connection_string = connection_host
|
14
13
|
@base_path = '/zktests'
|
15
14
|
@zk = ZK::Client::Threaded.new(*connection_args).tap { |z| wait_until { z.connected? } }
|
@@ -17,6 +16,9 @@ shared_context 'threaded client connection' do
|
|
17
16
|
@zk.on_exception { |e| @threadpool_exception = e }
|
18
17
|
@zk.rm_rf(@base_path)
|
19
18
|
|
19
|
+
@orig_default_root_lock_node = ZK::Locker.default_root_lock_node
|
20
|
+
ZK::Locker.default_root_lock_node = "#{@base_path}/_zklocking"
|
21
|
+
|
20
22
|
# logger.debug { "threaded client connection - end before hook" }
|
21
23
|
end
|
22
24
|
|
@@ -30,6 +32,8 @@ shared_context 'threaded client connection' do
|
|
30
32
|
z.rm_rf(@base_path)
|
31
33
|
end
|
32
34
|
|
35
|
+
ZK::Locker.default_root_lock_node = @orig_default_root_lock_node
|
36
|
+
|
33
37
|
# logger.debug { "threaded client connection - end after hook" }
|
34
38
|
end
|
35
39
|
end
|
@@ -1,18 +1,35 @@
|
|
1
1
|
shared_examples_for 'client' do
|
2
2
|
describe :mkdir_p do
|
3
|
-
before
|
4
|
-
|
5
|
-
|
6
|
-
@
|
3
|
+
before do
|
4
|
+
base = @base_path.sub(%r_^/_, '')
|
5
|
+
|
6
|
+
@path_ary = %W[#{base} test mkdir_p path creation]
|
7
|
+
@bogus_path = File.join('', *@path_ary)
|
7
8
|
end
|
8
|
-
|
9
|
+
|
9
10
|
it %[should create all intermediate paths for the path givem] do
|
10
|
-
@zk.rm_rf('/test')
|
11
11
|
@zk.should_not be_exists(@bogus_path)
|
12
12
|
@zk.should_not be_exists(File.dirname(@bogus_path))
|
13
13
|
@zk.mkdir_p(@bogus_path)
|
14
14
|
@zk.should be_exists(@bogus_path)
|
15
15
|
end
|
16
|
+
|
17
|
+
it %[should place the data only at the leaf node] do
|
18
|
+
@zk.mkdir_p(@bogus_path, :data => 'foobar')
|
19
|
+
@zk.get(@bogus_path).first.should == 'foobar'
|
20
|
+
|
21
|
+
path = ''
|
22
|
+
@path_ary[0..-2].each do |el|
|
23
|
+
path = File.join(path, el)
|
24
|
+
@zk.get(path).first.should == ''
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it %[should replace the data at the leaf node if it already exists] do
|
29
|
+
@zk.mkdir_p(@bogus_path, :data => 'blahfoo')
|
30
|
+
@zk.mkdir_p(@bogus_path, :data => 'foodink')
|
31
|
+
@zk.get(@bogus_path).first.should == 'foodink'
|
32
|
+
end
|
16
33
|
end
|
17
34
|
|
18
35
|
# nail down all possible cases
|
@@ -114,6 +131,45 @@ shared_examples_for 'client' do
|
|
114
131
|
proc { @zk.create("#{@base_path}/foo/bar/baz", :ignore => :no_node).should be_nil }.should_not raise_error(ZK::Exceptions::NoNode)
|
115
132
|
end
|
116
133
|
end
|
134
|
+
|
135
|
+
describe %[:or option] do
|
136
|
+
let(:path) { "#{@base_path}/foo/bar" }
|
137
|
+
|
138
|
+
it %[should barf if anything but the the :set value is given] do
|
139
|
+
proc { @zk.create(path, :or => :GFY) }.should raise_error(ArgumentError)
|
140
|
+
end
|
141
|
+
|
142
|
+
def create_args(opts={})
|
143
|
+
proc do
|
144
|
+
begin
|
145
|
+
@zk.create(path, opts.merge(:or => :set))
|
146
|
+
ensure
|
147
|
+
@zk.rm_rf(path)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it %[should barf if any node option besides 'persistent' is given] do
|
153
|
+
create_args(:persistent => true).should_not raise_error
|
154
|
+
create_args(:sequential => true).should raise_error(ArgumentError)
|
155
|
+
create_args(:mode => :ephemeral).should raise_error(ArgumentError)
|
156
|
+
create_args(:mode => :ephemeral_sequential).should raise_error(ArgumentError)
|
157
|
+
create_args(:mode => :sequential).should raise_error(ArgumentError)
|
158
|
+
end
|
159
|
+
|
160
|
+
it %[should replace the data at the leaf node if it already exists] do
|
161
|
+
@zk.mkdir_p(path, :data => 'foodink')
|
162
|
+
@zk.create(path, 'blahfoo', :or => :set)
|
163
|
+
@zk.get(path).first.should == 'blahfoo'
|
164
|
+
end
|
165
|
+
|
166
|
+
it %[should create the intermediate paths] do
|
167
|
+
proc { @zk.create(path, 'foobar', :or => :set) }.should_not raise_error
|
168
|
+
|
169
|
+
@zk.stat(@base_path).should exist
|
170
|
+
@zk.stat("#{@base_path}/foo").should exist
|
171
|
+
end
|
172
|
+
end
|
117
173
|
end
|
118
174
|
|
119
175
|
describe :delete do
|
@@ -298,20 +354,54 @@ shared_examples_for 'client' do
|
|
298
354
|
|
299
355
|
describe 'reconnection' do
|
300
356
|
it %[should if it receives a client_invalid? event] do
|
301
|
-
|
357
|
+
# note: we can't trust the events to be delivered in any particular order
|
358
|
+
# since they're happening on two different threads. if we see we're connected
|
359
|
+
# in the beginning, that there was a disconnection, then a reopen, that's
|
360
|
+
# probably fine.
|
361
|
+
#
|
362
|
+
# we also check that the session_id was changed, which is the desired effect
|
363
|
+
|
364
|
+
orig_session_id = @zk.session_id
|
365
|
+
@zk.should be_connected
|
302
366
|
|
303
|
-
props = {
|
367
|
+
props = {
|
368
|
+
:session_event? => true,
|
369
|
+
:node_event? => false,
|
370
|
+
:client_invalid? => true,
|
371
|
+
:state_name => 'ZOO_EXPIRED_SESSION_STATE',
|
372
|
+
:state => Zookeeper::ZOO_EXPIRED_SESSION_STATE,
|
373
|
+
}
|
304
374
|
|
305
375
|
bogus_event = flexmock(:expired_session_event, props)
|
376
|
+
bogus_event.should_receive(:zk=).with(@zk).once
|
377
|
+
|
378
|
+
mutex = Monitor.new
|
379
|
+
cond = mutex.new_cond
|
380
|
+
events = []
|
306
381
|
|
307
|
-
|
308
|
-
|
382
|
+
@sub = @zk.on_state_change do |event|
|
383
|
+
mutex.synchronize do
|
384
|
+
logger.debug { "event: #{event.inspect}" }
|
385
|
+
events << event.state
|
386
|
+
cond.broadcast
|
387
|
+
end
|
309
388
|
end
|
310
389
|
|
311
|
-
|
312
|
-
|
390
|
+
mutex.synchronize do
|
391
|
+
events.should be_empty
|
392
|
+
@zk.event_handler.process(bogus_event)
|
393
|
+
end
|
394
|
+
|
395
|
+
logger.debug { "events: #{events.inspect}" }
|
396
|
+
|
397
|
+
mutex.synchronize do
|
398
|
+
time_to_stop = Time.now + 2
|
399
|
+
cond.wait_while { (events.length < 2 ) && (Time.now < time_to_stop) }
|
400
|
+
end
|
313
401
|
|
314
|
-
|
402
|
+
events.should include(Zookeeper::ZOO_EXPIRED_SESSION_STATE)
|
403
|
+
events.should include(Zookeeper::ZOO_CONNECTED_STATE)
|
404
|
+
@zk.session_id.should_not == orig_session_id
|
315
405
|
end
|
316
406
|
end # reconnection
|
317
407
|
|
data/spec/support/logging.rb
CHANGED
@@ -2,10 +2,13 @@ module ZK
|
|
2
2
|
TEST_LOG_PATH = File.join(ZK::ZK_ROOT, 'test.log')
|
3
3
|
|
4
4
|
def self.logging_gem_setup
|
5
|
-
|
5
|
+
layout_opts = {
|
6
6
|
:pattern => '%.1l, [%d #%p] (%9.9T) %25.25c{2}: %m\n',
|
7
|
-
|
8
|
-
|
7
|
+
}
|
8
|
+
|
9
|
+
layout_opts[:date_pattern] = ZK.jruby? ? '%H:%M:%S.%3N' : '%H:%M:%S.%6N'
|
10
|
+
|
11
|
+
layout = ::Logging.layouts.pattern(layout_opts)
|
9
12
|
|
10
13
|
appender = ENV['ZK_DEBUG'] ? ::Logging.appenders.stderr : ::Logging.appenders.file(ZK::TEST_LOG_PATH)
|
11
14
|
appender.layout = layout
|
@@ -20,8 +20,11 @@ shared_examples_for 'ZK::Locker::ExclusiveLocker' do
|
|
20
20
|
locker2.lock(true)
|
21
21
|
end
|
22
22
|
|
23
|
+
th.run
|
24
|
+
|
23
25
|
logger.debug { "calling wait_until_blocked" }
|
24
|
-
locker2.wait_until_blocked(2)
|
26
|
+
proc { locker2.wait_until_blocked(2) }.should_not raise_error
|
27
|
+
logger.debug { "wait_until_blocked returned" }
|
25
28
|
locker2.should be_waiting
|
26
29
|
|
27
30
|
wait_until { zk.exists?(locker2.lock_path) }
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ZK::Locker do
|
4
|
+
include_context 'threaded client connection'
|
5
|
+
|
6
|
+
describe :cleanup do
|
7
|
+
it %[should remove dead lock directories] do
|
8
|
+
locker = @zk.locker('legit')
|
9
|
+
locker.lock
|
10
|
+
locker.assert!
|
11
|
+
|
12
|
+
bogus_lock_dir_names = %w[a b c d e f g]
|
13
|
+
bogus_lock_dir_names.each { |n| @zk.create("#{ZK::Locker.default_root_lock_node}/#{n}") }
|
14
|
+
|
15
|
+
ZK::Locker.cleanup(@zk)
|
16
|
+
|
17
|
+
lambda { locker.assert! }.should_not raise_error
|
18
|
+
|
19
|
+
bogus_lock_dir_names.each { |n| @zk.stat("#{ZK::Locker.default_root_lock_node}/#{n}").should_not exist }
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
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: 15
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 1.
|
8
|
+
- 6
|
9
|
+
- 0
|
10
|
+
version: 1.6.0
|
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-29 00:00:00 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: zookeeper
|
@@ -163,6 +163,7 @@ files:
|
|
163
163
|
- spec/zk/locker/locker_basic_spec.rb
|
164
164
|
- spec/zk/locker/shared_exclusive_integration_spec.rb
|
165
165
|
- spec/zk/locker/shared_locker_spec.rb
|
166
|
+
- spec/zk/locker_spec.rb
|
166
167
|
- spec/zk/module_spec.rb
|
167
168
|
- spec/zk/mongoid_spec.rb
|
168
169
|
- spec/zk/node_deletion_watcher_spec.rb
|
@@ -239,6 +240,7 @@ test_files:
|
|
239
240
|
- spec/zk/locker/locker_basic_spec.rb
|
240
241
|
- spec/zk/locker/shared_exclusive_integration_spec.rb
|
241
242
|
- spec/zk/locker/shared_locker_spec.rb
|
243
|
+
- spec/zk/locker_spec.rb
|
242
244
|
- spec/zk/module_spec.rb
|
243
245
|
- spec/zk/mongoid_spec.rb
|
244
246
|
- spec/zk/node_deletion_watcher_spec.rb
|