zk 1.5.1 → 1.5.2
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/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
|
+
|