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/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
|
|