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 CHANGED
@@ -1,6 +1,8 @@
1
1
  --no-private
2
2
  --tag hidden_example:Hidden
3
+ --tag hidden_option:Hidden
3
4
  --hide-tag hidden_example
5
+ --hide-tag hidden_option
4
6
  --markup markdown
5
7
  -
6
8
  LICENSE
@@ -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:
@@ -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
@@ -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 triggerd for given paths.
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
- # @option opts [Integer] :acl defaults to <tt>Zookeeper::Constants::ZOO_OPEN_ACL_UNSAFE</tt>,
184
- # otherwise the ACL for the node. Should be a `ZOO_*` constant defined under the
185
- # Zookeeper::ACLs module in the zookeeper gem.
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
- # @option opts [bool] :ephemeral (false) if true, the created node will be ephemeral
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
- # @option opts [bool] :sequence (false) if true, the created node will be sequential
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
- # @option opts [bool] :sequential (false) alias for :sequence option. if both are given
192
- # an ArgumentError is raised
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
- # @option opts [Zookeeper::Callbacks::StringCallback] :callback (nil) provide a callback object
195
- # that will be called when the znode has been created
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
- # @option opts [:ephemeral_sequential, :persistent_sequential, :persistent, :ephemeral] :mode (nil)
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 = check_rc(cnx.create(h), h)
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
- check_rc(cnx.get(h), h)
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 = check_rc(cnx.set(h), h)
538
+ rv = call_and_check_rc(:set, h)
455
539
 
456
- opts[:callback] ? rv : rv[:stat]
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
- # @option opts [Zookeeper::Callbacks::StringsCallback] :callback to make this
659
+ # @hidden_option opts [Zookeeper::Callbacks::StringsCallback] :callback to make this
560
660
  # call asynchronously
561
661
  #
562
- # @option opts [Object] :context an object passed to the `:callback`
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
- check_rc(cnx.get_children(h), h)
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 = check_rc(cnx.delete(h), h)
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 = check_rc(cnx.get_acl(h), h)
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 = check_rc(cnx.set_acl(h), h)
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 register different blocks for
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
- def check_rc(hash, inputs=nil)
904
- code = hash[:rc]
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
- hash
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
@@ -221,6 +221,11 @@ module ZK
221
221
  nil
222
222
  end
223
223
 
224
+ # {see Base#close}
225
+ def close
226
+ super
227
+ end
228
+
224
229
  # (see Threadpool#on_threadpool?)
225
230
  def on_threadpool?
226
231
  @threadpool and @threadpool.on_threadpool?
@@ -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!) }
@@ -1,3 +1,3 @@
1
1
  module ZK
2
- VERSION = "1.3.1"
2
+ VERSION = "1.4.0"
3
3
  end
@@ -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) { "localhost:#{ZK.test_port}" }
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
- @zk.delete(@missing_path) rescue ZK::Exceptions::NoNode
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
 
@@ -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
- pending_in_travis "skip this test, flaky in travis"
65
- pending_rbx('fails in rubinius') do
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
- 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
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
- @parent_pid = $$
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
- @zk.create("#{@pids_root}/#{$$}", $$.to_s)
126
+ delete_latch = Zookeeper::Latch.new
82
127
 
83
- event_catcher = EventCatcher.new
128
+ delete_event = nil
84
129
 
85
- @zk.register(@pids_root, :only => :child) do |event|
86
- event_catcher << event
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
- @pid = fork do
90
- @zk.reopen
91
- @zk.wait_until_connected
143
+ @zk.exists?(child_pid_path, :watch => true)
144
+
145
+ @zk.delete(child_pid_path)
92
146
 
93
- wait_until(3) { @zk.exists?("#{@pids_root}/#{$$}") }
147
+ logger.debug { "awaiting deletion event notification" }
148
+ delete_latch.await unless delete_event
94
149
 
95
- logger.debug { "in child: child pid path exists?: %p" % [@zk.exists?("#{@pids_root}/#{$$}")] }
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
- _, stat = Process.wait2(@pid)
159
+ # replicates deletion watcher inside child
160
+ child_pid_path = "#{@pids_root}/#{@pid}"
101
161
 
102
- stat.should_not be_signaled
103
- stat.should be_exited
104
- stat.should be_success
162
+ delete_latch = Latch.new
105
163
 
106
- event_catcher.synchronize do
107
- unless event_catcher.child.empty?
108
- event_catcher.wait_for_child
109
- event_catcher.child.should_not be_empty
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.2'
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: 25
4
+ hash: 7
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
- - 3
9
- - 1
10
- version: 1.3.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-09 00:00:00 Z
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: 19
29
+ hash: 17
30
30
  segments:
31
31
  - 1
32
32
  - 0
33
- - 2
34
- version: 1.0.2
33
+ - 3
34
+ version: 1.0.3
35
35
  type: :runtime
36
36
  version_requirements: *id001
37
37
  - !ruby/object:Gem::Dependency