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 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