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