zk 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|