zk 1.1.1 → 1.2.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.
@@ -1,7 +1,7 @@
1
1
  module ZK
2
2
  module Client
3
3
  module Unixisms
4
- include ZookeeperConstants
4
+ include Zookeeper::Constants
5
5
  include Exceptions
6
6
 
7
7
  # Creates all parent paths and 'path' in zookeeper as persistent nodes with
@@ -114,15 +114,15 @@ module ZK
114
114
  # mixes in the InterruptedSession module will be raised, so for convenience,
115
115
  # users can just rescue {InterruptedSession}.
116
116
  #
117
- # @raise [ZookeeperExceptions::ZookeeperException::SessionExpired] raised
117
+ # @raise [Zookeeper::Exceptions::SessionExpired] raised
118
118
  # when we receive `ZOO_EXPIRED_SESSION_STATE` while blocking waiting for
119
119
  # a deleted event. Includes the {InterruptedSession} module.
120
120
  #
121
- # @raise [ZookeeperExceptions::ZookeeperException::NotConnected] raised
121
+ # @raise [Zookeeper::Exceptions::NotConnected] raised
122
122
  # when we receive `ZOO_CONNECTING_STATE` while blocking waiting for
123
123
  # a deleted event. Includes the {InterruptedSession} module.
124
124
  #
125
- # @raise [ZookeeperExceptions::ZookeeperException::ConnectionClosed] raised
125
+ # @raise [Zookeeper::Exceptions::ConnectionClosed] raised
126
126
  # when we receive `ZOO_CLOSED_STATE` while blocking waiting for
127
127
  # a deleted event. Includes the {InterruptedSession} module.
128
128
  #
@@ -162,11 +162,11 @@ module ZK
162
162
  when :deleted
163
163
  true
164
164
  when ZOO_EXPIRED_SESSION_STATE
165
- raise ZookeeperExceptions::ZookeeperException::SessionExpired
165
+ raise Zookeeper::Exceptions::SessionExpired
166
166
  when ZOO_CONNECTING_STATE
167
- raise ZookeeperExceptions::ZookeeperException::NotConnected
167
+ raise Zookeeper::Exceptions::NotConnected
168
168
  when ZOO_CLOSED_STATE
169
- raise ZookeeperExceptions::ZookeeperException::ConnectionClosed
169
+ raise Zookeeper::Exceptions::ConnectionClosed
170
170
  else
171
171
  raise "Hit unexpected case in block_until_node_deleted"
172
172
  end
data/lib/zk/core_ext.rb CHANGED
@@ -73,3 +73,29 @@ module ::Kernel
73
73
  end
74
74
  end
75
75
 
76
+ # @private
77
+ class ::Module
78
+ unless method_defined?(:alias_method_chain)
79
+ def alias_method_chain(target, feature)
80
+ # Strip out punctuation on predicates or bang methods since
81
+ # e.g. target?_without_feature is not a valid method name.
82
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
83
+ yield(aliased_target, punctuation) if block_given?
84
+
85
+ with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
86
+
87
+ alias_method without_method, target
88
+ alias_method target, with_method
89
+
90
+ case
91
+ when public_method_defined?(without_method)
92
+ public target
93
+ when protected_method_defined?(without_method)
94
+ protected target
95
+ when private_method_defined?(without_method)
96
+ private target
97
+ end
98
+ end
99
+ end
100
+ end
101
+
data/lib/zk/event.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  module ZK
2
2
  # Provides most of the functionality ZK uses around events. Base class is actually
3
- # [ZookeeperCallbacks::WatcherCallback](http://rubydoc.info/gems/slyphon-zookeeper/ZookeeperCallbacks/WatcherCallback),
3
+ # [Zookeeper::Callbacks::WatcherCallback](http://rubydoc.info/gems/slyphon-zookeeper/Zookeeper::Callbacks/WatcherCallback),
4
4
  # but this module is mixed in and provides a lot of useful syntactic sugar.
5
5
  #
6
6
  module Event
7
- include ZookeeperConstants
7
+ include Zookeeper::Constants
8
8
 
9
9
  # unless defined? apparently messes up yard's ability to see the @private
10
10
  silence_warnings do
@@ -26,10 +26,10 @@ module ZK
26
26
  EVENT_TYPES = %w[created deleted changed child session notwatching].freeze
27
27
  end
28
28
 
29
- # for testing, create a new ZookeeperCallbacks::WatcherCallback and return it
29
+ # for testing, create a new Zookeeper::Callbacks::WatcherCallback and return it
30
30
  # @private
31
31
  def self.new(hash)
32
- ZookeeperCallbacks::WatcherCallback.new.tap do |wc|
32
+ Zookeeper::Callbacks::WatcherCallback.new.tap do |wc|
33
33
  wc.call(hash)
34
34
  end
35
35
  end
@@ -170,7 +170,7 @@ module ZK
170
170
  end
171
171
 
172
172
  # @private
173
- class ZookeeperCallbacks::WatcherCallback
173
+ Zookeeper::Callbacks::WatcherCallback.class_eval do
174
174
  include ::ZK::Event
175
175
  end
176
176
 
@@ -257,7 +257,7 @@ module ZK
257
257
  protected
258
258
  # @private
259
259
  def watcher_callback
260
- ZookeeperCallbacks::WatcherCallback.create { |event| process(event) }
260
+ Zookeeper::Callbacks::WatcherCallback.create { |event| process(event) }
261
261
  end
262
262
 
263
263
  # @private
@@ -265,7 +265,7 @@ module ZK
265
265
  int =
266
266
  case arg
267
267
  when String, Symbol
268
- ZookeeperConstants.const_get(:"ZOO_#{arg.to_s.upcase}_STATE")
268
+ Zookeeper::Constants.const_get(:"ZOO_#{arg.to_s.upcase}_STATE")
269
269
  when Integer
270
270
  arg
271
271
  else
@@ -16,19 +16,7 @@ module ZK
16
16
  # guarantees), just perhaps at different times.
17
17
  #
18
18
  class Actor < Base
19
- extend Forwardable
20
-
21
- def_delegators :@threaded_callback, :call
22
-
23
- def initialize(*a)
24
- super
25
- @threaded_callback = ThreadedCallback.new(@callback)
26
- end
27
-
28
- def unsubscribe
29
- @threaded_callback.shutdown
30
- super
31
- end
19
+ include Subscription::ActorStyle
32
20
 
33
21
  def async?
34
22
  true
@@ -1,19 +1,15 @@
1
1
  module ZK
2
2
  module EventHandlerSubscription
3
- class Base
3
+ class Base < Subscription::Base
4
4
  include ZK::Logging
5
5
 
6
- # the event handler associated with this subscription
7
- # @return [EventHandler]
8
- attr_accessor :event_handler
9
-
10
6
  # the path this subscription is for
11
7
  # @return [String]
12
8
  attr_accessor :path
13
9
 
14
10
  # the block associated with the path
15
11
  # @return [Proc]
16
- attr_accessor :callback
12
+ # attr_accessor :callback
17
13
 
18
14
  # an array of what kinds of events this handler is interested in receiving
19
15
  # this is the :only option, essentially
@@ -24,26 +20,30 @@ module ZK
24
20
  # @private
25
21
  attr_accessor :interests
26
22
 
23
+ alias event_handler parent
24
+ alias callback callable
25
+
27
26
  ALL_EVENTS = [:created, :deleted, :changed, :child].freeze unless defined?(ALL_EVENTS)
28
27
  ALL_EVENT_SET = Set.new(ALL_EVENTS).freeze unless defined?(ALL_EVENT_SET)
29
28
 
30
29
  # @private
31
30
  def initialize(event_handler, path, callback, opts={})
32
- @event_handler, @path, @callback = event_handler, path, callback
31
+ super(event_handler, callback)
32
+ @path = path
33
33
  @interests = prep_interests(opts[:only])
34
34
  end
35
35
 
36
36
  # unsubscribe from the path or state you were watching
37
37
  # @see ZK::Client::Base#register
38
- def unsubscribe
39
- @event_handler.unregister(self)
40
- end
41
- alias :unregister :unsubscribe
38
+ # def unsubscribe
39
+ # @event_handler.unregister(self)
40
+ # end
41
+ # alias :unregister :unsubscribe
42
42
 
43
- # @private
44
- def call(event)
45
- callback.call(event)
46
- end
43
+ # # @private
44
+ # def call(event)
45
+ # callback.call(event)
46
+ # end
47
47
 
48
48
  # the Actor returns true for this
49
49
  # @private
data/lib/zk/exceptions.rb CHANGED
@@ -126,6 +126,9 @@ module ZK
126
126
  # raised when someone performs a blocking ZK operation on the event dispatch thread.
127
127
  class EventDispatchThreadException < ZKError; end
128
128
 
129
+ # raised when someone calls lock.assert! but they do not hold the lock
130
+ class LockAssertionFailedError < ZKError; end
131
+
129
132
  # raised when a chrooted conection is requested but the root doesn't exist
130
133
  class ChrootPathDoesNotExistError < NoNode
131
134
  def initialize(host_string, chroot_path)
data/lib/zk/extensions.rb CHANGED
@@ -30,49 +30,20 @@ module ZK
30
30
  end
31
31
  end
32
32
  end
33
-
34
- module Extensions
35
- # some extensions to the ZookeeperCallbacks classes, mainly convenience
36
- # interrogators
37
- module Callbacks
38
- module Callback
39
- extend Concern
40
-
41
- # allow access to the connection that fired this callback
42
- attr_accessor :zk
43
-
44
- module ClassMethods
45
- # allows for easier construction of a user callback block that will be
46
- # called with the callback object itself as an argument.
47
- #
48
- # *args, if given, will be passed on *after* the callback
49
- #
50
- # @example
51
- #
52
- # WatcherCallback.create do |cb|
53
- # puts "watcher callback called with argument: #{cb.inspect}"
54
- # end
55
- #
56
- # "watcher callback called with argument: #<ZookeeperCallbacks::WatcherCallback:0x1018a3958 @state=3, @type=1, ...>"
57
- #
58
- #
59
- def create(*args, &block)
60
- cb_inst = new { block.call(cb_inst) }
61
- end
62
- end
63
- end # Callback
64
- end # Callbacks
65
- end # Extensions
66
33
  end # ZK
67
34
 
68
- # ZookeeperCallbacks::Callback.extend(ZK::Extensions::Callbacks::Callback)
69
- ZookeeperCallbacks::Callback.send(:include, ZK::Extensions::Callbacks::Callback)
35
+ Zookeeper::Callbacks::Base.class_eval do
36
+ # allows us to stick a reference to the connection associated with the event
37
+ # on the event
38
+ attr_accessor :zk
39
+ end
40
+
70
41
 
71
- # Include the InterruptedSession module in key ZookeeperExceptions to allow
42
+ # Include the InterruptedSession module in key Zookeeper::Exceptions to allow
72
43
  # clients to catch a single error type when waiting on a node (for example)
73
44
 
74
45
  [:ConnectionClosed, :NotConnected, :SessionExpired, :SessionMoved, :ConnectionLoss].each do |class_name|
75
- ZookeeperExceptions::ZookeeperException.const_get(class_name).tap do |klass|
46
+ Zookeeper::Exceptions.const_get(class_name).tap do |klass|
76
47
  klass.__send__(:include, ZK::Exceptions::InterruptedSession)
77
48
  end
78
49
  end
data/lib/zk/locker.rb CHANGED
@@ -93,6 +93,14 @@ module ZK
93
93
  SHARED_LOCK_PREFIX = 'sh'.freeze
94
94
  EXCLUSIVE_LOCK_PREFIX = 'ex'.freeze
95
95
 
96
+ @default_root_lock_node = '/_zklocking'.freeze unless @default_root_lock_node
97
+
98
+ class << self
99
+ # the default root path we will use when a value is not given to a
100
+ # constructor
101
+ attr_accessor :default_root_lock_node
102
+ end
103
+
96
104
  # Create a {SharedLocker} instance
97
105
  #
98
106
  # @param client (see LockerBase#initialize)
@@ -14,17 +14,10 @@ module ZK
14
14
  # * __no__: return false, you lose
15
15
  #
16
16
  class ExclusiveLocker < LockerBase
17
+ # (see LockerBase#lock)
17
18
  # obtain an exclusive lock.
18
19
  #
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)
20
+ def lock(blocking=false)
28
21
  return true if @locked
29
22
  create_lock_path!(EXCLUSIVE_LOCK_PREFIX)
30
23
 
@@ -40,10 +33,31 @@ module ZK
40
33
  end
41
34
  end
42
35
 
36
+ # (see LockerBase#assert!)
37
+ #
38
+ # checks that we:
39
+ #
40
+ # * we have obtained the lock (i.e. {#locked?} is true)
41
+ # * have a lock path
42
+ # * our lock path still exists
43
+ # * there are no locks, _exclusive or shared_, with lower numbers than ours
44
+ #
45
+ def assert!
46
+ super
47
+ end
48
+
49
+ # (see LockerBase#acquirable?)
50
+ def acquirable?
51
+ return true if locked?
52
+ stat = zk.stat(root_lock_path)
53
+ !stat.exists? or stat.num_children == 0
54
+ rescue Exceptions::NoNode
55
+ true
56
+ end
57
+
43
58
  protected
44
59
  # the node that is next-lowest in sequence number to ours, the one we
45
60
  # watch for updates to
46
- # @private
47
61
  def next_lowest_node
48
62
  ary = ordered_lock_children()
49
63
  my_idx = ary.index(lock_basename)
@@ -53,12 +67,11 @@ module ZK
53
67
  ary[(my_idx - 1)]
54
68
  end
55
69
 
56
- # @private
57
70
  def got_write_lock?
58
71
  ordered_lock_children.first == lock_basename
59
72
  end
73
+ alias got_lock? got_write_lock?
60
74
 
61
- # @private
62
75
  def block_until_write_lock!
63
76
  begin
64
77
  path = [root_lock_path, next_lowest_node].join('/')
@@ -9,6 +9,7 @@ module ZK
9
9
  #
10
10
  class LockerBase
11
11
  include ZK::Logging
12
+ include ZK::Exceptions
12
13
 
13
14
  # @private
14
15
  attr_accessor :zk
@@ -42,14 +43,15 @@ module ZK
42
43
  # holding the same lock.
43
44
  #
44
45
  # @param [String] root_lock_node the root path on the server under which all
45
- # locks will be generated
46
+ # locks will be generated, the default is Locker.default_root_lock_node
46
47
  #
47
- def initialize(client, name, root_lock_node = "/_zklocking")
48
+ def initialize(client, name, root_lock_node=nil)
48
49
  @zk = client
49
- @root_lock_node = root_lock_node
50
+ @root_lock_node = root_lock_node || Locker.default_root_lock_node
50
51
  @path = name
51
52
  @locked = false
52
53
  @waiting = false
54
+ @lock_path = nil
53
55
  @root_lock_path = "#{@root_lock_node}/#{@path.gsub("/", "__")}"
54
56
  end
55
57
 
@@ -58,10 +60,10 @@ module ZK
58
60
  # there is no non-blocking version of this method
59
61
  #
60
62
  def with_lock
61
- lock!(true)
63
+ lock(true)
62
64
  yield
63
65
  ensure
64
- unlock!
66
+ unlock
65
67
  end
66
68
 
67
69
  # the basename of our lock path
@@ -79,9 +81,36 @@ module ZK
79
81
  lock_path and File.basename(lock_path)
80
82
  end
81
83
 
82
- # @return [true,false] true if we hold the lock
83
- def locked?
84
- false|@locked
84
+ # returns our current idea of whether or not we hold the lock, which does
85
+ # not actually check the state on the server.
86
+ #
87
+ # The reason for the equivocation around _thinking_ we hold the lock is
88
+ # to contrast our current state and the actual state on the server. If you
89
+ # want to make double-triple certain of the state of the lock, use {#assert!}
90
+ #
91
+ # @return [true] if we hold the lock
92
+ # @return [false] if we don't hold the lock
93
+ #
94
+ def locked?(check_if_any=false)
95
+ false|@locked
96
+ end
97
+
98
+ # * If this instance holds the lock {#locked? is true} we return true (as
99
+ # we have already succeeded in acquiring the lock)
100
+ # * If this instance doesn't hold the lock, we'll do a check on the server
101
+ # to see if there are any participants _who hold the lock and would
102
+ # prevent us from acquiring the lock_.
103
+ # * If this instance could acquire the lock we will return true.
104
+ # * If another client would prevent us from acquiring the lock, we return false.
105
+ #
106
+ # @note It should be obvious, but there is no way to guarantee that
107
+ # between the time this method checks the server and taking any action to
108
+ # acquire the lock, another client may grab the lock before us (or
109
+ # converseley, another client may release the lock). This is simply meant
110
+ # as an advisory, and may be useful in some cases.
111
+ #
112
+ def acquirable?
113
+ raise NotImplementedError
85
114
  end
86
115
 
87
116
  # @return [true] if we held the lock and this method has
@@ -89,7 +118,7 @@ module ZK
89
118
  #
90
119
  # @return [false] we did not own the lock
91
120
  #
92
- def unlock!
121
+ def unlock
93
122
  if @locked
94
123
  cleanup_lock_path!
95
124
  @locked = false
@@ -99,6 +128,38 @@ module ZK
99
128
  end
100
129
  end
101
130
 
131
+ # (see #unlock)
132
+ # @deprecated the use of unlock! is deprecated and may be removed or have
133
+ # its semantics changed in a future release
134
+ def unlock!
135
+ unlock
136
+ end
137
+
138
+ # @param blocking [true,false] if true we block the caller until we can obtain
139
+ # a lock on the resource
140
+ #
141
+ # @return [true] if we're already obtained a shared lock, or if we were able to
142
+ # obtain the lock in non-blocking mode.
143
+ #
144
+ # @return [false] if we did not obtain the lock in non-blocking mode
145
+ #
146
+ # @return [void] if we obtained the lock in blocking mode.
147
+ #
148
+ # @raise [InterruptedSession] raised when blocked waiting for a lock and
149
+ # the underlying client's session is interrupted.
150
+ #
151
+ # @see ZK::Client::Unixisms#block_until_node_deleted more about possible execptions
152
+ def lock(blocking=false)
153
+ raise NotImplementedError
154
+ end
155
+
156
+ # (see #lock)
157
+ # @deprecated the use of lock! is deprecated and may be removed or have
158
+ # its semantics changed in a future release
159
+ def lock!(blocking=false)
160
+ lock(blocking)
161
+ end
162
+
102
163
  # returns true if this locker is waiting to acquire lock
103
164
  #
104
165
  # @private
@@ -106,8 +167,43 @@ module ZK
106
167
  false|@waiting
107
168
  end
108
169
 
170
+ # This is for users who wish to check that the assumption is correct
171
+ # that they actually still hold the lock. (check for session interruption,
172
+ # perhaps a lock is obtained in one method and handed to another)
173
+ #
174
+ # This, unlike {#locked?} will actually go and check the conditions
175
+ # that constitute "holding the lock" with the server.
176
+ #
177
+ # @raise [InterruptedSession] raised when the zk session has either
178
+ # closed or is in an invalid state.
179
+ #
180
+ # @raise [LockAssertionFailedError] raised if the lock is not held
181
+ #
182
+ # @example
183
+ #
184
+ # def process_jobs
185
+ # @lock.with_lock do
186
+ # @jobs.each do |j|
187
+ # @lock.assert!
188
+ # perform_job(j)
189
+ # end
190
+ # end
191
+ # end
192
+ #
193
+ # def perform_job(j)
194
+ # puts "hah! he thinks we're workin!"
195
+ # sleep(60)
196
+ # end
197
+ #
198
+ def assert!
199
+ raise LockAssertionFailedError, "have not obtained the lock yet" unless locked?
200
+ raise LockAssertionFailedError, "not connected" unless zk.connected?
201
+ raise LockAssertionFailedError, "lock_path was #{lock_path.inspect}" unless lock_path
202
+ raise LockAssertionFailedError, "the lock path #{lock_path} did not exist!" unless zk.exists?(lock_path)
203
+ raise LockAssertionFailedError, "we do not actually hold the lock" unless got_lock?
204
+ end
205
+
109
206
  protected
110
- # @private
111
207
  def in_waiting_status
112
208
  w, @waiting = @waiting, true
113
209
  yield
@@ -115,46 +211,53 @@ module ZK
115
211
  @waiting = w
116
212
  end
117
213
 
118
- # @private
119
214
  def digit_from(path)
120
215
  self.class.digit_from_lock_path(path)
121
216
  end
122
217
 
123
- # @private
218
+ # possibly lighter weight check to see if the lock path has any children
219
+ # (using stat, rather than getting the list of children).
220
+ def any_lock_children?
221
+ end
222
+
124
223
  def lock_children(watch=false)
125
- @zk.children(root_lock_path, :watch => watch)
224
+ zk.children(root_lock_path, :watch => watch)
126
225
  end
127
226
 
128
- # @private
129
227
  def ordered_lock_children(watch=false)
130
228
  lock_children(watch).tap do |ary|
131
229
  ary.sort! { |a,b| digit_from(a) <=> digit_from(b) }
132
230
  end
133
231
  end
134
232
 
135
- # @private
136
233
  def create_root_path!
137
- @zk.mkdir_p(@root_lock_path)
234
+ zk.mkdir_p(@root_lock_path)
235
+ end
236
+
237
+ # performs the checks that (according to the recipe) mean that we hold
238
+ # the lock. used by (#assert!)
239
+ #
240
+ def got_lock?
241
+ raise NotImplementedError
138
242
  end
139
243
 
140
244
  # prefix is the string that will appear in front of the sequence num,
141
245
  # defaults to 'lock'
142
246
  #
143
- # @private
144
247
  def create_lock_path!(prefix='lock')
145
248
  @lock_path = @zk.create("#{root_lock_path}/#{prefix}", "", :mode => :ephemeral_sequential)
146
249
  logger.debug { "got lock path #{@lock_path}" }
147
250
  @lock_path
148
- rescue Exceptions::NoNode
251
+ rescue NoNode
149
252
  create_root_path!
150
253
  retry
151
254
  end
152
255
 
153
- # @private
154
256
  def cleanup_lock_path!
155
257
  logger.debug { "removing lock path #{@lock_path}" }
156
- @zk.delete(@lock_path)
157
- @zk.delete(root_lock_path) rescue Exceptions::NotEmpty
258
+ zk.delete(@lock_path)
259
+ zk.delete(root_lock_path) rescue NotEmpty
260
+ @lock_path = nil
158
261
  end
159
262
  end # LockerBase
160
263
  end # Locker