zk 1.0.0 → 1.1.0

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