zk 1.1.0 → 1.1.1

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/.travis.yml CHANGED
@@ -10,6 +10,11 @@ rvm:
10
10
  - 1.9.3
11
11
  - 1.9.2
12
12
  - 1.8.7
13
+ - ree
14
+
15
+ # jruby specs are too intesive for travis
16
+ # - jruby-18mode
17
+ # - jruby-19mode
13
18
 
14
19
  bundler_args: --without development docs
15
20
 
data/Gemfile CHANGED
@@ -6,7 +6,7 @@ gem 'rake', :group => [:development, :test]
6
6
  gem 'pry', :group => [:development]
7
7
 
8
8
  group :docs do
9
- gem 'yard', '~> 0.7.5'
9
+ gem 'yard', '~> 0.8.0'
10
10
 
11
11
  platform :mri_19 do
12
12
  gem 'redcarpet'
@@ -16,8 +16,7 @@ end
16
16
  group :test do
17
17
  gem 'rspec', '~> 2.8.0'
18
18
  gem 'flexmock', '~> 0.8.10'
19
- # gem 'zk-server', :path => '~/mbox/zk-server'
20
- gem 'zk-server', '~> 0.9.1'
19
+ gem 'zk-server', '~> 1.0.1'
21
20
  end
22
21
 
23
22
  # Specify your gem's dependencies in zk.gemspec
data/README.markdown CHANGED
@@ -18,6 +18,13 @@ Development is sponsored by [Snapfish][] and has been generously released to the
18
18
  [MIT]: http://www.gnu.org/licenses/license-list.html#Expat "MIT (Expat) License"
19
19
  [Snapfish]: http://www.snapfish.com/ "Snapfish"
20
20
 
21
+ ## Contacting the author
22
+
23
+ * I'm usually hanging out in IRC on freenode.net in the BRAND NEW #zk-gem channel.
24
+ * if you really want to, you can also reach me via twitter [@slyphon][]
25
+
26
+ [@slyphon]: https://twitter.com/#!/slyphon
27
+
21
28
  ## New in 1.1 !! ##
22
29
 
23
30
  * 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.
@@ -161,11 +168,4 @@ ZK strives to be a complete, correct, and convenient way of interacting with Zoo
161
168
  [szk-jar-gem]: https://rubygems.org/gems/slyphon-zookeeper_jar
162
169
  [szk-jar-repo]: https://github.com/slyphon/zookeeper_jar
163
170
 
164
- ## Contacting the author
165
-
166
- * Send me a github message (slyphon)
167
- * I'm usually hanging out in IRC on freenode.net in #ruby-lang and in #zookeeper
168
- * if you really want to, you can also reach me via twitter [@slyphon][]
169
-
170
- [@slyphon]: https://twitter.com/#!/slyphon
171
171
 
data/RELEASES.markdown CHANGED
@@ -1,4 +1,22 @@
1
1
  This file notes feature differences and bugfixes contained between releases.
2
+ ### v1.1.1 ###
3
+
4
+ * Documentation for Locker and ilk
5
+
6
+ * Documentation cleanup
7
+
8
+ * Fixes for Locker tests so that we can run specs against all supported ruby implementations on travis (relies on in-process zookeeper server in the zk-server-1.0.1 gem)
9
+
10
+ * Support for 1.8.7 will be continued
11
+
12
+ ## v1.1.0 ##
13
+
14
+ (forgot to put this here, put it in the readme though)
15
+
16
+ * 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.
17
+
18
+ * 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!
19
+
2
20
 
3
21
  ### v1.0.0 ###
4
22
 
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ gemset_name = 'zk'
4
4
 
5
5
  GEMSPEC_NAME = 'zk.gemspec'
6
6
 
7
- %w[1.8.7 1.9.2 jruby rbx 1.9.3].each do |ns_name|
7
+ %w[1.8.7 1.9.2 jruby rbx ree 1.9.3].each do |ns_name|
8
8
  rvm_ruby = (ns_name == 'rbx') ? "rbx-2.0.testing" : ns_name
9
9
 
10
10
  ruby_with_gemset = "#{rvm_ruby}@#{gemset_name}"
@@ -59,6 +59,10 @@ namespace :yard do
59
59
  task :server => :clean do
60
60
  sh "yard server --reload"
61
61
  end
62
+
63
+ task :gems do
64
+ sh 'yard server --gems --port=8809'
65
+ end
62
66
  end
63
67
 
64
68
  task :clean => 'yard:clean'
@@ -70,7 +74,7 @@ namespace :spec do
70
74
  require 'rspec/core/rake_task'
71
75
 
72
76
  RSpec::Core::RakeTask.new('spec:runner') do |t|
73
- t.rspec_opts = '-f d'
77
+ t.rspec_opts = '-f d' if ENV['TRAVIS']
74
78
  end
75
79
  end
76
80
 
@@ -1,8 +1,10 @@
1
1
  module ZK
2
2
  module Client
3
- # EXTENSIONS
3
+ # Convenience methods for creating instances of the cluster coordination
4
+ # objects ZK provides, using the current connection.
5
+ #
6
+ # Mixed into {ZK::Client::Threaded}
4
7
  #
5
- # convenience methods for dealing with zookeeper (rm -rf, mkdir -p, etc)
6
8
  module Conveniences
7
9
  # Queue an operation to be run on an internal threadpool. You may either
8
10
  # provide an object that responds_to?(:call) or pass a block. There is no
@@ -18,6 +20,7 @@ module ZK
18
20
  # @yield [] the block that should be run in the threadpool, if `callable`
19
21
  # isn't given
20
22
  #
23
+ # @private
21
24
  def defer(callable=nil, &block)
22
25
  @threadpool.defer(callable, &block)
23
26
  end
@@ -33,55 +36,53 @@ module ZK
33
36
  false
34
37
  end
35
38
 
36
- # creates a new locker based on the name you send in
37
- #
38
- # @see ZK::Locker::ExclusiveLocker
39
- #
40
- # returns a ZK::Locker::ExclusiveLocker instance using this Client and provided
41
- # lock name
39
+ # Creates a new locker based on the name you provide, using this client
40
+ # as the connection.
42
41
  #
43
- # ==== Arguments
44
- # * <tt>name</tt> name of the lock you wish to use
42
+ # @param name [String] the name of the lock you wish to use. see
43
+ # {ZK::Locker} for a description of how the name is used to generate a
44
+ # key.
45
45
  #
46
- # ==== Examples
47
- #
48
- # zk.locker("blah")
49
- # # => #<ZK::Locker::ExclusiveLocker:0x102034cf8 ...>
46
+ # @return [Locker::ExclusiveLocker] instance using this Client and
47
+ # provided lock name.
50
48
  #
51
49
  def locker(name)
52
50
  Locker.exclusive_locker(self, name)
53
51
  end
52
+ alias exclusive_locker locker
54
53
 
55
54
  # create a new shared locking instance based on the name given
56
55
  #
57
- # returns a ZK::Locker::SharedLocker instance using this Client and provided
58
- # lock name
59
- #
60
- # ==== Arguments
61
- # * <tt>name</tt> name of the lock you wish to use
62
- #
63
- # ==== Examples
56
+ # @param name (see #locker)
64
57
  #
65
- # zk.shared_locker("blah")
66
- # # => #<ZK::Locker::SharedLocker:0x102034cf8 ...>
58
+ # @return [Locker::SharedLocker] instance using this Client and provided
59
+ # lock name.
67
60
  #
68
61
  def shared_locker(name)
69
62
  Locker.shared_locker(self, name)
70
63
  end
71
64
 
72
65
  # Convenience method for acquiring a lock then executing a code block. This
73
- # will block the caller until the lock is acquired.
66
+ # will block the caller until the lock is acquired, and release the lock
67
+ # when the block is exited.
74
68
  #
75
- # ==== Arguments
76
- # * <tt>name</tt>: the name of the lock to use
77
- # * <tt>:mode</tt>: either :shared or :exclusive, defaults to :exclusive
69
+ # @param name (see #locker)
78
70
  #
79
- # ==== Examples
71
+ # @option opts [:shared,:exclusive] :mode (:exclusive) the type of lock
72
+ # to create and then call with_lock on
73
+ #
74
+ # @return the return value of the given block
75
+ #
76
+ # @yield calls the block once the lock has been acquired
77
+ #
78
+ # @example
80
79
  #
81
80
  # zk.with_lock('foo') do
82
81
  # # this code is executed while holding the lock
83
82
  # end
84
83
  #
84
+ # @raise [ArgumentError] if `opts[:mode]` is not one of the expected values
85
+ #
85
86
  def with_lock(name, opts={}, &b)
86
87
  mode = opts[:mode] || :exclusive
87
88
 
@@ -94,29 +95,38 @@ module ZK
94
95
  end
95
96
  end
96
97
 
97
- # Convenience method for constructing a ZK::Election::Candidate object using this
98
- # Client connection, the given election +name+ and +data+.
98
+ # Constructs an {Election::Candidate} object using self as the connection
99
+ #
100
+ # @param [String] name the name of the election to participate in
101
+ # @param [String] data the data we will write to the leadership node if/when we win
99
102
  #
103
+ # @return [Election::Candidate] the candidate instance using self as a connection
100
104
  def election_candidate(name, data, opts={})
101
105
  opts = opts.merge(:data => data)
102
106
  ZK::Election::Candidate.new(self, name, opts)
103
107
  end
104
108
 
105
- # Convenience method for constructing a ZK::Election::Observer object using this
106
- # Client connection, and the given election +name+.
109
+ # Constructs an {Election::Observer} object using self as the connection
110
+ #
111
+ # @param name (see #election_candidate)
107
112
  #
113
+ # @return [Election::Observer] the candidate instance using self as a connection
108
114
  def election_observer(name, opts={})
109
115
  ZK::Election::Observer.new(self, name, opts)
110
116
  end
111
117
 
112
- # creates a new message queue of name +name+
118
+ # creates a new message queue of name `name`
119
+ #
120
+ # @note The message queue has some scalability limitations. For
121
+ # heavy-duty message processing, the author recommends investigating
122
+ # a purpose-built solution.
113
123
  #
114
- # returns a ZK::MessageQueue object
124
+ # @return [MessageQueue] the new instance using self as its
125
+ # client
115
126
  #
116
- # ==== Arguments
117
- # * <tt>name</tt> the name of the queue
127
+ # @param [String] name the name of the queue
118
128
  #
119
- # ==== Examples
129
+ # @example
120
130
  #
121
131
  # zk.queue("blah").publish({:some_data => "that is yaml serializable"})
122
132
  #
@@ -3,7 +3,7 @@ module ZK
3
3
  # This is the default client that ZK will use. In the zk-eventmachine gem,
4
4
  # there is an Evented client.
5
5
  #
6
- # If you want to register `on_*` callbacks (see ZK::Client::StateMixin)
6
+ # If you want to register `on_*` callbacks (see {ZK::Client::StateMixin})
7
7
  # then you should pass a block, which will be called before the
8
8
  # connection is set up (this way you can get the `on_connected` event). See
9
9
  # the 'Register on_connected callback' example.
@@ -26,7 +26,7 @@ module ZK
26
26
  # # the nice thing about this pattern is that in the case of a call to #reopen
27
27
  # # all your watches will be re-established
28
28
  #
29
- # ZK::Client::Threaded.new('localhsot:2181') do |zk|
29
+ # ZK::Client::Threaded.new('localhost:2181') do |zk|
30
30
  # # do not do anything in here except register callbacks
31
31
  #
32
32
  # zk.on_connected do |event|
@@ -45,6 +45,12 @@ module ZK
45
45
 
46
46
  # Construct a new threaded client.
47
47
  #
48
+ # Pay close attention to the `:threaded` option, and have a look at the
49
+ # [EventDeliveryModel](https://github.com/slyphon/zk/wiki/EventDeliveryModel)
50
+ # page in the wiki for a discussion of the relative advantages and
51
+ # disadvantages of the choices available. The default is safe, but the
52
+ # alternative will likely provide better performance.
53
+ #
48
54
  # @note The `:timeout` argument here is *not* the session_timeout for the
49
55
  # connection. rather it is the amount of time we wait for the connection
50
56
  # to be established. The session timeout exchanged with the server is
@@ -68,12 +74,15 @@ module ZK
68
74
  # [this wiki article](https://github.com/slyphon/zk/wiki/EventDeliveryModel) for more
69
75
  # information and a demonstration.
70
76
  #
71
- # @param [String] host (see ZK::Client::Base#initialize)
77
+ # @param host (see Base#initialize)
72
78
  #
73
79
  # @option opts [true,false] :reconnect (true) if true, we will register
74
80
  # the equivalent of `on_session_expired { zk.reopen }` so that in the
75
81
  # case of an expired session, we will keep trying to reestablish the
76
- # connection.
82
+ # connection. You *almost definately* want to leave this at the default.
83
+ # The only reason not to is if you already have a handler registered
84
+ # that does something application specific, and you want to avoid a
85
+ # conflict.
77
86
  #
78
87
  # @option opts [:single,:per_callback] :thread (:single) choose your event
79
88
  # delivery model:
@@ -112,6 +121,8 @@ module ZK
112
121
  # operations with the client as you will get a NoMethodError (the
113
122
  # underlying connection is nil).
114
123
  #
124
+ # @return [Threaded] a new client instance
125
+ #
115
126
  # @see Base#initialize
116
127
  def initialize(host, opts={}, &b)
117
128
  super(host, opts)
@@ -2,6 +2,7 @@ module ZK
2
2
  module Client
3
3
  module Unixisms
4
4
  include ZookeeperConstants
5
+ include Exceptions
5
6
 
6
7
  # Creates all parent paths and 'path' in zookeeper as persistent nodes with
7
8
  # zero data.
@@ -21,12 +22,12 @@ module ZK
21
22
  # this could get expensive w/ psychotically long paths
22
23
 
23
24
  create(path, '', :mode => :persistent)
24
- rescue Exceptions::NodeExists
25
+ rescue NodeExists
25
26
  return
26
- rescue Exceptions::NoNode
27
+ rescue NoNode
27
28
  if File.dirname(path) == '/'
28
29
  # ok, we're screwed, blow up
29
- raise Exceptions::NonExistentRootError, "could not create '/', are you chrooted into a non-existent path?", caller
30
+ raise NonExistentRootError, "could not create '/', are you chrooted into a non-existent path?", caller
30
31
  end
31
32
 
32
33
  mkdir_p(File.dirname(path))
@@ -43,7 +44,7 @@ module ZK
43
44
 
44
45
  delete(path)
45
46
  nil
46
- rescue Exceptions::NoNode
47
+ rescue NoNode
47
48
  end
48
49
  end
49
50
  end
@@ -103,19 +104,27 @@ module ZK
103
104
  ZK::Find.find(self, *paths, &block)
104
105
  end
105
106
 
106
- # Will _safely_ block the caller until `abs_node_path` has been removed.
107
- # This is trickier than it first appears. This method will wake the caller
108
- # if a session event occurs that would ensure the event would never be
109
- # delivered, and also checks to make sure that the caller is not calling
110
- # from the event distribution thread (which would cause a deadlock).
111
- #
112
- # @note this is dangerous to use in callbacks! there is only one
113
- # event-delivery thread, so if you use this method in a callback or
114
- # watcher, you *will* deadlock!
107
+ # Will _safely_ block the caller until `abs_node_path` has been removed
108
+ # (this is trickier than it appears at first). This method will wake the
109
+ # caller if a session event occurs that would ensure the event would
110
+ # never be delivered.
115
111
  #
116
112
  # @raise [Exceptions::InterruptedSession] If a session event occurs while we're
117
113
  # blocked waiting for the node to be deleted, an exception that
118
- # mixes in the InterruptedSession module will be raised.
114
+ # mixes in the InterruptedSession module will be raised, so for convenience,
115
+ # users can just rescue {InterruptedSession}.
116
+ #
117
+ # @raise [ZookeeperExceptions::ZookeeperException::SessionExpired] raised
118
+ # when we receive `ZOO_EXPIRED_SESSION_STATE` while blocking waiting for
119
+ # a deleted event. Includes the {InterruptedSession} module.
120
+ #
121
+ # @raise [ZookeeperExceptions::ZookeeperException::NotConnected] raised
122
+ # when we receive `ZOO_CONNECTING_STATE` while blocking waiting for
123
+ # a deleted event. Includes the {InterruptedSession} module.
124
+ #
125
+ # @raise [ZookeeperExceptions::ZookeeperException::ConnectionClosed] raised
126
+ # when we receive `ZOO_CLOSED_STATE` while blocking waiting for
127
+ # a deleted event. Includes the {InterruptedSession} module.
119
128
  #
120
129
  def block_until_node_deleted(abs_node_path)
121
130
  subs = []
data/lib/zk/client.rb CHANGED
@@ -23,6 +23,7 @@ module ZK
23
23
  }.freeze unless defined?(STATE_SYM_MAP)
24
24
 
25
25
  class << self
26
+ # (see Threaded#initialize)
26
27
  def new(*a, &b)
27
28
  Threaded.new(*a, &b)
28
29
  end
@@ -5,7 +5,7 @@ module ZK
5
5
  #
6
6
  # you never really need to initialize this yourself
7
7
  class EventHandler
8
- include org.apache.zookeeper.Watcher if defined?(JRUBY_VERSION)
8
+ include Java::OrgApacheZookeeper::Watcher if defined?(JRUBY_VERSION)
9
9
  include ZK::Logging
10
10
 
11
11
  # @private
data/lib/zk/exceptions.rb CHANGED
@@ -74,27 +74,30 @@ module ZK
74
74
  include InterruptedSession
75
75
  end
76
76
 
77
- ERROR_MAP = {
78
- SYSTEMERROR => SystemError,
79
- RUNTIMEINCONSISTENCY => RunTimeInconsistency,
80
- DATAINCONSISTENCY => DataInconsistency,
81
- CONNECTIONLOSS => ConnectionLoss,
82
- MARSHALLINGERROR => MarshallingError,
83
- UNIMPLEMENTED => Unimplemented,
84
- OPERATIONTIMEOUT => OperationTimeOut,
85
- BADARGUMENTS => BadArguments,
86
- APIERROR => ApiError,
87
- NONODE => NoNode,
88
- NOAUTH => NoAuth,
89
- BADVERSION => BadVersion,
90
- NOCHILDRENFOREPHEMERALS => NoChildrenForEphemerals,
91
- NODEEXISTS => NodeExists,
92
- NOTEMPTY => NotEmpty,
93
- SESSIONEXPIRED => SessionExpired,
94
- INVALIDCALLBACK => InvalidCallback,
95
- INVALIDACL => InvalidACL,
96
- AUTHFAILED => AuthFailed,
97
- }.freeze unless defined?(ERROR_MAP)
77
+ silence_warnings do
78
+ # @private
79
+ ERROR_MAP = {
80
+ SYSTEMERROR => SystemError,
81
+ RUNTIMEINCONSISTENCY => RunTimeInconsistency,
82
+ DATAINCONSISTENCY => DataInconsistency,
83
+ CONNECTIONLOSS => ConnectionLoss,
84
+ MARSHALLINGERROR => MarshallingError,
85
+ UNIMPLEMENTED => Unimplemented,
86
+ OPERATIONTIMEOUT => OperationTimeOut,
87
+ BADARGUMENTS => BadArguments,
88
+ APIERROR => ApiError,
89
+ NONODE => NoNode,
90
+ NOAUTH => NoAuth,
91
+ BADVERSION => BadVersion,
92
+ NOCHILDRENFOREPHEMERALS => NoChildrenForEphemerals,
93
+ NODEEXISTS => NodeExists,
94
+ NOTEMPTY => NotEmpty,
95
+ SESSIONEXPIRED => SessionExpired,
96
+ INVALIDCALLBACK => InvalidCallback,
97
+ INVALIDACL => InvalidACL,
98
+ AUTHFAILED => AuthFailed,
99
+ }.freeze
100
+ end
98
101
 
99
102
  # base class of ZK generated errors (not driver-level errors)
100
103
  class ZKError < StandardError; end
@@ -0,0 +1,75 @@
1
+ module ZK
2
+ module Locker
3
+ # An exclusive lock implementation
4
+ #
5
+ # If the name 'dingus' is given, then in the case of an exclusive lock, the
6
+ # algorithm works like:
7
+ #
8
+ # * lock_path = `zk.create("/_zklocking/dingus/ex", :sequential => true, :ephemeral => true)`
9
+ # * extract the digit from the lock path
10
+ # * of all the children under '/_zklocking/dingus', do we have the lowest digit?
11
+ # * __yes__: then we hold the lock, if we're non-blocking, return true
12
+ # * __no__: is the lock blocking?
13
+ # * __yes__: then set a watch on the next-to-lowest node and sleep the current thread until that node has been deleted
14
+ # * __no__: return false, you lose
15
+ #
16
+ class ExclusiveLocker < LockerBase
17
+ # obtain an exclusive lock.
18
+ #
19
+ # @param blocking (see SharedLocker#lock!)
20
+ # @return (see SharedLocker#lock!)
21
+ #
22
+ # @raise [InterruptedSession] raised when blocked waiting for a lock and
23
+ # the underlying client's session is interrupted.
24
+ #
25
+ # @see ZK::Client::Unixisms#block_until_node_deleted more about possible execptions
26
+ #
27
+ def lock!(blocking=false)
28
+ return true if @locked
29
+ create_lock_path!(EXCLUSIVE_LOCK_PREFIX)
30
+
31
+ if got_write_lock?
32
+ @locked = true
33
+ elsif blocking
34
+ in_waiting_status do
35
+ block_until_write_lock!
36
+ end
37
+ else
38
+ cleanup_lock_path!
39
+ false
40
+ end
41
+ end
42
+
43
+ protected
44
+ # the node that is next-lowest in sequence number to ours, the one we
45
+ # watch for updates to
46
+ # @private
47
+ def next_lowest_node
48
+ ary = ordered_lock_children()
49
+ my_idx = ary.index(lock_basename)
50
+
51
+ raise WeAreTheLowestLockNumberException if my_idx == 0
52
+
53
+ ary[(my_idx - 1)]
54
+ end
55
+
56
+ # @private
57
+ def got_write_lock?
58
+ ordered_lock_children.first == lock_basename
59
+ end
60
+
61
+ # @private
62
+ def block_until_write_lock!
63
+ begin
64
+ path = [root_lock_path, next_lowest_node].join('/')
65
+ logger.debug { "SharedLocker#block_until_write_lock! path=#{path.inspect}" }
66
+ @zk.block_until_node_deleted(path)
67
+ rescue WeAreTheLowestLockNumberException
68
+ end
69
+
70
+ @locked = true
71
+ end
72
+ end # ExclusiveLocker
73
+ end # Locker
74
+ end # ZK
75
+