zk 1.0.0 → 1.1.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.
@@ -106,39 +106,38 @@ module ZK
106
106
  end
107
107
 
108
108
  # Asynchronously call the block when the leader has acknowledged its
109
- # role. The given block will *always* be called on a background thread.
109
+ # role.
110
110
  def on_leader_ack(&block)
111
- creation_sub = @zk.watcher.register(leader_ack_path) do |event|
112
- case event.type
113
- when Zookeeper::ZOO_CREATED_EVENT, Zookeeper::ZOO_CHANGED_EVENT
111
+ creation_sub = @zk.register(leader_ack_path, :only => [:created, :changed]) do |event|
112
+ begin
113
+ logger.debug { "in #{leader_ack_path} watcher, got creation event, notifying" }
114
+ safe_call(block)
115
+ ensure
116
+ creation_sub.unregister
117
+ end
118
+ end
119
+
120
+ deletion_sub = @zk.register(leader_ack_path, :only => [:deleted, :child]) do |event|
121
+ if @zk.exists?(leader_ack_path, :watch => true)
114
122
  begin
115
- logger.debug { "in #{leader_ack_path} watcher, got creation event, notifying" }
116
- block.call
123
+ logger.debug { "in #{leader_ack_path} watcher, node created behind our back, notifying" }
124
+ safe_call(block)
117
125
  ensure
118
126
  creation_sub.unregister
119
127
  end
120
128
  else
121
- if @zk.exists?(leader_ack_path, :watch => true)
122
- begin
123
- logger.debug { "in #{leader_ack_path} watcher, node created behind our back, notifying" }
124
- block.call
125
- ensure
126
- creation_sub.unregister
127
- end
128
- else
129
- logger.debug { "in #{leader_ack_path} watcher, got non-creation event, re-watching" }
130
- end
129
+ logger.debug { "in #{leader_ack_path} watcher, got non-creation event, re-watching" }
131
130
  end
132
131
  end
133
132
 
134
- @zk.defer do
135
- if @zk.exists?(leader_ack_path, :watch => true)
136
- logger.debug { "on_leader_ack, #{leader_ack_path} exists, calling block" }
137
- begin
138
- block.call
139
- ensure
140
- creation_sub.unregister if creation_sub
141
- end
133
+ subs = [creation_sub, deletion_sub]
134
+
135
+ if @zk.exists?(leader_ack_path, :watch => true)
136
+ logger.debug { "on_leader_ack, #{leader_ack_path} exists, calling block" }
137
+ begin
138
+ safe_call(block)
139
+ ensure
140
+ subs.each { |s| s.unregister }
142
141
  end
143
142
  end
144
143
  end
@@ -154,7 +153,6 @@ module ZK
154
153
 
155
154
  def digit(path)
156
155
  path[/\d+$/].to_i
157
-
158
156
  end
159
157
 
160
158
  def safe_call(*callbacks)
@@ -167,6 +165,12 @@ module ZK
167
165
  end
168
166
  end
169
167
  end
168
+
169
+ def synchronize
170
+ # call_line = caller[0..-2]
171
+ # logger.debug { "synchronizing, backtrace:\n#{call_line.join("\n")}" }
172
+ @mutex.synchronize { yield }
173
+ end
170
174
  end
171
175
 
172
176
  # This class is for registering candidates in the leader election. This instance will
@@ -225,7 +229,7 @@ module ZK
225
229
  #
226
230
  # +data+ will be placed in the znode representing our vote
227
231
  def vote!
228
- @mutex.synchronize do
232
+ synchronize do
229
233
  clear_next_node_ballot_sub!
230
234
  cast_ballot!(@data) unless @vote_path
231
235
  check_election_results!
@@ -279,17 +283,17 @@ module ZK
279
283
 
280
284
  logger.info { "ZK: following #{next_ballot} for changes, #{@data.inspect}" }
281
285
 
282
- @next_node_ballot_sub ||= @zk.watcher.register(next_ballot) do |event|
286
+ @next_node_ballot_sub ||= @zk.register(next_ballot) do |event|
283
287
  if event.node_deleted?
284
288
  logger.debug { "#{next_ballot} was deleted, voting, #{@data.inspect}" }
285
- vote!
289
+ @zk.defer { vote! }
286
290
  else
287
291
  # this takes care of the race condition where the leader ballot would
288
292
  # have been deleted before we could re-register to receive updates
289
293
  # if zk.stat returns false, it means the path was deleted
290
294
  unless @zk.exists?(next_ballot, :watch => true)
291
295
  logger.debug { "#{next_ballot} was deleted (detected on re-watch), voting, #{@data.inspect}" }
292
- vote!
296
+ @zk.defer { vote! }
293
297
  end
294
298
  end
295
299
  end
@@ -298,7 +302,7 @@ module ZK
298
302
  # our callback has fired. In this case, retry and do this procedure again
299
303
  unless @zk.stat(next_ballot, :watch => true).exists?
300
304
  logger.debug { "#{@data.inspect}: the node #{next_ballot} did not exist, retrying" }
301
- vote!
305
+ @zk.defer { vote! }
302
306
  end
303
307
  end
304
308
  end
@@ -331,7 +335,7 @@ module ZK
331
335
 
332
336
  # our current idea about the state of the election
333
337
  def leader_alive #:nodoc:
334
- @mutex.synchronize { @leader_alive }
338
+ synchronize { @leader_alive }
335
339
  end
336
340
 
337
341
  # register callbacks that should be fired when a leader dies
@@ -346,11 +350,11 @@ module ZK
346
350
  end
347
351
 
348
352
  def observe!
349
- @mutex.synchronize do
353
+ synchronize do
350
354
  return if @observing
351
355
  @observing = true
352
356
 
353
- @leader_ack_sub ||= @zk.watcher.register(leader_ack_path) do |event|
357
+ @leader_ack_sub ||= @zk.register(leader_ack_path) do |event|
354
358
  logger.debug { "leader_ack_callback, event.node_deleted? #{event.node_deleted?}, event.node_created? #{event.node_created?}" }
355
359
 
356
360
  if event.node_deleted?
@@ -379,7 +383,7 @@ module ZK
379
383
  end
380
384
 
381
385
  def close
382
- @mutex.synchronize do
386
+ synchronize do
383
387
  return unless @observing
384
388
 
385
389
  @deletion_sub.unregister if @deletion_sub
@@ -397,7 +401,7 @@ module ZK
397
401
 
398
402
  protected
399
403
  def the_king_is_dead
400
- @mutex.synchronize do
404
+ synchronize do
401
405
  safe_call(*@leader_death_cbs)
402
406
  @leader_alive = false
403
407
  end
@@ -406,7 +410,7 @@ module ZK
406
410
  end
407
411
 
408
412
  def long_live_the_king
409
- @mutex.synchronize do
413
+ synchronize do
410
414
  safe_call(*@new_leader_cbs)
411
415
  @leader_alive = true
412
416
  end
@@ -414,5 +418,5 @@ module ZK
414
418
  the_king_is_dead unless leader_acked?(true)
415
419
  end
416
420
  end
417
- end
418
- end
421
+ end # Election
422
+ end # ZK
@@ -26,6 +26,14 @@ 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
30
+ # @private
31
+ def self.new(hash)
32
+ ZookeeperCallbacks::WatcherCallback.new.tap do |wc|
33
+ wc.call(hash)
34
+ end
35
+ end
36
+
29
37
  # The numeric constant (one of `ZOO_*_EVENT`) that ZooKeeper sets to
30
38
  # indicate the type of event this is. Users are advised to use the '?'
31
39
  # methods below instead of using this value.
@@ -8,10 +8,13 @@ module ZK
8
8
  include org.apache.zookeeper.Watcher if defined?(JRUBY_VERSION)
9
9
  include ZK::Logging
10
10
 
11
+ # @private
11
12
  VALID_WATCH_TYPES = [:data, :child].freeze
12
13
 
14
+ # @private
13
15
  ALL_NODE_EVENTS_KEY = :all_node_events
14
16
 
17
+ # @private
15
18
  ZOOKEEPER_WATCH_TYPE_MAP = {
16
19
  Zookeeper::ZOO_CREATED_EVENT => :data,
17
20
  Zookeeper::ZOO_DELETED_EVENT => :data,
@@ -19,15 +22,24 @@ module ZK
19
22
  Zookeeper::ZOO_CHILD_EVENT => :child,
20
23
  }.freeze
21
24
 
25
+ # @private
26
+ VALID_THREAD_OPTS = [:single, :per_callback].freeze
27
+
22
28
  # @private
23
29
  attr_accessor :zk
24
30
 
25
31
  # @private
26
32
  # :nodoc:
27
- def initialize(zookeeper_client)
33
+ def initialize(zookeeper_client, opts={})
28
34
  @zk = zookeeper_client
29
35
  @callbacks = Hash.new { |h,k| h[k] = [] }
30
36
 
37
+ @thread_opt = opts.fetch(:thread, :single)
38
+
39
+ # this is side-effecty, will raise an ArgumentError if given a bad
40
+ # value.
41
+ EventHandlerSubscription.class_for_thread_option(@thread_opt)
42
+
31
43
  @mutex = Monitor.new
32
44
 
33
45
  @outstanding_watches = VALID_WATCH_TYPES.inject({}) do |h,k|
@@ -36,10 +48,25 @@ module ZK
36
48
  end
37
49
 
38
50
  # @see ZK::Client::Base#register
39
- def register(path, interests=nil, &block)
51
+ def register(path, opts={}, &block)
40
52
  path = ALL_NODE_EVENTS_KEY if path == :all
41
53
 
42
- EventHandlerSubscription.new(self, path, block, interests).tap do |subscription|
54
+ hash = {:thread => @thread_opt}
55
+
56
+ # gah, ok, handle the 1.0 form
57
+ case opts
58
+ when Array, Symbol
59
+ warn "Deprecated! #{self.class}#register use the :only option instead of passing a symbol or array"
60
+ hash[:only] = opts
61
+ when Hash
62
+ hash.merge!(opts)
63
+ when nil
64
+ # no-op
65
+ else
66
+ raise ArgumentError, "don't know how to handle options: #{opts.inspect}"
67
+ end
68
+
69
+ EventHandlerSubscription.new(self, path, block, hash).tap do |subscription|
43
70
  synchronize { @callbacks[path] << subscription }
44
71
  end
45
72
  end
@@ -65,7 +92,7 @@ module ZK
65
92
  # @deprecated use #unsubscribe on the subscription object
66
93
  # @see ZK::EventHandlerSubscription#unsubscribe
67
94
  def unregister_state_handler(*args)
68
- if args.first.is_a?(EventHandlerSubscription)
95
+ if args.first.is_a?(EventHandlerSubscription::Base)
69
96
  unregister(args.first)
70
97
  else
71
98
  unregister(state_key(args.first), args[1])
@@ -75,9 +102,9 @@ module ZK
75
102
  # @deprecated use #unsubscribe on the subscription object
76
103
  # @see ZK::EventHandlerSubscription#unsubscribe
77
104
  def unregister(*args)
78
- if args.first.is_a?(EventHandlerSubscription)
105
+ if args.first.is_a?(EventHandlerSubscription::Base)
79
106
  subscription = args.first
80
- elsif args.first.is_a?(String) and args[1].is_a?(EventHandlerSubscription)
107
+ elsif args.first.is_a?(String) and args[1].is_a?(EventHandlerSubscription::Base)
81
108
  subscription = args[1]
82
109
  else
83
110
  path, index = args[0..1]
@@ -258,7 +285,11 @@ module ZK
258
285
  callbacks.each do |cb|
259
286
  next unless cb.respond_to?(:call)
260
287
 
261
- zk.defer { cb.call(*args) }
288
+ if cb.async?
289
+ cb.call(*args)
290
+ else
291
+ zk.defer { cb.call(*args) }
292
+ end
262
293
  end
263
294
  end
264
295
 
@@ -2,67 +2,31 @@ module ZK
2
2
  # the subscription object that is passed back from subscribing
3
3
  # to events.
4
4
  # @see ZK::Client::Base#register
5
- class EventHandlerSubscription
6
- # the event handler associated with this subscription
7
- # @return [EventHandler]
8
- attr_accessor :event_handler
9
-
10
- # the path this subscription is for
11
- # @return [String]
12
- attr_accessor :path
13
-
14
- # the block associated with the path
15
- # @return [Proc]
16
- attr_accessor :callback
17
-
18
- # an array of what kinds of events this handler is interested in receiving
19
- #
20
- # @return [Set] containing any combination of :create, :change, :delete,
21
- # or :children
22
- #
23
- # @private
24
- attr_accessor :interests
25
-
26
- ALL_EVENTS = [:created, :deleted, :changed, :child].freeze unless defined?(ALL_EVENTS)
27
- ALL_EVENT_SET = Set.new(ALL_EVENTS).freeze unless defined?(ALL_EVENT_SET)
5
+ module EventHandlerSubscription
28
6
 
29
7
  # @private
30
- def initialize(event_handler, path, callback, interests)
31
- @event_handler, @path, @callback = event_handler, path, callback
32
- @interests = prep_interests(interests)
8
+ def self.class_for_thread_option(thopt)
9
+ case thopt
10
+ when :single
11
+ Base
12
+ when :per_callback
13
+ Actor
14
+ else
15
+ raise ArgumentError, "Unrecognized :thread option: #{thopt}"
16
+ end
33
17
  end
34
18
 
35
- # unsubscribe from the path or state you were watching
36
- # @see ZK::Client::Base#register
37
- def unsubscribe
38
- @event_handler.unregister(self)
39
- end
40
- alias :unregister :unsubscribe
19
+ def self.new(*a, &b)
20
+ opts = a.extract_options!
21
+
22
+ klass = class_for_thread_option(opts.delete(:thread))
41
23
 
42
- # @private
43
- def call(event)
44
- callback.call(event)
24
+ a << opts
25
+ klass.new(*a, &b)
45
26
  end
46
-
47
- private
48
- def prep_interests(a)
49
- return ALL_EVENT_SET if a.nil?
50
-
51
- rval =
52
- case a
53
- when Array
54
- Set.new(a)
55
- when Symbol
56
- Set.new([a])
57
- else
58
- raise ArgumentError, "Don't know how to handle interests: #{a.inspect}"
59
- end
60
-
61
- rval.tap do |rv|
62
- invalid = (rv - ALL_EVENT_SET)
63
- raise ArgumentError, "Invalid event name(s) #{invalid.to_a.inspect} given" unless invalid.empty?
64
- end
65
- end
66
27
  end
67
28
  end
68
29
 
30
+ require 'zk/event_handler_subscription/base'
31
+ require 'zk/event_handler_subscription/actor'
32
+
@@ -0,0 +1,38 @@
1
+ module ZK
2
+ module EventHandlerSubscription
3
+ # Stealing some ideas from Celluloid, this event handler subscription
4
+ # (basically, the wrapper around the user block), will spin up its own
5
+ # thread for delivery, and use a queue. This gives us the basis for better
6
+ # concurrency (event handlers run in parallel), but preserves the
7
+ # underlying behavior that a single-event-thread ZK gives us, which is that
8
+ # a single callback block is inherently serial. Without this, you have to
9
+ # make sure your callbacks are either synchronized, or totally reentrant,
10
+ # so that multiple threads could be calling your block safely (which is
11
+ # really difficult, and annoying).
12
+ #
13
+ # Using this delivery mechanism means that the block still must not block
14
+ # forever, however each event will "wait its turn" and all callbacks will
15
+ # receive their events in the same order (which is what ZooKeeper
16
+ # guarantees), just perhaps at different times.
17
+ #
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
32
+
33
+ def async?
34
+ true
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,76 @@
1
+ module ZK
2
+ module EventHandlerSubscription
3
+ class Base
4
+ include ZK::Logging
5
+
6
+ # the event handler associated with this subscription
7
+ # @return [EventHandler]
8
+ attr_accessor :event_handler
9
+
10
+ # the path this subscription is for
11
+ # @return [String]
12
+ attr_accessor :path
13
+
14
+ # the block associated with the path
15
+ # @return [Proc]
16
+ attr_accessor :callback
17
+
18
+ # an array of what kinds of events this handler is interested in receiving
19
+ # this is the :only option, essentially
20
+ #
21
+ # @return [Set] containing any combination of :create, :change, :delete,
22
+ # or :children
23
+ #
24
+ # @private
25
+ attr_accessor :interests
26
+
27
+ ALL_EVENTS = [:created, :deleted, :changed, :child].freeze unless defined?(ALL_EVENTS)
28
+ ALL_EVENT_SET = Set.new(ALL_EVENTS).freeze unless defined?(ALL_EVENT_SET)
29
+
30
+ # @private
31
+ def initialize(event_handler, path, callback, opts={})
32
+ @event_handler, @path, @callback = event_handler, path, callback
33
+ @interests = prep_interests(opts[:only])
34
+ end
35
+
36
+ # unsubscribe from the path or state you were watching
37
+ # @see ZK::Client::Base#register
38
+ def unsubscribe
39
+ @event_handler.unregister(self)
40
+ end
41
+ alias :unregister :unsubscribe
42
+
43
+ # @private
44
+ def call(event)
45
+ callback.call(event)
46
+ end
47
+
48
+ # the Actor returns true for this
49
+ # @private
50
+ def async?
51
+ false
52
+ end
53
+
54
+ protected
55
+ def prep_interests(a)
56
+ # logger.debug { "prep_interests: #{a.inspect}" }
57
+ return ALL_EVENT_SET if a.nil?
58
+
59
+ rval =
60
+ case a
61
+ when Array
62
+ Set.new(a)
63
+ when Symbol
64
+ Set.new([a])
65
+ else
66
+ raise ArgumentError, "Don't know how to handle interests: #{a.inspect}"
67
+ end
68
+
69
+ rval.tap do |rv|
70
+ invalid = (rv - ALL_EVENT_SET)
71
+ raise ArgumentError, "Invalid event name(s) #{invalid.to_a.inspect} given" unless invalid.empty?
72
+ end
73
+ end
74
+ end # Base
75
+ end # EventHandlerSubscription
76
+ end # ZK