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/spec/zk/locker_spec.rb
CHANGED
@@ -12,6 +12,7 @@ describe 'ZK::Client#locker' do
|
|
12
12
|
@zk3 = ZK.new("localhost:#{ZK.test_port}", connection_opts)
|
13
13
|
@connections = [@zk, @zk2, @zk3]
|
14
14
|
wait_until { @connections.all? { |c| c.connected? } }
|
15
|
+
logger.debug { "all connections connected" }
|
15
16
|
@path_to_lock = "/lock_tester"
|
16
17
|
end
|
17
18
|
|
@@ -19,7 +20,7 @@ describe 'ZK::Client#locker' do
|
|
19
20
|
@zk.close!
|
20
21
|
@zk2.close!
|
21
22
|
@zk3.close!
|
22
|
-
wait_until{ @connections.all? { |c|
|
23
|
+
wait_until { @connections.all? { |c| c.closed? } }
|
23
24
|
end
|
24
25
|
|
25
26
|
it "should be able to acquire the lock if no one else is locking it" do
|
@@ -194,14 +195,16 @@ shared_examples_for 'SharedLocker' do
|
|
194
195
|
ary << :locked
|
195
196
|
end
|
196
197
|
|
197
|
-
|
198
|
+
shared_locker.wait_until_blocked(5)
|
199
|
+
shared_locker.should be_waiting
|
198
200
|
shared_locker.should_not be_locked
|
201
|
+
ary.should be_empty
|
199
202
|
|
200
203
|
zk.delete(@write_lock_path)
|
201
204
|
|
202
|
-
th.join(2)
|
205
|
+
th.join(2).should == th
|
203
206
|
|
204
|
-
|
207
|
+
ary.should_not be_empty
|
205
208
|
ary.length.should == 1
|
206
209
|
|
207
210
|
shared_locker.should be_locked
|
@@ -235,24 +238,32 @@ shared_examples_for 'ExclusiveLocker' do
|
|
235
238
|
|
236
239
|
it %[should raise LockAssertionFailedError if there is an exclusive lock with a number lower than ours] do
|
237
240
|
# this should *really* never happen
|
238
|
-
|
239
|
-
|
241
|
+
|
242
|
+
rlp = ex_locker.root_lock_path
|
243
|
+
|
244
|
+
zk.mkdir_p(rlp)
|
245
|
+
|
246
|
+
bogus_path = zk.create("#{rlp}/#{ZK::Locker::EXCLUSIVE_LOCK_PREFIX}", :sequential => true, :ephemeral => true)
|
240
247
|
|
241
248
|
th = Thread.new do
|
242
249
|
ex_locker2.lock(true)
|
243
250
|
end
|
244
251
|
|
245
|
-
|
252
|
+
logger.debug { "calling wait_until_blocked" }
|
253
|
+
ex_locker2.wait_until_blocked(2)
|
254
|
+
ex_locker2.should be_waiting
|
246
255
|
|
247
|
-
|
248
|
-
|
249
|
-
zk.exists?(
|
256
|
+
wait_until { zk.exists?(ex_locker2.lock_path) }
|
257
|
+
|
258
|
+
zk.exists?(ex_locker2.lock_path).should be_true
|
259
|
+
|
260
|
+
zk.delete(bogus_path)
|
250
261
|
|
251
262
|
th.join(5).should == th
|
252
263
|
|
253
|
-
ex_locker2.lock_path.should_not ==
|
264
|
+
ex_locker2.lock_path.should_not == bogus_path
|
254
265
|
|
255
|
-
zk.create(
|
266
|
+
zk.create(bogus_path, :ephemeral => true)
|
256
267
|
|
257
268
|
lambda { ex_locker2.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
|
258
269
|
end
|
@@ -299,11 +310,11 @@ shared_examples_for 'ExclusiveLocker' do
|
|
299
310
|
describe 'blocking' do
|
300
311
|
before do
|
301
312
|
zk.mkdir_p(root_lock_path)
|
302
|
-
@read_lock_path = zk.create('/_zklocking/shlock/read', '', :mode => :ephemeral_sequential)
|
303
313
|
end
|
304
314
|
|
305
315
|
it %[should block waiting for the lock] do
|
306
316
|
ary = []
|
317
|
+
read_lock_path = zk.create('/_zklocking/shlock/read', '', :mode => :ephemeral_sequential)
|
307
318
|
|
308
319
|
ex_locker.lock.should be_false
|
309
320
|
|
@@ -312,20 +323,20 @@ shared_examples_for 'ExclusiveLocker' do
|
|
312
323
|
ary << :locked
|
313
324
|
end
|
314
325
|
|
315
|
-
|
326
|
+
ex_locker.wait_until_blocked(5)
|
316
327
|
|
317
328
|
ary.should be_empty
|
318
329
|
ex_locker.should_not be_locked
|
319
330
|
|
320
|
-
zk.delete(
|
331
|
+
zk.delete(read_lock_path)
|
321
332
|
|
322
|
-
th.join(2)
|
333
|
+
th.join(2).should == th
|
323
334
|
|
324
335
|
ary.length.should == 1
|
325
336
|
ex_locker.should be_locked
|
326
337
|
end
|
327
|
-
end
|
328
|
-
end
|
338
|
+
end # blocking
|
339
|
+
end # lock
|
329
340
|
end # ExclusiveLocker
|
330
341
|
|
331
342
|
shared_examples_for 'shared-exclusive interaction' do
|
@@ -428,8 +439,13 @@ shared_examples_for 'shared-exclusive interaction' do
|
|
428
439
|
end
|
429
440
|
end
|
430
441
|
|
431
|
-
|
442
|
+
logger.debug { "about to wait for @ex_lock to be blocked" }
|
443
|
+
|
444
|
+
@ex_lock.wait_until_blocked(5)
|
432
445
|
@ex_lock.should be_waiting
|
446
|
+
|
447
|
+
logger.debug { "@ex_lock is waiting" }
|
448
|
+
|
433
449
|
@ex_lock.should_not be_locked
|
434
450
|
|
435
451
|
# this is the important one, does the second shared lock get blocked by
|
@@ -446,15 +462,19 @@ shared_examples_for 'shared-exclusive interaction' do
|
|
446
462
|
end
|
447
463
|
end
|
448
464
|
|
449
|
-
|
465
|
+
logger.debug { "about to wait for @sh_lock2 to be blocked" }
|
466
|
+
|
467
|
+
@sh_lock2.wait_until_blocked(5)
|
450
468
|
@sh_lock2.should be_waiting
|
451
469
|
|
470
|
+
logger.debug { "@sh_lock2 is waiting" }
|
471
|
+
|
452
472
|
@sh_lock.unlock.should be_true
|
453
473
|
|
454
|
-
ex_th.
|
474
|
+
ex_th.join(5).should == ex_th
|
455
475
|
ex_th[:got_lock].should be_true
|
456
476
|
|
457
|
-
sh2_th.
|
477
|
+
sh2_th.join(5).should == sh2_th
|
458
478
|
sh2_th[:got_lock].should be_true
|
459
479
|
|
460
480
|
@array.length.should == 2
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ZK::NodeDeletionWatcher do
|
4
|
+
include_context 'threaded client connection'
|
5
|
+
|
6
|
+
before do
|
7
|
+
@path = "#{@base_path}/node_deleteion_watcher_victim"
|
8
|
+
|
9
|
+
@n = ZK::NodeDeletionWatcher.new(@zk, @path)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe %[when the node already exists] do
|
13
|
+
it %[blocks the caller until the node is deleted] do
|
14
|
+
@zk.mkdir_p(@path)
|
15
|
+
|
16
|
+
th = Thread.new { @n.block_until_deleted }
|
17
|
+
|
18
|
+
@n.wait_until_blocked(5).should be_true
|
19
|
+
|
20
|
+
logger.debug { "wait_until_blocked returned" }
|
21
|
+
|
22
|
+
@n.should be_blocked
|
23
|
+
|
24
|
+
@zk.rm_rf(@path)
|
25
|
+
|
26
|
+
th.join(5).should == th
|
27
|
+
@n.should_not be_blocked
|
28
|
+
@n.should be_done
|
29
|
+
end
|
30
|
+
|
31
|
+
it %[should wake up if interrupt! is called] do
|
32
|
+
@zk.mkdir_p(@path)
|
33
|
+
|
34
|
+
@exc = nil
|
35
|
+
|
36
|
+
th = Thread.new do
|
37
|
+
begin
|
38
|
+
@n.block_until_deleted
|
39
|
+
rescue Exception => e
|
40
|
+
@exc = e
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@n.wait_until_blocked(5)
|
45
|
+
|
46
|
+
@n.should be_blocked
|
47
|
+
|
48
|
+
@n.interrupt!
|
49
|
+
th.join(5).should == th
|
50
|
+
|
51
|
+
@exc.should be_kind_of(ZK::Exceptions::WakeUpException)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe %[when the node doesn't exist] do
|
56
|
+
it %[should not block the caller and be done] do
|
57
|
+
@zk.exists?(@path).should be_false
|
58
|
+
|
59
|
+
th = Thread.new { @n.block_until_deleted }
|
60
|
+
|
61
|
+
@n.wait_until_blocked
|
62
|
+
@n.should_not be_blocked
|
63
|
+
th.join(5).should == th
|
64
|
+
@n.should be_done
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
data/spec/zk/pool_spec.rb
CHANGED
@@ -55,17 +55,27 @@ describe ZK::Pool do
|
|
55
55
|
it %[should shutdown gracefully] do
|
56
56
|
release_q = Queue.new
|
57
57
|
|
58
|
+
latch = Latch.new
|
59
|
+
|
58
60
|
@about_to_block = false
|
59
61
|
|
62
|
+
@mutex = Mutex.new
|
63
|
+
@cond = ConditionVariable.new
|
64
|
+
@cnx = nil
|
65
|
+
|
60
66
|
open_th = Thread.new do
|
61
|
-
@
|
62
|
-
|
63
|
-
|
64
|
-
|
67
|
+
@mutex.synchronize do
|
68
|
+
@cnx = @connection_pool.checkout(true)
|
69
|
+
@cond.broadcast
|
70
|
+
end
|
71
|
+
latch.await(30) # don't time out
|
72
|
+
end
|
73
|
+
|
74
|
+
@mutex.synchronize do
|
75
|
+
@cond.wait(@mutex) while @cnx.nil?
|
65
76
|
end
|
66
77
|
|
67
|
-
|
68
|
-
@about_to_block.should be_true
|
78
|
+
@cnx.should_not be_nil
|
69
79
|
|
70
80
|
closing_th = Thread.new do
|
71
81
|
@connection_pool.close_all!
|
@@ -75,14 +85,13 @@ describe ZK::Pool do
|
|
75
85
|
@connection_pool.should be_closing
|
76
86
|
logger.debug { "connection pool is closing" }
|
77
87
|
|
78
|
-
lambda { @connection_pool.with_connection { |c| } }.should raise_error(ZK::Exceptions::PoolIsShuttingDownException)
|
88
|
+
lambda { @connection_pool.with_connection { |c| c } }.should raise_error(ZK::Exceptions::PoolIsShuttingDownException)
|
79
89
|
|
80
|
-
|
90
|
+
latch.release
|
81
91
|
|
82
92
|
open_th.join(5).should == open_th
|
83
93
|
|
84
|
-
|
85
|
-
# $stderr.puts "@connection_pool.pool_state: #{@connection_pool.pool_state.inspect}"
|
94
|
+
@connection_pool.wait_until_closed
|
86
95
|
|
87
96
|
@connection_pool.should be_closed
|
88
97
|
|
@@ -94,7 +103,7 @@ describe ZK::Pool do
|
|
94
103
|
end
|
95
104
|
|
96
105
|
describe :force_close! do
|
97
|
-
it %[should raise PoolIsShuttingDownException in a thread blocked waiting for a connection] do
|
106
|
+
it %[should raise PoolIsShuttingDownException in a thread blocked waiting for a connection], :mri_187 => :broken do
|
98
107
|
@cnx = []
|
99
108
|
|
100
109
|
until @connection_pool.available_size <= 0
|
@@ -103,8 +112,15 @@ describe ZK::Pool do
|
|
103
112
|
|
104
113
|
@cnx.length.should_not be_zero
|
105
114
|
|
115
|
+
# this exc nonsense is because 1.8.7's scheduler is broken
|
116
|
+
@exc = nil
|
117
|
+
|
106
118
|
th = Thread.new do
|
107
|
-
|
119
|
+
begin
|
120
|
+
@connection_pool.checkout(true)
|
121
|
+
rescue
|
122
|
+
@exc = $!
|
123
|
+
end
|
108
124
|
end
|
109
125
|
|
110
126
|
# th.join_until { @connection_pool.count_waiters > 0 }
|
@@ -112,7 +128,10 @@ describe ZK::Pool do
|
|
112
128
|
|
113
129
|
@connection_pool.force_close!
|
114
130
|
|
115
|
-
|
131
|
+
@connection_pool.wait_until_closed
|
132
|
+
|
133
|
+
th.join(5).should == th
|
134
|
+
@exc.should be_kind_of(ZK::Exceptions::PoolIsShuttingDownException)
|
116
135
|
end
|
117
136
|
end
|
118
137
|
|
@@ -129,21 +148,16 @@ describe ZK::Pool do
|
|
129
148
|
end
|
130
149
|
|
131
150
|
@connection_pool.with_connection do |zk|
|
132
|
-
# $stderr.puts "registering callback"
|
133
151
|
zk.watcher.register(@path) do |event|
|
134
|
-
# $stderr.puts "callback fired! event: #{event.inspect}"
|
135
152
|
|
136
153
|
@callback_called = true
|
137
154
|
event.path.should == @path
|
138
|
-
# $stderr.puts "signaling other waiters"
|
139
155
|
end
|
140
156
|
|
141
|
-
# $stderr.puts "setting up watcher"
|
142
157
|
zk.exists?(@path, :watch => true).should be_false
|
143
158
|
end
|
144
159
|
|
145
160
|
@connection_pool.with_connection do |zk|
|
146
|
-
# $stderr.puts "creating path"
|
147
161
|
zk.create(@path, "", :mode => :ephemeral).should == @path
|
148
162
|
end
|
149
163
|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ZK::ThreadedCallback do
|
4
|
+
before do
|
5
|
+
@called = []
|
6
|
+
@called.extend(MonitorMixin)
|
7
|
+
@cond = @called.new_cond
|
8
|
+
|
9
|
+
@callback = proc do |*a|
|
10
|
+
@called.synchronize do
|
11
|
+
@called << a
|
12
|
+
@cond.broadcast
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@tcb = ZK::ThreadedCallback.new(@callback)
|
17
|
+
end
|
18
|
+
|
19
|
+
after do
|
20
|
+
@tcb.shutdown.should be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
it %[should have started the thread] do
|
24
|
+
@tcb.should be_alive
|
25
|
+
end
|
26
|
+
|
27
|
+
describe %[pausing for fork] do
|
28
|
+
describe %[when running] do
|
29
|
+
before do
|
30
|
+
@tcb.pause_before_fork_in_parent
|
31
|
+
end
|
32
|
+
|
33
|
+
it %[should stop the thread] do
|
34
|
+
@tcb.should_not be_alive
|
35
|
+
end
|
36
|
+
|
37
|
+
it %[should allow calls] do
|
38
|
+
lambda { @tcb.call(:a) }.should_not raise_error
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe %[when not running] do
|
43
|
+
it %[should barf with InvalidStateError] do
|
44
|
+
@tcb.shutdown.should be_true
|
45
|
+
@tcb.should_not be_alive
|
46
|
+
lambda { @tcb.pause_before_fork_in_parent }.should raise_error(ZK::Exceptions::InvalidStateError)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe %[resuming] do
|
52
|
+
describe %[after pause] do
|
53
|
+
before do
|
54
|
+
@tcb.pause_before_fork_in_parent
|
55
|
+
@tcb.should_not be_alive
|
56
|
+
end
|
57
|
+
|
58
|
+
it %[should deliver any calls on resume] do
|
59
|
+
@tcb.call(:a)
|
60
|
+
@tcb.call(:b)
|
61
|
+
|
62
|
+
@tcb.resume_after_fork_in_parent
|
63
|
+
|
64
|
+
start = Time.now
|
65
|
+
|
66
|
+
wait_until { @called.length >= 2 }
|
67
|
+
|
68
|
+
@called.length.should >= 2
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe %[if not paused] do
|
73
|
+
it %[should barf with InvalidStateError] do
|
74
|
+
lambda { @tcb.resume_after_fork_in_parent }.should raise_error(ZK::Exceptions::InvalidStateError)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/spec/zk/threadpool_spec.rb
CHANGED
@@ -98,5 +98,57 @@ describe ZK::Threadpool do
|
|
98
98
|
@a.first.should be_true
|
99
99
|
end
|
100
100
|
end
|
101
|
+
|
102
|
+
describe :pause_before_fork_in_parent do
|
103
|
+
it %[should stop all running threads] do
|
104
|
+
@threadpool.should be_running
|
105
|
+
@threadpool.should be_alive
|
106
|
+
@threadpool.pause_before_fork_in_parent
|
107
|
+
|
108
|
+
@threadpool.should_not be_alive
|
109
|
+
end
|
110
|
+
|
111
|
+
it %[should raise InvalidStateError if already paused] do
|
112
|
+
@threadpool.pause_before_fork_in_parent
|
113
|
+
lambda { @threadpool.pause_before_fork_in_parent }.should raise_error(ZK::Exceptions::InvalidStateError)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe :resume_after_fork_in_parent do
|
118
|
+
before do
|
119
|
+
@threadpool.pause_before_fork_in_parent
|
120
|
+
end
|
121
|
+
|
122
|
+
it %[should start all threads running again] do
|
123
|
+
@threadpool.resume_after_fork_in_parent
|
124
|
+
@threadpool.should be_alive
|
125
|
+
end
|
126
|
+
|
127
|
+
it %[should raise InvalidStateError if not in paused state] do
|
128
|
+
@threadpool.shutdown
|
129
|
+
lambda { @threadpool.resume_after_fork_in_parent }.should raise_error(ZK::Exceptions::InvalidStateError)
|
130
|
+
end
|
131
|
+
|
132
|
+
it %[should run callbacks deferred while paused] do
|
133
|
+
calls = []
|
134
|
+
|
135
|
+
num = 5
|
136
|
+
|
137
|
+
latch = Latch.new(num)
|
138
|
+
|
139
|
+
num.times do |n|
|
140
|
+
@threadpool.defer do
|
141
|
+
calls << n
|
142
|
+
latch.release
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
@threadpool.resume_after_fork_in_parent
|
147
|
+
|
148
|
+
latch.await(2)
|
149
|
+
|
150
|
+
calls.should_not be_empty
|
151
|
+
end
|
152
|
+
end
|
101
153
|
end
|
102
154
|
|