zk 1.1.1 → 1.2.0

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