zk 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +2 -0
- data/README.markdown +24 -7
- data/RELEASES.markdown +21 -0
- data/lib/zk/client/base.rb +257 -85
- data/lib/zk/client/threaded.rb +5 -0
- data/lib/zk/event_handler.rb +30 -1
- data/lib/zk/version.rb +1 -1
- data/spec/shared/client_contexts.rb +1 -1
- data/spec/shared/client_examples.rb +49 -1
- data/spec/support/00_test_port_attr.rb +5 -0
- data/spec/zk/client_spec.rb +100 -31
- data/zk.gemspec +1 -1
- metadata +8 -8
data/.yardopts
CHANGED
data/README.markdown
CHANGED
@@ -8,6 +8,8 @@ ZK is licensed under the [MIT][] license.
|
|
8
8
|
|
9
9
|
The key place to start in the documentation is with ZK::Client::Base ([rubydoc.info][ZK::Client::Base], [local](/docs/ZK/Client/Base)).
|
10
10
|
|
11
|
+
See the [RELEASES][] file for information on what changed between versions.
|
12
|
+
|
11
13
|
This library is heavily used in a production deployment and is actively developed and maintained.
|
12
14
|
|
13
15
|
Development is sponsored by [Snapfish][] and has been generously released to the Open Source community by HPDC, L.P.
|
@@ -17,6 +19,7 @@ Development is sponsored by [Snapfish][] and has been generously released to the
|
|
17
19
|
[zookeeper gem]: https://github.com/slyphon/zookeeper "slyphon-zookeeper gem"
|
18
20
|
[MIT]: http://www.gnu.org/licenses/license-list.html#Expat "MIT (Expat) License"
|
19
21
|
[Snapfish]: http://www.snapfish.com/ "Snapfish"
|
22
|
+
[RELEASES]: https://github.com/slyphon/zk/blob/master/RELEASES.markdown
|
20
23
|
|
21
24
|
## What is ZooKeeper? ##
|
22
25
|
|
@@ -63,6 +66,27 @@ In addition to all of that, I would like to think that the public API the ZK::Cl
|
|
63
66
|
|
64
67
|
## NEWS ##
|
65
68
|
|
69
|
+
### v1.4.0 ###
|
70
|
+
|
71
|
+
* Added a new `:ignore` option for convenience when you don't care if an operation fails. In the case of a failure, the method will return nil instead of raising an exception. This option works for `children`, `create`, `delete`, `get`, `get_acl`, `set`, and `set_acl`. `stat` will ignore the option (because it doesn't care about the state of a node).
|
72
|
+
|
73
|
+
```
|
74
|
+
# so instead of having to do:
|
75
|
+
|
76
|
+
begin
|
77
|
+
zk.delete('/some/path')
|
78
|
+
rescue ZK::Exceptions;:NoNode
|
79
|
+
end
|
80
|
+
|
81
|
+
# you can do
|
82
|
+
|
83
|
+
zk.delete('/some/path', :ignore => :no_node)
|
84
|
+
|
85
|
+
```
|
86
|
+
|
87
|
+
* 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).
|
88
|
+
|
89
|
+
|
66
90
|
### v1.3.1 ###
|
67
91
|
|
68
92
|
* [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
|
@@ -96,13 +120,6 @@ You are __STRONGLY ENCOURAGED__ to go and look at the [CHANGELOG](http://git.io/
|
|
96
120
|
* Fixed a race condition in `event_catcher_spec.rb` that would cause 100% cpu usage and hang.
|
97
121
|
|
98
122
|
|
99
|
-
### v1.1.0 ###
|
100
|
-
|
101
|
-
* NEW! Thread-per-Callback event delivery model! [Read all about it!](https://github.com/slyphon/zk/wiki/EventDeliveryModel). Provides a simple, sane way to increase the concurrency in your ZK-based app while maintaining the ordering guarantees ZooKeeper makes. Each callback can perform whatever work it needs to without blocking other callbacks from receiving events. Inspired by [Celluloid's](https://github.com/celluloid/celluloid) actor model.
|
102
|
-
|
103
|
-
* Use the [zk-server](https://github.com/slyphon/zk-server) gem to run a standalone ZooKeeper server for tests (`rake SPAWN_ZOOKEEPER=1`). Makes live-fire testing of any project that uses ZK easy to run anywhere!
|
104
|
-
|
105
|
-
|
106
123
|
## Caveats
|
107
124
|
|
108
125
|
ZK strives to be a complete, correct, and convenient way of interacting with ZooKeeper. There are a few things to be aware of:
|
data/RELEASES.markdown
CHANGED
@@ -1,4 +1,25 @@
|
|
1
1
|
This file notes feature differences and bugfixes contained between releases.
|
2
|
+
|
3
|
+
### v1.4.0 ###
|
4
|
+
|
5
|
+
* Added a new `:ignore` option for convenience when you don't care if an operation fails. In the case of a failure, the method will return nil instead of raising an exception. This option works for `children`, `create`, `delete`, `get`, `get_acl`, `set`, and `set_acl`. `stat` will ignore the option (because it doesn't care about the state of a node).
|
6
|
+
|
7
|
+
```
|
8
|
+
# so instead of having to do:
|
9
|
+
|
10
|
+
begin
|
11
|
+
zk.delete('/some/path')
|
12
|
+
rescue ZK::Exceptions;:NoNode
|
13
|
+
end
|
14
|
+
|
15
|
+
# you can do
|
16
|
+
|
17
|
+
zk.delete('/some/path', :ignore => :no_node)
|
18
|
+
|
19
|
+
```
|
20
|
+
|
21
|
+
* 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).
|
22
|
+
|
2
23
|
### v1.3.1 ###
|
3
24
|
|
4
25
|
* [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
|
data/lib/zk/client/base.rb
CHANGED
@@ -43,8 +43,8 @@ module ZK
|
|
43
43
|
#
|
44
44
|
class Base
|
45
45
|
# The Eventhandler is used by client code to register callbacks to handle
|
46
|
-
# events
|
47
|
-
#
|
46
|
+
# events triggered for given paths.
|
47
|
+
#
|
48
48
|
# @see ZK::Client::Base#register
|
49
49
|
attr_reader :event_handler
|
50
50
|
|
@@ -53,6 +53,17 @@ module ZK
|
|
53
53
|
attr_reader :cnx
|
54
54
|
protected :cnx
|
55
55
|
|
56
|
+
# maps from a symbol given as an option, to the numeric error constant that should
|
57
|
+
# not raise an exception
|
58
|
+
#
|
59
|
+
# @private
|
60
|
+
ERROR_IGNORE_MAP = {
|
61
|
+
:no_node => Zookeeper::ZNONODE,
|
62
|
+
:node_exists => Zookeeper::ZNODEEXISTS,
|
63
|
+
:not_empty => Zookeeper::ZNOTEMPTY,
|
64
|
+
:bad_version => Zookeeper::ZBADVERSION,
|
65
|
+
}
|
66
|
+
|
56
67
|
# @deprecated for backwards compatibility only
|
57
68
|
# use ZK::Client::Base#event_handler instead
|
58
69
|
def watcher
|
@@ -125,6 +136,12 @@ module ZK
|
|
125
136
|
#
|
126
137
|
def close!
|
127
138
|
event_handler.clear!
|
139
|
+
close
|
140
|
+
end
|
141
|
+
|
142
|
+
# close the underlying connection, but do not reset callbacks registered
|
143
|
+
# via the `register` method. This is to be used when preparing to fork.
|
144
|
+
def close
|
128
145
|
wrap_state_closed_error { cnx.close if cnx && !cnx.closed? }
|
129
146
|
end
|
130
147
|
|
@@ -177,29 +194,58 @@ module ZK
|
|
177
194
|
# creates a znode at the absolute `path` with blank data and given
|
178
195
|
# options
|
179
196
|
#
|
197
|
+
# @option opts [Zookeeper::Callbacks::StringCallback] :callback (nil) provide a callback object
|
198
|
+
# that will be called when the znode has been created
|
199
|
+
#
|
200
|
+
# @option opts [Object] :context (nil) an object passed to the `:callback`
|
201
|
+
# given as the `context` param
|
202
|
+
#
|
203
|
+
# @option opts [:ephemeral_sequential, :persistent_sequential, :persistent, :ephemeral] :mode (nil)
|
204
|
+
# may be specified instead of :ephemeral and :sequence options. If `:mode` *and* either of
|
205
|
+
# the `:ephermeral` or `:sequential` options are given, the `:mode` option will win
|
206
|
+
#
|
207
|
+
# @option opts [:no_node,:node_exists] :ignore (nil) Do not raise an error if
|
208
|
+
# one of the given statuses is returned from ZooKeeper. This option
|
209
|
+
# may be given as either a symbol (for a single option) or as an Array
|
210
|
+
# of symbols for multiple ignores. This is useful when you want to
|
211
|
+
# create a node but don't care if it's already been created, and don't
|
212
|
+
# want to have to wrap it in a begin/rescue/end block.
|
213
|
+
#
|
214
|
+
# * `:no_node`: silences the error case where you try to
|
215
|
+
# create `/foo/bar/baz` but any of the parent paths (`/foo` or
|
216
|
+
# `/foo/bar`) don't exist.
|
217
|
+
#
|
218
|
+
# * `:node_exists`: silences the error case where you try to create
|
219
|
+
# `/foo/bar` but it already exists.
|
220
|
+
#
|
180
221
|
# @overload create(path, data, opts={})
|
181
222
|
# creates a znode at the absolute `path` with given data and options
|
182
223
|
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
224
|
+
# @option opts [Zookeeper::Callbacks::StringCallback] :callback (nil) provide a callback object
|
225
|
+
# that will be called when the znode has been created
|
226
|
+
#
|
227
|
+
# @option opts [Object] :context (nil) an object passed to the `:callback`
|
228
|
+
# given as the `context` param
|
186
229
|
#
|
187
|
-
#
|
230
|
+
# @option opts [:ephemeral_sequential, :persistent_sequential, :persistent, :ephemeral] :mode (nil)
|
231
|
+
# may be specified instead of :ephemeral and :sequence options. If `:mode` *and* either of
|
232
|
+
# the `:ephermeral` or `:sequential` options are given, the `:mode` option will win
|
188
233
|
#
|
189
|
-
#
|
234
|
+
# @option opts [:no_node,:node_exists] :ignore (nil) Do not raise an error if
|
235
|
+
# one of the given statuses is returned from ZooKeeper. This option
|
236
|
+
# may be given as either a symbol (for a single option) or as an Array
|
237
|
+
# of symbols for multiple ignores. This is useful when you want to
|
238
|
+
# create a node but don't care if it's already been created, and don't
|
239
|
+
# want to have to wrap it in a begin/rescue/end block.
|
190
240
|
#
|
191
|
-
#
|
192
|
-
#
|
241
|
+
# * `:no_node`: silences the error case where you try to
|
242
|
+
# create `/foo/bar/baz` but any of the parent paths (`/foo` or
|
243
|
+
# `/foo/bar`) don't exist.
|
193
244
|
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
# @option opts [Object] :context (nil) an object passed to the `:callback`
|
198
|
-
# given as the `context` param
|
245
|
+
# * `:node_exists`: silences the error case where you try to create
|
246
|
+
# `/foo/bar` but it already exists.
|
199
247
|
#
|
200
|
-
# @
|
201
|
-
# may be specified instead of :ephemeral and :sequence options. If `:mode` *and* either of
|
202
|
-
# the `:ephermeral` or `:sequential` options are given, the `:mode` option will win
|
248
|
+
# @since 1.4.0: `:ignore` option
|
203
249
|
#
|
204
250
|
# @raise [ZK::Exceptions::NodeExists] if a node with the same `path` already exists
|
205
251
|
#
|
@@ -208,7 +254,9 @@ module ZK
|
|
208
254
|
# @raise [ZK::Exceptions::NoChildrenForEphemerals] if the parent node of
|
209
255
|
# the given path is ephemeral
|
210
256
|
#
|
211
|
-
# @return [String] the path created on the server
|
257
|
+
# @return [String] if created successfully the path created on the server
|
258
|
+
#
|
259
|
+
# @return [nil] if :ignore option is given and one of the errors listed
|
212
260
|
#
|
213
261
|
# @todo Document the asynchronous methods
|
214
262
|
#
|
@@ -325,7 +373,7 @@ module ZK
|
|
325
373
|
end
|
326
374
|
end
|
327
375
|
|
328
|
-
rv =
|
376
|
+
rv = call_and_check_rc(:create, h)
|
329
377
|
|
330
378
|
h[:callback] ? rv : rv[:path]
|
331
379
|
end
|
@@ -386,7 +434,7 @@ module ZK
|
|
386
434
|
h = { :path => path }.merge(opts)
|
387
435
|
|
388
436
|
rv = setup_watcher!(:data, h) do
|
389
|
-
|
437
|
+
call_and_check_rc(:get, h)
|
390
438
|
end
|
391
439
|
|
392
440
|
opts[:callback] ? rv : rv.values_at(:data, :stat)
|
@@ -427,6 +475,24 @@ module ZK
|
|
427
475
|
# @option opts [Object] :context an object passed to the `:callback`
|
428
476
|
# given as the `context` param
|
429
477
|
#
|
478
|
+
# @option opts [:no_node,:bad_version] :ignore (nil) Do not raise an error if
|
479
|
+
# one of the given statuses is returned from ZooKeeper. This option
|
480
|
+
# may be given as either a symbol (for a single option) or as an Array
|
481
|
+
# of symbols for multiple ignores. This is useful when you want to
|
482
|
+
# set a node if it exists but don't care if it doesn't.
|
483
|
+
#
|
484
|
+
# * `:no_node`: silences the error case where you try to
|
485
|
+
# set `/foo/bar/baz` but it doesn't exist.
|
486
|
+
#
|
487
|
+
# * `:bad_version`: silences the error case where you give a `:version`
|
488
|
+
# but it doesn't match the server's version.
|
489
|
+
#
|
490
|
+
# @since 1.4.0: `:ignore` option
|
491
|
+
#
|
492
|
+
# @return [Stat] the stat of the node after a successful update
|
493
|
+
#
|
494
|
+
# @return [nil] if `:ignore` is given and our update was not successful
|
495
|
+
#
|
430
496
|
# @example unconditionally set the data of "/path"
|
431
497
|
#
|
432
498
|
# zk.set("/path", "foo")
|
@@ -435,25 +501,59 @@ module ZK
|
|
435
501
|
#
|
436
502
|
# zk.set("/path", "foo", :version => 0)
|
437
503
|
#
|
504
|
+
# @example set the data of a non-existent node, check for success
|
505
|
+
#
|
506
|
+
# if zk.set("/path/does/not/exist", 'blah', :ignore => :no_node)
|
507
|
+
# puts 'the node existed and we updated it to say "blah"'
|
508
|
+
# else
|
509
|
+
# puts "pffft, i didn't wanna update that stupid node anyway"
|
510
|
+
# end
|
511
|
+
#
|
512
|
+
# @example fail to set the data of a node, ignore bad_version
|
513
|
+
#
|
514
|
+
# data, stat = zk.get('/path')
|
515
|
+
#
|
516
|
+
# if zk.set("/path", 'blah', :version => stat.version, :ignore => :bad_version)
|
517
|
+
# puts 'the node existed, had the right version, and we updated it to say "blah"'
|
518
|
+
# else
|
519
|
+
# puts "guess someone beat us to it"
|
520
|
+
# end
|
521
|
+
#
|
522
|
+
# @hidden_example set data asynchronously
|
523
|
+
#
|
524
|
+
# class StatCallback
|
525
|
+
# def process_result(return_code, path, context, stat)
|
526
|
+
# # do processing here
|
527
|
+
# end
|
528
|
+
# end
|
529
|
+
#
|
530
|
+
# callback = StatCallback.new
|
531
|
+
# context = Object.new
|
532
|
+
#
|
533
|
+
# zk.set("/path", "foo", :callback => callback, :context => context)
|
534
|
+
#
|
438
535
|
def set(path, data, opts={})
|
439
|
-
# ===== set data asynchronously
|
440
|
-
#
|
441
|
-
# class StatCallback
|
442
|
-
# def process_result(return_code, path, context, stat)
|
443
|
-
# # do processing here
|
444
|
-
# end
|
445
|
-
# end
|
446
|
-
#
|
447
|
-
# callback = StatCallback.new
|
448
|
-
# context = Object.new
|
449
|
-
#
|
450
|
-
# zk.set("/path", "foo", :callback => callback, :context => context)
|
451
|
-
|
452
536
|
h = { :path => path, :data => data }.merge(opts)
|
453
537
|
|
454
|
-
rv =
|
538
|
+
rv = call_and_check_rc(:set, h)
|
455
539
|
|
456
|
-
|
540
|
+
logger.debug { "rv: #{rv.inspect}" }
|
541
|
+
|
542
|
+
# the reason we check the :rc here is: if the user set an :ignore which
|
543
|
+
# has successfully squashed an error code from turning into an exception
|
544
|
+
# we want to return nil. If the user was successful, we want to return
|
545
|
+
# the Stat we got back from the server
|
546
|
+
#
|
547
|
+
# in the case of an async request, we want to return the result code of
|
548
|
+
# the async operation (the submission)
|
549
|
+
|
550
|
+
if opts[:callback]
|
551
|
+
rv
|
552
|
+
elsif (rv[:rc] == Zookeeper::ZOK)
|
553
|
+
rv[:stat]
|
554
|
+
else
|
555
|
+
nil
|
556
|
+
end
|
457
557
|
end
|
458
558
|
|
459
559
|
# Return the stat of the node of the given path. Return nil if the node
|
@@ -556,11 +656,19 @@ module ZK
|
|
556
656
|
# @option opts [bool] :watch (false) set to true if you want your registered
|
557
657
|
# callbacks for this node to be called on change
|
558
658
|
#
|
559
|
-
# @
|
659
|
+
# @hidden_option opts [Zookeeper::Callbacks::StringsCallback] :callback to make this
|
560
660
|
# call asynchronously
|
561
661
|
#
|
562
|
-
# @
|
662
|
+
# @hidden_option opts [Object] :context an object passed to the `:callback`
|
563
663
|
# given as the `context` param
|
664
|
+
#
|
665
|
+
# @option opts [:no_node] :ignore (nil) Do not raise an error if
|
666
|
+
# one of the given statuses is returned from ZooKeeper. This option
|
667
|
+
# may be given as either a symbol (for a single option) or as an Array
|
668
|
+
# of symbols for multiple ignores.
|
669
|
+
#
|
670
|
+
# * `:no_node`: silences the error case where you try to
|
671
|
+
# set `/foo/bar/baz` but it doesn't exist.
|
564
672
|
#
|
565
673
|
# @example get children for path
|
566
674
|
#
|
@@ -577,24 +685,24 @@ module ZK
|
|
577
685
|
# zk.children("/path", :watch => true)
|
578
686
|
# # => ["child_0", "child_1"]
|
579
687
|
#
|
688
|
+
# @hidden_example
|
689
|
+
#
|
690
|
+
# class ChildrenCallback
|
691
|
+
# def process_result(return_code, path, context, children)
|
692
|
+
# # do processing here
|
693
|
+
# end
|
694
|
+
# end
|
695
|
+
#
|
696
|
+
# callback = ChildrenCallback.new
|
697
|
+
# context = Object.new
|
698
|
+
# zk.children("/path", :callback => callback, :context => context)
|
699
|
+
#
|
580
700
|
def children(path, opts={})
|
581
|
-
# ===== get children asynchronously
|
582
|
-
#
|
583
|
-
# class ChildrenCallback
|
584
|
-
# def process_result(return_code, path, context, children)
|
585
|
-
# # do processing here
|
586
|
-
# end
|
587
|
-
# end
|
588
|
-
#
|
589
|
-
# callback = ChildrenCallback.new
|
590
|
-
# context = Object.new
|
591
|
-
# zk.children("/path", :callback => callback, :context => context)
|
592
|
-
|
593
701
|
|
594
702
|
h = { :path => path }.merge(opts)
|
595
703
|
|
596
704
|
rv = setup_watcher!(:child, h) do
|
597
|
-
|
705
|
+
call_and_check_rc(:get_children, h)
|
598
706
|
end
|
599
707
|
|
600
708
|
opts[:callback] ? rv : rv[:children]
|
@@ -629,6 +737,24 @@ module ZK
|
|
629
737
|
#
|
630
738
|
# @option opts [Object] :context an object passed to the `:callback`
|
631
739
|
# given as the `context` param
|
740
|
+
#
|
741
|
+
# @option opts [:no_node,:not_empty,:bad_version] :ignore (nil) Do not
|
742
|
+
# raise an error if one of the given statuses is returned from ZooKeeper.
|
743
|
+
# This option may be given as either a symbol (for a single option) or as
|
744
|
+
# an Array of symbols for multiple ignores. This is useful when you want
|
745
|
+
# to delete a node but don't care if it's already been deleted, and don't
|
746
|
+
# want to have to wrap it in a begin/rescue/end block.
|
747
|
+
#
|
748
|
+
# * `:no_node`: silences the error case where you try to
|
749
|
+
# delete `/foo/bar/baz` but it doesn't exist.
|
750
|
+
#
|
751
|
+
# * `:not_empty`: silences the error case where you try to delete
|
752
|
+
# `/foo/bar` but it has children.
|
753
|
+
#
|
754
|
+
# * `:bad_version`: silences the error case where you give a `:version`
|
755
|
+
# but it doesn't match the server's version.
|
756
|
+
#
|
757
|
+
# @since 1.4.0: `:ignore` option
|
632
758
|
#
|
633
759
|
# @example delete a node
|
634
760
|
# zk.delete("/path")
|
@@ -636,23 +762,22 @@ module ZK
|
|
636
762
|
# @example delete a node with a specific version
|
637
763
|
# zk.delete("/path", :version => 5)
|
638
764
|
#
|
765
|
+
# @hidden_example
|
766
|
+
#
|
767
|
+
# class VoidCallback
|
768
|
+
# def process_result(return_code, path, context)
|
769
|
+
# # do processing here
|
770
|
+
# end
|
771
|
+
# end
|
772
|
+
#
|
773
|
+
# callback = VoidCallback.new
|
774
|
+
# context = Object.new
|
775
|
+
#
|
776
|
+
# zk.delete(/path", :callback => callback, :context => context)
|
777
|
+
#
|
639
778
|
def delete(path, opts={})
|
640
|
-
# ===== delete node asynchronously
|
641
|
-
#
|
642
|
-
# class VoidCallback
|
643
|
-
# def process_result(return_code, path, context)
|
644
|
-
# # do processing here
|
645
|
-
# end
|
646
|
-
# end
|
647
|
-
#
|
648
|
-
# callback = VoidCallback.new
|
649
|
-
# context = Object.new
|
650
|
-
#
|
651
|
-
# zk.delete(/path", :callback => callback, :context => context)
|
652
|
-
|
653
|
-
|
654
779
|
h = { :path => path, :version => -1 }.merge(opts)
|
655
|
-
rv =
|
780
|
+
rv = call_and_check_rc(:delete, h)
|
656
781
|
opts[:callback] ? rv : nil
|
657
782
|
end
|
658
783
|
|
@@ -684,21 +809,21 @@ module ZK
|
|
684
809
|
# zk.get_acl("/path", :stat => stat)
|
685
810
|
# # => [ACL]
|
686
811
|
#
|
812
|
+
# @hidden_example
|
813
|
+
#
|
814
|
+
# class AclCallback
|
815
|
+
# def processResult(return_code, path, context, acl, stat)
|
816
|
+
# # do processing here
|
817
|
+
# end
|
818
|
+
# end
|
819
|
+
#
|
820
|
+
# callback = AclCallback.new
|
821
|
+
# context = Object.new
|
822
|
+
# zk.acls("/path", :callback => callback, :context => context)
|
823
|
+
#
|
687
824
|
def get_acl(path, opts={})
|
688
|
-
# ===== get acl asynchronously
|
689
|
-
#
|
690
|
-
# class AclCallback
|
691
|
-
# def processResult(return_code, path, context, acl, stat)
|
692
|
-
# # do processing here
|
693
|
-
# end
|
694
|
-
# end
|
695
|
-
#
|
696
|
-
# callback = AclCallback.new
|
697
|
-
# context = Object.new
|
698
|
-
# zk.acls("/path", :callback => callback, :context => context)
|
699
|
-
|
700
825
|
h = { :path => path }.merge(opts)
|
701
|
-
rv =
|
826
|
+
rv = call_and_check_rc(:get_acl, h)
|
702
827
|
opts[:callback] ? rv : rv.values_at(:children, :stat)
|
703
828
|
end
|
704
829
|
|
@@ -729,7 +854,7 @@ module ZK
|
|
729
854
|
#
|
730
855
|
def set_acl(path, acls, opts={})
|
731
856
|
h = { :path => path, :acl => acls }.merge(opts)
|
732
|
-
rv =
|
857
|
+
rv = call_and_check_rc(:set_acl, h)
|
733
858
|
opts[:callback] ? rv : rv[:stat]
|
734
859
|
end
|
735
860
|
|
@@ -780,9 +905,18 @@ module ZK
|
|
780
905
|
# to remove the block from further updates by calling its `.unsubscribe` method.
|
781
906
|
#
|
782
907
|
# You can specify a list of event types after the path that you wish to
|
783
|
-
# receive in your block. This allows you to
|
784
|
-
# different types of events.
|
785
|
-
#
|
908
|
+
# receive in your block using the `:only` option. This allows you to
|
909
|
+
# register different blocks for different types of events. This sounds
|
910
|
+
# more convenient, but __there is a potential pitfall__. The `:only`
|
911
|
+
# option does filtering behind the scenes, so if you need a `:created`
|
912
|
+
# event, but a `:changed` event is delivered instead, *and you don't have
|
913
|
+
# a handler registered* for the `:changed` event which re-watches, then
|
914
|
+
# you will most likely just miss it and blame the author. You should try
|
915
|
+
# to stick to the style where you use a single block to test for the
|
916
|
+
# different event types, re-registering as necessary. If you find that
|
917
|
+
# block gets too out of hand, then use the `:only` option and break the
|
918
|
+
# logic up between handlers.
|
919
|
+
#
|
786
920
|
# @note All node watchers are one-shot handlers. After an event is delivered to
|
787
921
|
# your handler, you *must* re-watch the node to receive more events. This
|
788
922
|
# leads to a pattern you will find throughout ZK code that avoids races,
|
@@ -899,15 +1033,53 @@ module ZK
|
|
899
1033
|
Process.pid != @pid
|
900
1034
|
end
|
901
1035
|
|
1036
|
+
def call_and_check_rc(meth, opts)
|
1037
|
+
scrubbed_opts = opts.dup
|
1038
|
+
scrubbed_opts.delete(:ignore)
|
1039
|
+
|
1040
|
+
rv = cnx.__send__(meth, scrubbed_opts)
|
1041
|
+
|
1042
|
+
check_rc(rv, opts)
|
1043
|
+
end
|
1044
|
+
|
902
1045
|
# @private
|
903
|
-
|
904
|
-
|
1046
|
+
# XXX: make this actually call the method on cnx
|
1047
|
+
def check_rc(rv_hash, inputs)
|
1048
|
+
code = rv_hash[:rc]
|
1049
|
+
|
905
1050
|
if code && (code != Zookeeper::ZOK)
|
1051
|
+
return rv_hash if ignore_set(inputs[:ignore]).include?(code)
|
1052
|
+
|
906
1053
|
msg = inputs ? "inputs: #{inputs.inspect}" : nil
|
907
1054
|
raise Exceptions::KeeperException.by_code(code), msg
|
908
1055
|
else
|
909
|
-
|
1056
|
+
rv_hash
|
1057
|
+
end
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
# arg is either a symbol (for one ignore) or an array
|
1061
|
+
# this method checks for validity, returns a set of the integers that
|
1062
|
+
# can be ignored or the empty set if arg is nil
|
1063
|
+
def ignore_set(arg)
|
1064
|
+
return Set.new if arg.nil?
|
1065
|
+
|
1066
|
+
sym_array =
|
1067
|
+
case arg
|
1068
|
+
when Symbol
|
1069
|
+
[arg]
|
1070
|
+
when Array
|
1071
|
+
arg
|
1072
|
+
else
|
1073
|
+
raise ArgumentError, ":ignore option needs to be one of: #{ERROR_IGNORE_MAP.keys.inspect}, as a symbol or array of symbols, not #{arg.inspect}"
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
bad_keys = sym_array - ERROR_IGNORE_MAP.keys
|
1077
|
+
|
1078
|
+
unless bad_keys.empty?
|
1079
|
+
raise ArgumentError, "Sorry, #{bad_keys.inspect} not valid for :ignore, valid arguments are: #{ERROR_IGNORE_MAP.keys.inspect}"
|
910
1080
|
end
|
1081
|
+
|
1082
|
+
Set.new(ERROR_IGNORE_MAP.values_at(*sym_array))
|
911
1083
|
end
|
912
1084
|
|
913
1085
|
# @private
|
data/lib/zk/client/threaded.rb
CHANGED
data/lib/zk/event_handler.rb
CHANGED
@@ -44,15 +44,44 @@ module ZK
|
|
44
44
|
h.tap { |x| x[k] = Set.new }
|
45
45
|
end
|
46
46
|
|
47
|
+
@state = :running
|
48
|
+
|
47
49
|
reopen_after_fork!
|
48
50
|
end
|
51
|
+
|
52
|
+
# stops the dispatching of events. already in-flight callbacks may still be running, but
|
53
|
+
# no new events will be dispatched until {#start} is called
|
54
|
+
#
|
55
|
+
# any events that are delivered while we are stopped will be lost
|
56
|
+
#
|
57
|
+
def stop
|
58
|
+
synchronize do
|
59
|
+
return if @state == :stopped
|
60
|
+
@state = :stopped
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# called to re-enable event delivery
|
65
|
+
def start
|
66
|
+
synchronize do
|
67
|
+
return if @state == :running
|
68
|
+
@state = :running
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def running?
|
73
|
+
synchronize { @state == :running }
|
74
|
+
end
|
75
|
+
|
76
|
+
def stopped?
|
77
|
+
synchronize { @state == :stopped }
|
78
|
+
end
|
49
79
|
|
50
80
|
# do not call this method. it is inteded for use only when we've forked and
|
51
81
|
# all other threads are dead.
|
52
82
|
#
|
53
83
|
# @private
|
54
84
|
def reopen_after_fork!
|
55
|
-
logger.debug { "#{self.class}##{__method__} reopening callbacks" }
|
56
85
|
@mutex = Monitor.new
|
57
86
|
# XXX: need to test this w/ actor-style callbacks
|
58
87
|
@callbacks.values.flatten.each { |cb| cb.reopen_after_fork! if cb.respond_to?(:reopen_after_fork!) }
|
data/lib/zk/version.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
shared_context 'connection opts' do
|
2
2
|
let(:connection_opts) { { :thread => :per_callback, :timeout => 5 } }
|
3
|
-
let(:connection_host) { "
|
3
|
+
let(:connection_host) { "#{ZK.default_host}:#{ZK.test_port}" }
|
4
4
|
let(:connection_args) { [connection_host, connection_opts] }
|
5
5
|
end
|
6
6
|
|
@@ -102,13 +102,48 @@ shared_examples_for 'client' do
|
|
102
102
|
it %[should barf if both :sequence and :sequential are given] do
|
103
103
|
lambda { @zk.create(@base_path, 'data', :sequence => true, :sequential => true) }.should raise_error(ArgumentError)
|
104
104
|
end
|
105
|
+
|
106
|
+
describe %[:ignore option] do
|
107
|
+
it %[should squelch node_exists] do
|
108
|
+
@zk.create(@base_path)
|
109
|
+
|
110
|
+
proc { @zk.create(@base_path, :ignore => :node_exists).should be_nil }.should_not raise_error(ZK::Exceptions::NoNode)
|
111
|
+
end
|
112
|
+
|
113
|
+
it %[should squelch no_node] do
|
114
|
+
proc { @zk.create("#{@base_path}/foo/bar/baz", :ignore => :no_node).should be_nil }.should_not raise_error(ZK::Exceptions::NoNode)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe :delete do
|
120
|
+
describe %[:ignore option] do
|
121
|
+
it %[should squelch not_empty] do
|
122
|
+
@zk.create(@base_path)
|
123
|
+
@zk.create("#{@base_path}/blah")
|
124
|
+
|
125
|
+
proc { @zk.delete(@base_path, :ignore => :not_empty).should be_nil }.should_not raise_error
|
126
|
+
end
|
127
|
+
|
128
|
+
it %[should squelch no_node] do
|
129
|
+
proc { @zk.delete("#{@base_path}/foo/bar/baz", :ignore => :no_node).should be_nil }.should_not raise_error
|
130
|
+
end
|
131
|
+
|
132
|
+
it %[should squelch bad_version] do
|
133
|
+
@zk.create(@base_path)
|
134
|
+
proc { @zk.delete("#{@base_path}", :version => 7, :ignore => :bad_version).should be_nil }.should_not raise_error
|
135
|
+
end
|
136
|
+
end
|
105
137
|
end
|
106
138
|
|
107
139
|
describe :stat do
|
108
140
|
describe 'for a missing node' do
|
109
141
|
before do
|
110
142
|
@missing_path = '/thispathdoesnotexist'
|
111
|
-
|
143
|
+
begin
|
144
|
+
@zk.delete(@missing_path)
|
145
|
+
rescue ZK::Exceptions::NoNode
|
146
|
+
end
|
112
147
|
end
|
113
148
|
|
114
149
|
it %[should not raise any error] do
|
@@ -125,6 +160,19 @@ shared_examples_for 'client' do
|
|
125
160
|
end
|
126
161
|
end
|
127
162
|
|
163
|
+
describe :set do
|
164
|
+
describe %[:ignore option] do
|
165
|
+
it %[should squelch no_node] do
|
166
|
+
proc { @zk.set("#{@base_path}/foo/bar/baz", '', :ignore => :no_node).should be_nil }.should_not raise_error
|
167
|
+
end
|
168
|
+
|
169
|
+
it %[should squelch bad_version] do
|
170
|
+
@zk.create(@base_path)
|
171
|
+
proc { @zk.set("#{@base_path}", '', :version => 7, :ignore => :bad_version).should be_nil }.should_not raise_error
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
128
176
|
describe :block_until_node_deleted do
|
129
177
|
before do
|
130
178
|
@path = '/_bogualkjdhsna'
|
@@ -16,5 +16,10 @@ module ZK
|
|
16
16
|
# argh, blah, this affects ZK.new everywhere (which is kind of the point, but
|
17
17
|
# still gross)
|
18
18
|
self.default_port = self.test_port
|
19
|
+
|
20
|
+
# only for testing is this done
|
21
|
+
if host = ENV['ZK_DEFAULT_HOST']
|
22
|
+
self.default_host = host
|
23
|
+
end
|
19
24
|
end
|
20
25
|
|
data/spec/zk/client_spec.rb
CHANGED
@@ -60,59 +60,128 @@ describe ZK::Client::Threaded do
|
|
60
60
|
ZK.open(*connection_args) { |z| z.rm_rf(@base_path) }
|
61
61
|
end
|
62
62
|
|
63
|
-
it %[should deliver callbacks in the child] do
|
64
|
-
|
65
|
-
pending_rbx('fails in rubinius')
|
63
|
+
it %[should deliver callbacks in the child], :fork => true do
|
64
|
+
# pending_in_travis "skip this test, flaky in travis"
|
65
|
+
pending_rbx('fails in rubinius')
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
67
|
+
logger.debug { "Process.pid of parent: #{Process.pid}" }
|
68
|
+
|
69
|
+
@zk = ZK.new do |z|
|
70
|
+
z.on_connected do
|
71
|
+
logger.debug { "on_connected fired, writing pid to path #{@pids_root}/#{$$}" }
|
72
|
+
begin
|
73
|
+
z.create("#{@pids_root}/#{Process.pid}", Process.pid.to_s)
|
74
|
+
rescue ZK::Exceptions::NodeExists
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
@parent_pid = $$
|
80
|
+
|
81
|
+
@zk.create("#{@pids_root}/#{$$}", $$.to_s)
|
82
|
+
|
83
|
+
event_catcher = EventCatcher.new
|
84
|
+
|
85
|
+
@zk.register(@pids_root) do |event|
|
86
|
+
if event.node_child?
|
87
|
+
event_catcher << event
|
88
|
+
else
|
89
|
+
@zk.children(@pids_root, :watch => true)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
logger.debug { "parent watching for children on #{@pids_root}" }
|
94
|
+
@zk.children(@pids_root, :watch => true) # side-effect, register watch
|
95
|
+
|
96
|
+
@pid = fork do
|
97
|
+
@zk.reopen
|
98
|
+
@zk.wait_until_connected
|
99
|
+
|
100
|
+
child_pid_path = "#{@pids_root}/#{$$}"
|
101
|
+
|
102
|
+
create_latch = Zookeeper::Latch.new
|
103
|
+
|
104
|
+
create_sub = @zk.register(child_pid_path) do |event|
|
105
|
+
if event.node_created?
|
106
|
+
logger.debug { "got created event, releasing create_latch" }
|
107
|
+
create_latch.release
|
108
|
+
else
|
109
|
+
if @zk.exists?(child_pid_path, :watch => true)
|
110
|
+
logger.debug { "created behind our backs, releasing create_latch" }
|
111
|
+
create_latch.release
|
75
112
|
end
|
76
113
|
end
|
77
114
|
end
|
78
115
|
|
79
|
-
@
|
116
|
+
if @zk.exists?(child_pid_path, :watch => true)
|
117
|
+
logger.debug { "woot! #{child_pid_path} exists!" }
|
118
|
+
create_sub.unregister
|
119
|
+
else
|
120
|
+
logger.debug { "awaiting the create_latch to release" }
|
121
|
+
create_latch.await
|
122
|
+
end
|
123
|
+
|
124
|
+
logger.debug { "now testing for delete event totally created in child" }
|
80
125
|
|
81
|
-
|
126
|
+
delete_latch = Zookeeper::Latch.new
|
82
127
|
|
83
|
-
|
128
|
+
delete_event = nil
|
84
129
|
|
85
|
-
@zk.register(
|
86
|
-
|
130
|
+
delete_sub = @zk.register(child_pid_path) do |event|
|
131
|
+
if event.node_deleted?
|
132
|
+
delete_event = event
|
133
|
+
logger.debug { "child got delete event on #{child_pid_path}" }
|
134
|
+
delete_latch.release
|
135
|
+
else
|
136
|
+
unless @zk.exists?(child_pid_path, :watch => true)
|
137
|
+
logger.debug { "child: someone deleted #{child_pid_path} behind our back" }
|
138
|
+
delete_latch.release
|
139
|
+
end
|
140
|
+
end
|
87
141
|
end
|
88
142
|
|
89
|
-
@
|
90
|
-
|
91
|
-
|
143
|
+
@zk.exists?(child_pid_path, :watch => true)
|
144
|
+
|
145
|
+
@zk.delete(child_pid_path)
|
92
146
|
|
93
|
-
|
147
|
+
logger.debug { "awaiting deletion event notification" }
|
148
|
+
delete_latch.await unless delete_event
|
94
149
|
|
95
|
-
|
150
|
+
logger.debug { "deletion event: #{delete_event}" }
|
96
151
|
|
152
|
+
if delete_event
|
97
153
|
exit! 0
|
154
|
+
else
|
155
|
+
exit! 1
|
98
156
|
end
|
157
|
+
end
|
99
158
|
|
100
|
-
|
159
|
+
# replicates deletion watcher inside child
|
160
|
+
child_pid_path = "#{@pids_root}/#{@pid}"
|
101
161
|
|
102
|
-
|
103
|
-
stat.should be_exited
|
104
|
-
stat.should be_success
|
162
|
+
delete_latch = Latch.new
|
105
163
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
164
|
+
delete_sub = @zk.register(child_pid_path) do |event|
|
165
|
+
if event.node_deleted?
|
166
|
+
logger.debug { "parent got delete event on #{child_pid_path}" }
|
167
|
+
delete_latch.release
|
168
|
+
else
|
169
|
+
unless @zk.exists?(child_pid_path, :watch => true)
|
170
|
+
logger.debug { "child: someone deleted #{child_pid_path} behind our back" }
|
171
|
+
delete_latch.release
|
110
172
|
end
|
111
173
|
end
|
174
|
+
end
|
175
|
+
|
176
|
+
delete_latch.await if @zk.exists?(child_pid_path, :watch => true)
|
177
|
+
|
178
|
+
_, stat = Process.wait2(@pid)
|
179
|
+
|
180
|
+
stat.should_not be_signaled
|
181
|
+
stat.should be_exited
|
182
|
+
stat.should be_success
|
112
183
|
|
113
|
-
@zk.should be_exists("#{@pids_root}/#{@pid}")
|
114
184
|
|
115
|
-
end
|
116
185
|
end # should deliver callbacks in the child
|
117
186
|
end # forked
|
118
187
|
end # # jruby guard
|
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.0.
|
15
|
+
s.add_runtime_dependency 'zookeeper', '~> 1.0.3'
|
16
16
|
s.add_runtime_dependency 'backports', '~> 2.5.1'
|
17
17
|
|
18
18
|
s.files = `git ls-files`.split("\n")
|
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: 7
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 1.
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 1.4.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-10 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: 17
|
30
30
|
segments:
|
31
31
|
- 1
|
32
32
|
- 0
|
33
|
-
-
|
34
|
-
version: 1.0.
|
33
|
+
- 3
|
34
|
+
version: 1.0.3
|
35
35
|
type: :runtime
|
36
36
|
version_requirements: *id001
|
37
37
|
- !ruby/object:Gem::Dependency
|