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.
- data/.gitignore +1 -0
- data/.travis.yml +6 -8
- data/Gemfile +4 -6
- data/README.markdown +39 -21
- data/Rakefile +4 -2
- data/lib/zk.rb +60 -5
- data/lib/zk/client/base.rb +31 -9
- data/lib/zk/client/threaded.rb +39 -20
- data/lib/zk/election.rb +42 -38
- data/lib/zk/event.rb +8 -0
- data/lib/zk/event_handler.rb +38 -7
- data/lib/zk/event_handler_subscription.rb +19 -55
- data/lib/zk/event_handler_subscription/actor.rb +38 -0
- data/lib/zk/event_handler_subscription/base.rb +76 -0
- data/lib/zk/threaded_callback.rb +56 -0
- data/lib/zk/threadpool.rb +1 -3
- data/lib/zk/version.rb +1 -1
- data/spec/event_catcher_spec.rb +29 -0
- data/spec/message_queue_spec.rb +2 -2
- data/spec/shared/client_contexts.rb +20 -10
- data/spec/shared/client_examples.rb +1 -1
- data/spec/spec_helper.rb +22 -7
- data/spec/support/00_test_port_attr.rb +20 -0
- data/spec/support/event_catcher.rb +73 -5
- data/spec/support/logging.rb +3 -1
- data/spec/support/pendings.rb +52 -0
- data/spec/support/wait_watchers.rb +10 -1
- data/spec/watch_spec.rb +143 -124
- data/spec/zk/client_spec.rb +15 -3
- data/spec/zk/election_spec.rb +12 -14
- data/spec/zk/locker_spec.rb +17 -12
- data/spec/zk/module_spec.rb +20 -12
- data/spec/zk/mongoid_spec.rb +3 -1
- data/spec/zk/pool_spec.rb +3 -3
- data/spec/zookeeper_spec.rb +2 -2
- metadata +13 -4
data/lib/zk/election.rb
CHANGED
@@ -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.
|
109
|
+
# role.
|
110
110
|
def on_leader_ack(&block)
|
111
|
-
creation_sub = @zk.
|
112
|
-
|
113
|
-
|
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,
|
116
|
-
block
|
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
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
353
|
+
synchronize do
|
350
354
|
return if @observing
|
351
355
|
@observing = true
|
352
356
|
|
353
|
-
@leader_ack_sub ||= @zk.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/zk/event.rb
CHANGED
@@ -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.
|
data/lib/zk/event_handler.rb
CHANGED
@@ -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,
|
51
|
+
def register(path, opts={}, &block)
|
40
52
|
path = ALL_NODE_EVENTS_KEY if path == :all
|
41
53
|
|
42
|
-
|
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
|
-
|
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
|
-
|
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
|
31
|
-
|
32
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
43
|
-
|
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
|