zk 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
+