zk 1.4.2 → 1.5.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/.dotfiles/ctags_paths +1 -0
- data/.dotfiles/rspec-logging +2 -2
- data/.gitignore +1 -0
- data/Gemfile +9 -3
- data/Guardfile +36 -0
- data/README.markdown +21 -18
- data/RELEASES.markdown +10 -0
- data/Rakefile +1 -1
- data/lib/zk.rb +28 -21
- data/lib/zk/client/threaded.rb +107 -17
- data/lib/zk/client/unixisms.rb +1 -41
- data/lib/zk/core_ext.rb +28 -0
- data/lib/zk/election.rb +14 -3
- data/lib/zk/event_handler.rb +36 -37
- data/lib/zk/event_handler_subscription/actor.rb +37 -2
- data/lib/zk/event_handler_subscription/base.rb +9 -0
- data/lib/zk/exceptions.rb +5 -0
- data/lib/zk/fork_hook.rb +112 -0
- data/lib/zk/install_fork_hooks.rb +37 -0
- data/lib/zk/locker/exclusive_locker.rb +14 -10
- data/lib/zk/locker/locker_base.rb +43 -26
- data/lib/zk/locker/shared_locker.rb +9 -5
- data/lib/zk/logging.rb +29 -7
- data/lib/zk/node_deletion_watcher.rb +167 -0
- data/lib/zk/pool.rb +14 -4
- data/lib/zk/subscription.rb +15 -34
- data/lib/zk/threaded_callback.rb +113 -29
- data/lib/zk/threadpool.rb +136 -40
- data/lib/zk/version.rb +1 -1
- data/spec/logging_progress_bar_formatter.rb +12 -0
- data/spec/shared/client_contexts.rb +13 -1
- data/spec/shared/client_examples.rb +3 -1
- data/spec/spec_helper.rb +28 -3
- data/spec/support/client_forker.rb +49 -8
- data/spec/support/latch.rb +1 -19
- data/spec/support/logging.rb +26 -10
- data/spec/support/wait_watchers.rb +2 -2
- data/spec/zk/00_forked_client_integration_spec.rb +1 -1
- data/spec/zk/client_spec.rb +11 -2
- data/spec/zk/election_spec.rb +21 -7
- data/spec/zk/locker_spec.rb +42 -22
- data/spec/zk/node_deletion_watcher_spec.rb +69 -0
- data/spec/zk/pool_spec.rb +32 -18
- data/spec/zk/threaded_callback_spec.rb +78 -0
- data/spec/zk/threadpool_spec.rb +52 -0
- data/spec/zk/watch_spec.rb +4 -0
- data/zk.gemspec +2 -1
- metadata +36 -10
- data/spec/support/logging_progress_bar_formatter.rb +0 -14
data/lib/zk/client/unixisms.rb
CHANGED
@@ -127,51 +127,11 @@ module ZK
|
|
127
127
|
# a deleted event. Includes the {InterruptedSession} module.
|
128
128
|
#
|
129
129
|
def block_until_node_deleted(abs_node_path)
|
130
|
-
subs = []
|
131
|
-
|
132
130
|
assert_we_are_not_on_the_event_dispatch_thread!
|
133
131
|
|
134
132
|
raise ArgumentError, "argument must be String-ish, not: #{abs_node_path.inspect}" unless abs_node_path
|
135
133
|
|
136
|
-
|
137
|
-
|
138
|
-
node_deletion_cb = lambda do |event|
|
139
|
-
if event.node_deleted?
|
140
|
-
queue.enq(:deleted)
|
141
|
-
else
|
142
|
-
queue.enq(:deleted) unless exists?(abs_node_path, :watch => true)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
subs << event_handler.register(abs_node_path, &node_deletion_cb)
|
147
|
-
|
148
|
-
# NOTE: this pattern may be necessary for other features with blocking semantics!
|
149
|
-
|
150
|
-
session_cb = lambda do |event|
|
151
|
-
queue.enq(event.state)
|
152
|
-
end
|
153
|
-
|
154
|
-
[:expired_session, :connecting, :closed].each do |sym|
|
155
|
-
subs << event_handler.register_state_handler(sym, &session_cb)
|
156
|
-
end
|
157
|
-
|
158
|
-
# set up the callback, but bail if we don't need to wait
|
159
|
-
return true unless exists?(abs_node_path, :watch => true)
|
160
|
-
|
161
|
-
case queue.pop
|
162
|
-
when :deleted
|
163
|
-
true
|
164
|
-
when ZOO_EXPIRED_SESSION_STATE
|
165
|
-
raise Zookeeper::Exceptions::SessionExpired
|
166
|
-
when ZOO_CONNECTING_STATE
|
167
|
-
raise Zookeeper::Exceptions::NotConnected
|
168
|
-
when ZOO_CLOSED_STATE
|
169
|
-
raise Zookeeper::Exceptions::ConnectionClosed
|
170
|
-
else
|
171
|
-
raise "Hit unexpected case in block_until_node_deleted"
|
172
|
-
end
|
173
|
-
ensure
|
174
|
-
subs.each(&:unregister)
|
134
|
+
NodeDeletionWatcher.new(self, abs_node_path).block_until_deleted
|
175
135
|
end
|
176
136
|
end
|
177
137
|
end
|
data/lib/zk/core_ext.rb
CHANGED
@@ -99,4 +99,32 @@ class ::Module
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
require 'logger'
|
103
|
+
|
104
|
+
# lets you clone a a Logger instance but change properties, this is
|
105
|
+
# used by the test suite to change the progname for different components
|
106
|
+
# @private
|
107
|
+
class ::Logger
|
108
|
+
unless method_defined?(:clone_new_log)
|
109
|
+
attr_writer :logdev
|
110
|
+
|
111
|
+
def clone_new_log(opts={})
|
112
|
+
self.class.new(nil).tap do |noo_log|
|
113
|
+
noo_log.progname = opts.fetch(:progname, self.progname)
|
114
|
+
noo_log.formatter = opts.fetch(:formatter, self.formatter)
|
115
|
+
noo_log.level = opts.fetch(:level, self.level)
|
116
|
+
noo_log.logdev = @logdev
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def debug_pp(title)
|
122
|
+
debug do
|
123
|
+
str = "---< #{title} >---\n"
|
124
|
+
require 'pp'
|
125
|
+
str << PP.pp(yield, '')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
102
130
|
|
data/lib/zk/election.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
module ZK
|
2
|
-
# NOTE: this module should be considered experimental.
|
3
|
-
# specs that have recently started failing under 1.9.2 (didn't fail under
|
4
|
-
# 1.8.7 or jruby 1.6) that need fixing.
|
2
|
+
# NOTE: this module should be considered experimental.
|
5
3
|
#
|
6
4
|
# ==== Overview
|
7
5
|
#
|
@@ -72,6 +70,14 @@ module ZK
|
|
72
70
|
opts = DEFAULT_OPTS.merge(opts)
|
73
71
|
@root_election_node = opts[:root_election_node]
|
74
72
|
@mutex = Monitor.new
|
73
|
+
@closed = false
|
74
|
+
end
|
75
|
+
|
76
|
+
def close
|
77
|
+
@mutex.synchronize do
|
78
|
+
return if @closed
|
79
|
+
@closed = true
|
80
|
+
end
|
75
81
|
end
|
76
82
|
|
77
83
|
# holds the ephemeral nodes of this election
|
@@ -109,6 +115,7 @@ module ZK
|
|
109
115
|
# role.
|
110
116
|
def on_leader_ack(&block)
|
111
117
|
creation_sub = @zk.register(leader_ack_path, :only => [:created, :changed]) do |event|
|
118
|
+
return if @closed
|
112
119
|
begin
|
113
120
|
logger.debug { "in #{leader_ack_path} watcher, got creation event, notifying" }
|
114
121
|
safe_call(block)
|
@@ -119,6 +126,7 @@ module ZK
|
|
119
126
|
|
120
127
|
deletion_sub = @zk.register(leader_ack_path, :only => [:deleted, :child]) do |event|
|
121
128
|
if @zk.exists?(leader_ack_path, :watch => true)
|
129
|
+
return if @closed
|
122
130
|
begin
|
123
131
|
logger.debug { "in #{leader_ack_path} watcher, node created behind our back, notifying" }
|
124
132
|
safe_call(block)
|
@@ -269,12 +277,15 @@ module ZK
|
|
269
277
|
end
|
270
278
|
|
271
279
|
def handle_winning_election
|
280
|
+
@mutex.synchronize { return if @closed }
|
272
281
|
@leader = true
|
273
282
|
fire_winning_callbacks!
|
274
283
|
acknowledge_win!
|
275
284
|
end
|
276
285
|
|
277
286
|
def handle_losing_election(our_idx, ballots)
|
287
|
+
@mutex.synchronize { return if @closed }
|
288
|
+
|
278
289
|
@leader = false
|
279
290
|
|
280
291
|
on_leader_ack do
|
data/lib/zk/event_handler.rb
CHANGED
@@ -33,6 +33,8 @@ module ZK
|
|
33
33
|
def initialize(zookeeper_client, opts={})
|
34
34
|
@zk = zookeeper_client
|
35
35
|
|
36
|
+
@orig_pid = Process.pid
|
37
|
+
|
36
38
|
@thread_opt = opts.fetch(:thread, :single)
|
37
39
|
EventHandlerSubscription.class_for_thread_option(@thread_opt) # this is side-effecty, will raise an ArgumentError if given a bad value.
|
38
40
|
|
@@ -48,42 +50,17 @@ module ZK
|
|
48
50
|
|
49
51
|
reopen_after_fork!
|
50
52
|
end
|
51
|
-
|
52
|
-
# stops the dispatching of events. already in-flight callbacks may still be running, but
|
53
|
-
# no new events will be dispatched until {#start} is called
|
54
|
-
#
|
55
|
-
# any events that are delivered while we are stopped will be lost
|
56
|
-
#
|
57
|
-
def stop
|
58
|
-
synchronize do
|
59
|
-
return if @state == :stopped
|
60
|
-
@state = :stopped
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# called to re-enable event delivery
|
65
|
-
def start
|
66
|
-
synchronize do
|
67
|
-
return if @state == :running
|
68
|
-
@state = :running
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def running?
|
73
|
-
synchronize { @state == :running }
|
74
|
-
end
|
75
|
-
|
76
|
-
def stopped?
|
77
|
-
synchronize { @state == :stopped }
|
78
|
-
end
|
79
|
-
|
53
|
+
|
80
54
|
# do not call this method. it is inteded for use only when we've forked and
|
81
55
|
# all other threads are dead.
|
82
56
|
#
|
83
57
|
# @private
|
84
58
|
def reopen_after_fork!
|
59
|
+
# logger.debug { "#{self.class}##{__method__}" }
|
85
60
|
@mutex = Monitor.new
|
86
61
|
# XXX: need to test this w/ actor-style callbacks
|
62
|
+
|
63
|
+
@state = :running
|
87
64
|
@callbacks.values.flatten.each { |cb| cb.reopen_after_fork! if cb.respond_to?(:reopen_after_fork!) }
|
88
65
|
@outstanding_watches.values.each { |set| set.clear }
|
89
66
|
nil
|
@@ -164,7 +141,7 @@ module ZK
|
|
164
141
|
end
|
165
142
|
alias :unsubscribe :unregister
|
166
143
|
|
167
|
-
# called from the
|
144
|
+
# called from the Client registered callback when an event fires
|
168
145
|
#
|
169
146
|
# @note this is *ONLY* dealing with asynchronous callbacks! watchers
|
170
147
|
# and session events go through here, NOT anything else!!
|
@@ -173,7 +150,7 @@ module ZK
|
|
173
150
|
def process(event)
|
174
151
|
@zk.raw_event_handler(event)
|
175
152
|
|
176
|
-
|
153
|
+
logger.debug { "EventHandler#process dispatching event: #{event.inspect}" }# unless event.type == -1
|
177
154
|
event.zk = @zk
|
178
155
|
|
179
156
|
cb_keys =
|
@@ -185,8 +162,6 @@ module ZK
|
|
185
162
|
raise ZKError, "don't know how to process event: #{event.inspect}"
|
186
163
|
end
|
187
164
|
|
188
|
-
# logger.debug { "EventHandler#process: cb_key: #{cb_key}" }
|
189
|
-
|
190
165
|
cb_ary = synchronize do
|
191
166
|
clear_watch_restrictions(event)
|
192
167
|
|
@@ -235,10 +210,34 @@ module ZK
|
|
235
210
|
def close
|
236
211
|
synchronize do
|
237
212
|
@callbacks.values.flatten.each(&:close)
|
213
|
+
@state = :closed
|
238
214
|
clear!
|
239
215
|
end
|
240
216
|
end
|
241
217
|
|
218
|
+
# @private
|
219
|
+
def pause_before_fork_in_parent
|
220
|
+
synchronize do
|
221
|
+
raise InvalidStateError, "invalid state, expected to be :running, was #{@state.inspect}" if @state != :running
|
222
|
+
return false if @state == :paused
|
223
|
+
@state = :paused
|
224
|
+
end
|
225
|
+
logger.debug { "#{self.class}##{__method__}" }
|
226
|
+
|
227
|
+
@callbacks.values.flatten.each(&:pause_before_fork_in_parent)
|
228
|
+
end
|
229
|
+
|
230
|
+
# @private
|
231
|
+
def resume_after_fork_in_parent
|
232
|
+
synchronize do
|
233
|
+
raise InvalidStateError, "expected :paused, was #{@state.inspect}" if @state != :paused
|
234
|
+
@state = :running
|
235
|
+
end
|
236
|
+
logger.debug { "#{self.class}##{__method__}" }
|
237
|
+
|
238
|
+
@callbacks.values.flatten.each(&:resume_after_fork_in_parent)
|
239
|
+
end
|
240
|
+
|
242
241
|
# @private
|
243
242
|
def synchronize
|
244
243
|
@mutex.synchronize { yield }
|
@@ -329,16 +328,16 @@ module ZK
|
|
329
328
|
|
330
329
|
# @private
|
331
330
|
def safe_call(callbacks, *args)
|
332
|
-
# oddly, a `while cb = callbacks.shift` here will have thread safety issues
|
333
|
-
# as cb will be nil when the defer block is called on the threadpool
|
334
|
-
|
335
331
|
callbacks.each do |cb|
|
336
332
|
next unless cb.respond_to?(:call)
|
337
333
|
|
338
334
|
if cb.async?
|
339
335
|
cb.call(*args)
|
340
336
|
else
|
341
|
-
zk.defer
|
337
|
+
zk.defer do
|
338
|
+
logger.debug { "called #{cb.inspect} with #{args.inspect} on threadpool" }
|
339
|
+
cb.call(*args)
|
340
|
+
end
|
342
341
|
end
|
343
342
|
end
|
344
343
|
end
|
@@ -16,15 +16,50 @@ module ZK
|
|
16
16
|
# guarantees), just perhaps at different times.
|
17
17
|
#
|
18
18
|
class Actor < Base
|
19
|
-
|
19
|
+
# @private
|
20
|
+
attr_reader :threaded_callback
|
21
|
+
|
22
|
+
def initialize(parent, path, callback, opts={})
|
23
|
+
super
|
24
|
+
@threaded_callback = ThreadedCallback.new(@callable)
|
25
|
+
end
|
20
26
|
|
21
27
|
def async?
|
22
28
|
true
|
23
29
|
end
|
24
30
|
|
31
|
+
def call(*args)
|
32
|
+
@threaded_callback.call(*args)
|
33
|
+
end
|
34
|
+
|
25
35
|
# calls unsubscribe and shuts down
|
26
36
|
def close
|
27
|
-
|
37
|
+
unregister
|
38
|
+
end
|
39
|
+
|
40
|
+
def unregister
|
41
|
+
super
|
42
|
+
@threaded_callback.shutdown
|
43
|
+
end
|
44
|
+
|
45
|
+
def reopen_after_fork!
|
46
|
+
logger.debug { "#{self.class}##{__method__}" }
|
47
|
+
super
|
48
|
+
@threaded_callback.reopen_after_fork!
|
49
|
+
end
|
50
|
+
|
51
|
+
def pause_before_fork_in_parent
|
52
|
+
synchronize do
|
53
|
+
logger.debug { "#{self.class}##{__method__}" }
|
54
|
+
@threaded_callback.pause_before_fork_in_parent
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def resume_after_fork_in_parent
|
60
|
+
super
|
61
|
+
logger.debug { "#{self.class}##{__method__}" }
|
62
|
+
@threaded_callback.resume_after_fork_in_parent
|
28
63
|
end
|
29
64
|
end
|
30
65
|
end
|
@@ -43,6 +43,15 @@ module ZK
|
|
43
43
|
def close
|
44
44
|
end
|
45
45
|
|
46
|
+
# stop anything non-fork-safe in parent
|
47
|
+
def pause_before_fork_in_parent
|
48
|
+
end
|
49
|
+
|
50
|
+
# take any action necessary to deliver events after a fork
|
51
|
+
# in the parent
|
52
|
+
def resume_after_fork_in_parent
|
53
|
+
end
|
54
|
+
|
46
55
|
protected
|
47
56
|
def prep_interests(a)
|
48
57
|
# logger.debug { "prep_interests: #{a.inspect}" }
|
data/lib/zk/exceptions.rb
CHANGED
@@ -129,6 +129,11 @@ module ZK
|
|
129
129
|
# raised when someone calls lock.assert! but they do not hold the lock
|
130
130
|
class LockAssertionFailedError < ZKError; end
|
131
131
|
|
132
|
+
# called when the client is reopened, resumed, or paused when in an invalid state
|
133
|
+
class InvalidStateError < ZKError; end
|
134
|
+
|
135
|
+
class WakeUpException < ZKError; end
|
136
|
+
|
132
137
|
# raised when a chrooted conection is requested but the root doesn't exist
|
133
138
|
class ChrootPathDoesNotExistError < NoNode
|
134
139
|
def initialize(host_string, chroot_path)
|
data/lib/zk/fork_hook.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
module ZK
|
2
|
+
module ForkHook
|
3
|
+
include ZK::Logging
|
4
|
+
extend self
|
5
|
+
|
6
|
+
@mutex = Mutex.new unless @mutex
|
7
|
+
|
8
|
+
@hooks = {
|
9
|
+
:prepare => [],
|
10
|
+
:after_child => [],
|
11
|
+
:after_parent => [],
|
12
|
+
} unless @hooks
|
13
|
+
|
14
|
+
attr_reader :hooks, :mutex
|
15
|
+
|
16
|
+
# @private
|
17
|
+
def fire_prepare_hooks!
|
18
|
+
@mutex.lock
|
19
|
+
safe_call(@hooks[:prepare])
|
20
|
+
end
|
21
|
+
|
22
|
+
# @private
|
23
|
+
def fire_after_child_hooks!
|
24
|
+
safe_call(@hooks[:after_child])
|
25
|
+
ensure
|
26
|
+
@mutex.unlock rescue nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# @private
|
30
|
+
def fire_after_parent_hooks!
|
31
|
+
safe_call(@hooks[:after_parent])
|
32
|
+
ensure
|
33
|
+
@mutex.unlock rescue nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# @private
|
37
|
+
def clear!
|
38
|
+
@mutex.synchronize { @hooks.values(&:clear) }
|
39
|
+
end
|
40
|
+
|
41
|
+
# @private
|
42
|
+
def unregister(sub)
|
43
|
+
@mutex.synchronize do
|
44
|
+
@hooks.fetch(sub.hook_type, []).delete(sub)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# do :call on each of callbacks. if a WeakRef::RefError
|
49
|
+
# is caught, modify `callbacks` by removing the dud reference
|
50
|
+
#
|
51
|
+
# @private
|
52
|
+
def safe_call(callbacks)
|
53
|
+
cbs = callbacks.dup
|
54
|
+
|
55
|
+
while cb = cbs.shift
|
56
|
+
begin
|
57
|
+
cb.call
|
58
|
+
rescue WeakRef::RefError
|
59
|
+
# clean weakrefs out of the original callback arrays if they're bad
|
60
|
+
callbacks.delete(cb)
|
61
|
+
rescue Exception => e
|
62
|
+
logger.error { e.to_std_format }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# @private
|
68
|
+
def register(hook_type, block)
|
69
|
+
unless hooks.has_key?(hook_type)
|
70
|
+
raise "Invalid hook type specified: #{hook.inspect}"
|
71
|
+
end
|
72
|
+
|
73
|
+
unless block.respond_to?(:call)
|
74
|
+
raise ArgumentError, "You must provide either a callable an argument or a block"
|
75
|
+
end
|
76
|
+
|
77
|
+
ForkSubscription.new(hook_type, block).tap do |sub|
|
78
|
+
# use a WeakRef so that the original objects can be GC'd
|
79
|
+
@mutex.synchronize { @hooks[hook_type] << WeakRef.new(sub) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Register a block that will be called in the parent process before a fork() occurs
|
84
|
+
def prepare_for_fork(callable=nil, &blk)
|
85
|
+
register(:prepare, callable || blk)
|
86
|
+
end
|
87
|
+
|
88
|
+
# register a block that will be called after the fork happens in the parent process
|
89
|
+
def after_fork_in_parent(callable=nil, &blk)
|
90
|
+
register(:after_parent, callable || blk)
|
91
|
+
end
|
92
|
+
|
93
|
+
# register a block that will be called after the fork happens in the child process
|
94
|
+
def after_fork_in_child(callable=nil, &blk)
|
95
|
+
register(:after_child, callable || blk)
|
96
|
+
end
|
97
|
+
|
98
|
+
class ForkSubscription < Subscription::Base
|
99
|
+
attr_reader :hook_type
|
100
|
+
|
101
|
+
def initialize(hook_type, block)
|
102
|
+
super(ForkHook, block)
|
103
|
+
|
104
|
+
@hook_type = hook_type
|
105
|
+
end
|
106
|
+
end # ForkSubscription
|
107
|
+
end # ForkHook
|
108
|
+
|
109
|
+
def self.install_fork_hook
|
110
|
+
require 'zk/install_fork_hooks'
|
111
|
+
end
|
112
|
+
end # ZK
|