zk 1.5.1 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/Guardfile +9 -5
- data/README.markdown +1 -1
- data/RELEASES.markdown +8 -0
- data/lib/zk/client/threaded.rb +12 -5
- data/lib/zk/fork_hook.rb +3 -0
- data/lib/zk/locker/locker_base.rb +58 -14
- data/lib/zk/version.rb +1 -1
- data/spec/message_queue_spec.rb +3 -2
- data/spec/shared/client_contexts.rb +1 -1
- data/spec/shared/locker_contexts.rb +53 -0
- data/spec/shared/locker_examples.rb +55 -0
- data/spec/support/logging.rb +37 -23
- data/spec/zk/locker/exclusive_locker_spec.rb +122 -0
- data/spec/zk/locker/locker_basic_spec.rb +79 -0
- data/spec/zk/locker/shared_exclusive_integration_spec.rb +157 -0
- data/spec/zk/locker/shared_locker_spec.rb +137 -0
- data/spec/zk/pool_spec.rb +6 -3
- data/spec/zk/watch_spec.rb +0 -1
- data/spec/zk/zookeeper_spec.rb +2 -1
- data/zk.gemspec +1 -1
- metadata +19 -9
- data/spec/zk/locker_spec.rb +0 -552
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples_for 'ZK::Locker::ExclusiveLocker' do
|
4
|
+
let(:locker) { ZK::Locker.exclusive_locker(zk, path) }
|
5
|
+
let(:locker2) { ZK::Locker.exclusive_locker(zk2, path) }
|
6
|
+
|
7
|
+
describe :assert! do
|
8
|
+
it_should_behave_like 'LockerBase#assert!'
|
9
|
+
|
10
|
+
it %[should raise LockAssertionFailedError if there is an exclusive lock with a number lower than ours] do
|
11
|
+
# this should *really* never happen
|
12
|
+
|
13
|
+
rlp = locker.root_lock_path
|
14
|
+
|
15
|
+
zk.mkdir_p(rlp)
|
16
|
+
|
17
|
+
bogus_path = zk.create("#{rlp}/#{ZK::Locker::EXCLUSIVE_LOCK_PREFIX}", :sequential => true, :ephemeral => true)
|
18
|
+
|
19
|
+
th = Thread.new do
|
20
|
+
locker2.lock(true)
|
21
|
+
end
|
22
|
+
|
23
|
+
logger.debug { "calling wait_until_blocked" }
|
24
|
+
locker2.wait_until_blocked(2)
|
25
|
+
locker2.should be_waiting
|
26
|
+
|
27
|
+
wait_until { zk.exists?(locker2.lock_path) }
|
28
|
+
|
29
|
+
zk.exists?(locker2.lock_path).should be_true
|
30
|
+
|
31
|
+
zk.delete(bogus_path)
|
32
|
+
|
33
|
+
th.join(5).should == th
|
34
|
+
|
35
|
+
locker2.lock_path.should_not == bogus_path
|
36
|
+
|
37
|
+
zk.create(bogus_path, :ephemeral => true)
|
38
|
+
|
39
|
+
lambda { locker2.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe :acquirable? do
|
44
|
+
it %[should work if the lock root doesn't exist] do
|
45
|
+
zk.rm_rf(ZK::Locker.default_root_lock_node)
|
46
|
+
locker.should be_acquirable
|
47
|
+
end
|
48
|
+
|
49
|
+
it %[should check local state of lockedness] do
|
50
|
+
locker.lock.should be_true
|
51
|
+
locker.should be_acquirable
|
52
|
+
end
|
53
|
+
|
54
|
+
it %[should check if any participants would prevent us from acquiring the lock] do
|
55
|
+
locker.lock.should be_true
|
56
|
+
locker2.should_not be_acquirable
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe :lock do
|
61
|
+
describe 'non-blocking' do
|
62
|
+
before do
|
63
|
+
@rval = locker.lock
|
64
|
+
@rval2 = locker2.lock
|
65
|
+
end
|
66
|
+
|
67
|
+
it %[should acquire the first lock] do
|
68
|
+
@rval.should be_true
|
69
|
+
end
|
70
|
+
|
71
|
+
it %[should not acquire the second lock] do
|
72
|
+
@rval2.should be_false
|
73
|
+
end
|
74
|
+
|
75
|
+
it %[should acquire the second lock after the first lock is released] do
|
76
|
+
locker.unlock.should be_true
|
77
|
+
locker2.lock.should be_true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'blocking' do
|
82
|
+
before do
|
83
|
+
zk.mkdir_p(root_lock_path)
|
84
|
+
end
|
85
|
+
|
86
|
+
it %[should block waiting for the lock] do
|
87
|
+
ary = []
|
88
|
+
read_lock_path = zk.create("/_zklocking/#{path}/read", '', :mode => :ephemeral_sequential)
|
89
|
+
|
90
|
+
locker.lock.should be_false
|
91
|
+
|
92
|
+
th = Thread.new do
|
93
|
+
locker.lock(true)
|
94
|
+
ary << :locked
|
95
|
+
end
|
96
|
+
|
97
|
+
locker.wait_until_blocked(5)
|
98
|
+
|
99
|
+
ary.should be_empty
|
100
|
+
locker.should_not be_locked
|
101
|
+
|
102
|
+
zk.delete(read_lock_path)
|
103
|
+
|
104
|
+
th.join(2).should == th
|
105
|
+
|
106
|
+
ary.length.should == 1
|
107
|
+
locker.should be_locked
|
108
|
+
end
|
109
|
+
end # blocking
|
110
|
+
end # lock
|
111
|
+
end # ExclusiveLocker
|
112
|
+
|
113
|
+
describe do
|
114
|
+
include_context 'locker non-chrooted'
|
115
|
+
it_should_behave_like 'ZK::Locker::ExclusiveLocker'
|
116
|
+
end
|
117
|
+
|
118
|
+
describe :chrooted => true do
|
119
|
+
include_context 'locker chrooted'
|
120
|
+
it_should_behave_like 'ZK::Locker::ExclusiveLocker'
|
121
|
+
end
|
122
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# this is a remnant of the old Locker class, but a good test of what's expected
|
4
|
+
# from ZK::Client#locker
|
5
|
+
#
|
6
|
+
describe 'ZK::Client#locker' do
|
7
|
+
include_context 'connection opts'
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@zk = ZK.new(*connection_args)
|
11
|
+
@zk2 = ZK.new(*connection_args)
|
12
|
+
@zk3 = ZK.new(*connection_args)
|
13
|
+
@connections = [@zk, @zk2, @zk3]
|
14
|
+
wait_until { @connections.all? { |c| c.connected? } }
|
15
|
+
logger.debug { "all connections connected" }
|
16
|
+
@path_to_lock = "/lock_tester"
|
17
|
+
end
|
18
|
+
|
19
|
+
after(:each) do
|
20
|
+
@zk.close!
|
21
|
+
@zk2.close!
|
22
|
+
@zk3.close!
|
23
|
+
wait_until { @connections.all? { |c| c.closed? } }
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should be able to acquire the lock if no one else is locking it" do
|
27
|
+
@zk.locker(@path_to_lock).lock.should be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not be able to acquire the lock if someone else is locking it" do
|
31
|
+
@zk.locker(@path_to_lock).lock.should be_true
|
32
|
+
@zk2.locker(@path_to_lock).lock.should be_false
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be able to acquire the lock after the first one releases it" do
|
36
|
+
lock1 = @zk.locker(@path_to_lock)
|
37
|
+
lock2 = @zk2.locker(@path_to_lock)
|
38
|
+
|
39
|
+
lock1.lock.should be_true
|
40
|
+
lock2.lock.should be_false
|
41
|
+
lock1.unlock
|
42
|
+
lock2.lock.should be_true
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should be able to acquire the lock if the first locker goes away" do
|
46
|
+
lock1 = @zk.locker(@path_to_lock)
|
47
|
+
lock2 = @zk2.locker(@path_to_lock)
|
48
|
+
|
49
|
+
lock1.lock.should be_true
|
50
|
+
lock2.lock.should be_false
|
51
|
+
@zk.close!
|
52
|
+
lock2.lock.should be_true
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should be able to handle multi part path locks" do
|
56
|
+
@zk.locker("my/multi/part/path").lock.should be_true
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should blocking lock" do
|
60
|
+
array = []
|
61
|
+
first_lock = @zk.locker("mylock")
|
62
|
+
first_lock.lock.should be_true
|
63
|
+
array << :first_lock
|
64
|
+
|
65
|
+
thread = Thread.new do
|
66
|
+
@zk.locker("mylock").with_lock do
|
67
|
+
array << :second_lock
|
68
|
+
end
|
69
|
+
array.length.should == 2
|
70
|
+
end
|
71
|
+
|
72
|
+
array.length.should == 1
|
73
|
+
first_lock.unlock
|
74
|
+
thread.join(10)
|
75
|
+
array.length.should == 2
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples_for :shared_exclusive_integration do
|
4
|
+
before do
|
5
|
+
@sh_lock = ZK::Locker.shared_locker(zk, path)
|
6
|
+
@ex_lock = ZK::Locker.exclusive_locker(zk2, path)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'shared lock acquired first' do
|
10
|
+
it %[should block exclusive locks from acquiring until released] do
|
11
|
+
@sh_lock.lock.should be_true
|
12
|
+
@ex_lock.lock.should be_false
|
13
|
+
|
14
|
+
mutex = Monitor.new
|
15
|
+
cond = mutex.new_cond
|
16
|
+
|
17
|
+
th = Thread.new do
|
18
|
+
logger.debug { "@ex_lock trying to acquire acquire lock" }
|
19
|
+
@ex_lock.with_lock do
|
20
|
+
th[:got_lock] = @ex_lock.locked?
|
21
|
+
logger.debug { "@ex_lock.locked? #{@ex_lock.locked?}" }
|
22
|
+
|
23
|
+
mutex.synchronize do
|
24
|
+
cond.broadcast
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
mutex.synchronize do
|
30
|
+
logger.debug { "unlocking the shared lock" }
|
31
|
+
@sh_lock.unlock.should be_true
|
32
|
+
cond.wait_until { th[:got_lock] } # make sure they actually received the lock (avoid race)
|
33
|
+
th[:got_lock].should be_true
|
34
|
+
logger.debug { "ok, they got the lock" }
|
35
|
+
end
|
36
|
+
|
37
|
+
th.join(5).should == th
|
38
|
+
|
39
|
+
logger.debug { "thread joined, exclusive lock should be releasd" }
|
40
|
+
|
41
|
+
@ex_lock.should_not be_locked
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'exclusive lock acquired first' do
|
46
|
+
it %[should block shared lock from acquiring until released] do
|
47
|
+
@ex_lock.lock.should be_true
|
48
|
+
@sh_lock.lock.should be_false
|
49
|
+
|
50
|
+
mutex = Monitor.new
|
51
|
+
cond = mutex.new_cond
|
52
|
+
|
53
|
+
th = Thread.new do
|
54
|
+
logger.debug { "@ex_lock trying to acquire acquire lock" }
|
55
|
+
@sh_lock.with_lock do
|
56
|
+
th[:got_lock] = @sh_lock.locked?
|
57
|
+
logger.debug { "@sh_lock.locked? #{@sh_lock.locked?}" }
|
58
|
+
|
59
|
+
mutex.synchronize do
|
60
|
+
cond.broadcast
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
mutex.synchronize do
|
66
|
+
logger.debug { "unlocking the shared lock" }
|
67
|
+
@ex_lock.unlock.should be_true
|
68
|
+
cond.wait_until { th[:got_lock] } # make sure they actually received the lock (avoid race)
|
69
|
+
th[:got_lock].should be_true
|
70
|
+
logger.debug { "ok, they got the lock" }
|
71
|
+
end
|
72
|
+
|
73
|
+
th.join(5).should == th
|
74
|
+
|
75
|
+
logger.debug { "thread joined, exclusive lock should be releasd" }
|
76
|
+
|
77
|
+
@sh_lock.should_not be_locked
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'shared-exclusive-shared' do
|
82
|
+
before do
|
83
|
+
zk3.should_not be_nil
|
84
|
+
@sh_lock2 = ZK::Locker.shared_locker(zk3, path)
|
85
|
+
end
|
86
|
+
|
87
|
+
it %[should act something like a queue] do
|
88
|
+
@array = []
|
89
|
+
|
90
|
+
@sh_lock.lock.should be_true
|
91
|
+
@sh_lock.should be_locked
|
92
|
+
|
93
|
+
ex_th = Thread.new do
|
94
|
+
begin
|
95
|
+
@ex_lock.lock(true) # blocking lock
|
96
|
+
@ex_lock.assert!
|
97
|
+
@array << :ex_lock
|
98
|
+
ensure
|
99
|
+
@ex_lock.unlock
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
logger.debug { "about to wait for @ex_lock to be blocked" }
|
104
|
+
|
105
|
+
@ex_lock.wait_until_blocked(5)
|
106
|
+
@ex_lock.should be_waiting
|
107
|
+
|
108
|
+
logger.debug { "@ex_lock is waiting" }
|
109
|
+
|
110
|
+
@ex_lock.should_not be_locked
|
111
|
+
|
112
|
+
# this is the important one, does the second shared lock get blocked by
|
113
|
+
# the exclusive lock
|
114
|
+
@sh_lock2.lock.should_not be_true
|
115
|
+
|
116
|
+
sh2_th = Thread.new do
|
117
|
+
begin
|
118
|
+
@sh_lock2.lock(true)
|
119
|
+
@sh_lock2.assert!
|
120
|
+
@array << :sh_lock2
|
121
|
+
ensure
|
122
|
+
@sh_lock2.unlock
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
logger.debug { "about to wait for @sh_lock2 to be blocked" }
|
127
|
+
|
128
|
+
@sh_lock2.wait_until_blocked(5)
|
129
|
+
@sh_lock2.should be_waiting
|
130
|
+
|
131
|
+
logger.debug { "@sh_lock2 is waiting" }
|
132
|
+
|
133
|
+
# ok, now unlock the first in the chain
|
134
|
+
@sh_lock.assert!
|
135
|
+
@sh_lock.unlock.should be_true
|
136
|
+
|
137
|
+
ex_th.join(5).should == ex_th
|
138
|
+
sh2_th.join(5).should == sh2_th
|
139
|
+
|
140
|
+
@array.length.should == 2
|
141
|
+
@array.should == [:ex_lock, :sh_lock2]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end # shared_exclusive_integration
|
145
|
+
|
146
|
+
describe do
|
147
|
+
include_context 'locker non-chrooted'
|
148
|
+
|
149
|
+
it_should_behave_like :shared_exclusive_integration
|
150
|
+
end
|
151
|
+
|
152
|
+
describe :chrooted => true do
|
153
|
+
include_context 'locker chrooted'
|
154
|
+
|
155
|
+
it_should_behave_like :shared_exclusive_integration
|
156
|
+
end
|
157
|
+
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples_for 'ZK::Locker::SharedLocker' do
|
4
|
+
let(:locker) { ZK::Locker::SharedLocker.new(zk, path) }
|
5
|
+
let(:locker2) { ZK::Locker::SharedLocker.new(zk2, path) }
|
6
|
+
|
7
|
+
describe :assert! do
|
8
|
+
it_should_behave_like 'LockerBase#assert!'
|
9
|
+
|
10
|
+
it %[should raise LockAssertionFailedError if there is an exclusive lock with a number lower than ours] do
|
11
|
+
# this should *really* never happen
|
12
|
+
locker.lock.should be_true
|
13
|
+
shl_path = locker.lock_path
|
14
|
+
|
15
|
+
locker2.lock.should be_true
|
16
|
+
|
17
|
+
locker.unlock.should be_true
|
18
|
+
locker.should_not be_locked
|
19
|
+
|
20
|
+
zk.exists?(shl_path).should be_false
|
21
|
+
|
22
|
+
locker2.lock_path.should_not == shl_path
|
23
|
+
|
24
|
+
# convert the first shared lock path into a exclusive one
|
25
|
+
|
26
|
+
exl_path = shl_path.sub(%r%/sh(\d+)\Z%, '/ex\1')
|
27
|
+
|
28
|
+
zk.create(exl_path, :ephemeral => true)
|
29
|
+
|
30
|
+
lambda { locker2.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe :acquirable? do
|
35
|
+
describe %[with default options] do
|
36
|
+
it %[should work if the lock root doesn't exist] do
|
37
|
+
zk.rm_rf(ZK::Locker.default_root_lock_node)
|
38
|
+
locker.should be_acquirable
|
39
|
+
end
|
40
|
+
|
41
|
+
it %[should check local state of lockedness] do
|
42
|
+
locker.lock.should be_true
|
43
|
+
locker.should be_acquirable
|
44
|
+
end
|
45
|
+
|
46
|
+
it %[should check if any participants would prevent us from acquiring the lock] do
|
47
|
+
ex_lock = ZK::Locker.exclusive_locker(zk, path)
|
48
|
+
ex_lock.lock.should be_true
|
49
|
+
locker.should_not be_acquirable
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe :lock do
|
55
|
+
describe 'non-blocking success' do
|
56
|
+
before do
|
57
|
+
@rval = locker.lock
|
58
|
+
@rval2 = locker2.lock
|
59
|
+
end
|
60
|
+
|
61
|
+
it %[should acquire the first lock] do
|
62
|
+
@rval.should be_true
|
63
|
+
locker.should be_locked
|
64
|
+
end
|
65
|
+
|
66
|
+
it %[should acquire the second lock] do
|
67
|
+
@rval2.should be_true
|
68
|
+
locker2.should be_locked
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'non-blocking failure' do
|
73
|
+
before do
|
74
|
+
zk.mkdir_p(root_lock_path)
|
75
|
+
@write_lock_path = zk.create("#{root_lock_path}/#{ZK::Locker::EXCLUSIVE_LOCK_PREFIX}", '', :mode => :ephemeral_sequential)
|
76
|
+
@rval = locker.lock
|
77
|
+
end
|
78
|
+
|
79
|
+
it %[should return false] do
|
80
|
+
@rval.should be_false
|
81
|
+
end
|
82
|
+
|
83
|
+
it %[should not be locked] do
|
84
|
+
locker.should_not be_locked
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'blocking success' do
|
89
|
+
before do
|
90
|
+
zk.mkdir_p(root_lock_path)
|
91
|
+
@write_lock_path = zk.create("#{root_lock_path}/#{ZK::Locker::EXCLUSIVE_LOCK_PREFIX}", '', :mode => :ephemeral_sequential)
|
92
|
+
$stderr.sync = true
|
93
|
+
end
|
94
|
+
|
95
|
+
it %[should acquire the lock after the write lock is released] do
|
96
|
+
ary = []
|
97
|
+
|
98
|
+
locker.lock.should be_false
|
99
|
+
|
100
|
+
th = Thread.new do
|
101
|
+
locker.lock(true)
|
102
|
+
ary << :locked
|
103
|
+
end
|
104
|
+
|
105
|
+
locker.wait_until_blocked(5)
|
106
|
+
locker.should be_waiting
|
107
|
+
locker.should_not be_locked
|
108
|
+
ary.should be_empty
|
109
|
+
|
110
|
+
zk.delete(@write_lock_path)
|
111
|
+
|
112
|
+
th.join(2).should == th
|
113
|
+
|
114
|
+
ary.should_not be_empty
|
115
|
+
ary.length.should == 1
|
116
|
+
|
117
|
+
locker.should be_locked
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end # lock
|
121
|
+
|
122
|
+
it_should_behave_like 'LockerBase#unlock'
|
123
|
+
end # SharedLocker
|
124
|
+
|
125
|
+
|
126
|
+
describe do
|
127
|
+
include_context 'locker non-chrooted'
|
128
|
+
|
129
|
+
it_should_behave_like 'ZK::Locker::SharedLocker'
|
130
|
+
end
|
131
|
+
|
132
|
+
describe :chrooted => true do
|
133
|
+
include_context 'locker chrooted'
|
134
|
+
|
135
|
+
it_should_behave_like 'ZK::Locker::SharedLocker'
|
136
|
+
end
|
137
|
+
|