zk 1.9.6 → 1.10.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.
- checksums.yaml +5 -13
- data/.github/workflows/build.yml +55 -0
- data/Gemfile +15 -6
- data/README.markdown +9 -9
- data/RELEASES.markdown +10 -1
- data/lib/zk/client.rb +1 -1
- data/lib/zk/event_handler.rb +15 -7
- data/lib/zk/fork_hook.rb +2 -2
- data/lib/zk/locker/locker_base.rb +13 -0
- data/lib/zk/locker/semaphore.rb +1 -3
- data/lib/zk/node_deletion_watcher.rb +3 -4
- data/lib/zk/pool.rb +11 -11
- data/lib/zk/version.rb +1 -1
- data/spec/event_catcher_spec.rb +1 -1
- data/spec/message_queue_spec.rb +6 -6
- data/spec/shared/client_examples.rb +81 -81
- data/spec/shared/locker_examples.rb +13 -13
- data/spec/spec_helper.rb +10 -11
- data/spec/zk/00_forked_client_integration_spec.rb +3 -3
- data/spec/zk/client/locking_and_session_death_spec.rb +2 -2
- data/spec/zk/client_spec.rb +19 -19
- data/spec/zk/election_spec.rb +44 -44
- data/spec/zk/extensions_spec.rb +2 -2
- data/spec/zk/locker/exclusive_locker_spec.rb +41 -41
- data/spec/zk/locker/locker_basic_spec.rb +55 -33
- data/spec/zk/locker/semaphore_spec.rb +39 -32
- data/spec/zk/locker/shared_exclusive_integration_spec.rb +37 -37
- data/spec/zk/locker/shared_locker_spec.rb +43 -43
- data/spec/zk/locker_spec.rb +2 -2
- data/spec/zk/module_spec.rb +25 -25
- data/spec/zk/mongoid_spec.rb +47 -47
- data/spec/zk/node_deletion_watcher_spec.rb +39 -31
- data/spec/zk/pool_spec.rb +56 -65
- data/spec/zk/threaded_callback_spec.rb +10 -10
- data/spec/zk/threadpool_spec.rb +25 -25
- data/spec/zk/watch_spec.rb +67 -79
- data/spec/zk/zookeeper_spec.rb +31 -31
- data/zk.gemspec +1 -1
- metadata +23 -24
- data/.travis.yml +0 -30
@@ -20,47 +20,47 @@ describe 'ZK::Client#locker' do
|
|
20
20
|
@zk.close!
|
21
21
|
@zk2.close!
|
22
22
|
@zk3.close!
|
23
|
-
wait_until { @connections.all? { |c| c.closed? } }
|
23
|
+
wait_until { @connections.all? { |c| c.closed? } }
|
24
24
|
end
|
25
25
|
|
26
26
|
it "should be able to acquire the lock if no one else is locking it" do
|
27
|
-
@zk.locker(@path_to_lock).lock.
|
27
|
+
expect(@zk.locker(@path_to_lock).lock).to be(true)
|
28
28
|
end
|
29
29
|
|
30
30
|
it "should not be able to acquire the lock if someone else is locking it" do
|
31
|
-
@zk.locker(@path_to_lock).lock.
|
32
|
-
@zk2.locker(@path_to_lock).lock.
|
31
|
+
expect(@zk.locker(@path_to_lock).lock).to be(true)
|
32
|
+
expect(@zk2.locker(@path_to_lock).lock).to be(false)
|
33
33
|
end
|
34
34
|
|
35
35
|
it "should assert properly if lock is acquired" do
|
36
|
-
@zk.locker(@path_to_lock).assert.
|
36
|
+
expect(@zk.locker(@path_to_lock).assert).to be(false)
|
37
37
|
l = @zk2.locker(@path_to_lock)
|
38
|
-
l.lock.
|
39
|
-
l.assert.
|
38
|
+
expect(l.lock).to be(true)
|
39
|
+
expect(l.assert).to be(true)
|
40
40
|
end
|
41
41
|
|
42
42
|
it "should be able to acquire the lock after the first one releases it" do
|
43
43
|
lock1 = @zk.locker(@path_to_lock)
|
44
44
|
lock2 = @zk2.locker(@path_to_lock)
|
45
|
-
|
46
|
-
lock1.lock.
|
47
|
-
lock2.lock.
|
45
|
+
|
46
|
+
expect(lock1.lock).to be(true)
|
47
|
+
expect(lock2.lock).to be(false)
|
48
48
|
lock1.unlock
|
49
|
-
lock2.lock.
|
49
|
+
expect(lock2.lock).to be(true)
|
50
50
|
end
|
51
51
|
|
52
52
|
it "should be able to acquire the lock if the first locker goes away" do
|
53
53
|
lock1 = @zk.locker(@path_to_lock)
|
54
54
|
lock2 = @zk2.locker(@path_to_lock)
|
55
55
|
|
56
|
-
lock1.lock.
|
57
|
-
lock2.lock.
|
56
|
+
expect(lock1.lock).to be(true)
|
57
|
+
expect(lock2.lock).to be(false)
|
58
58
|
@zk.close!
|
59
|
-
lock2.lock.
|
59
|
+
expect(lock2.lock).to be(true)
|
60
60
|
end
|
61
61
|
|
62
62
|
it "should be able to handle multi part path locks" do
|
63
|
-
@zk.locker("my/multi/part/path").lock.
|
63
|
+
expect(@zk.locker("my/multi/part/path").lock).to be(true)
|
64
64
|
end
|
65
65
|
|
66
66
|
describe :with_lock do
|
@@ -70,23 +70,23 @@ describe 'ZK::Client#locker' do
|
|
70
70
|
describe 'Client::Conveniences' do
|
71
71
|
it %[should yield the lock instance to the block] do
|
72
72
|
@zk.with_lock(@path_to_lock) do |lock|
|
73
|
-
lock.
|
74
|
-
lock.
|
75
|
-
|
73
|
+
expect(lock).not_to be_nil
|
74
|
+
expect(lock).to be_kind_of(ZK::Locker::LockerBase)
|
75
|
+
expect { lock.assert! }.not_to raise_error
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
79
|
it %[should yield a shared lock when :mode => shared given] do
|
80
80
|
@zk.with_lock(@path_to_lock, :mode => :shared) do |lock|
|
81
|
-
lock.
|
82
|
-
lock.
|
83
|
-
|
81
|
+
expect(lock).not_to be_nil
|
82
|
+
expect(lock).to be_kind_of(ZK::Locker::SharedLocker)
|
83
|
+
expect { lock.assert! }.not_to raise_error
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
87
|
it %[should take a timeout] do
|
88
88
|
first_lock = @zk.locker(@path_to_lock)
|
89
|
-
first_lock.lock.
|
89
|
+
expect(first_lock.lock).to be(true)
|
90
90
|
|
91
91
|
thread = Thread.new do
|
92
92
|
begin
|
@@ -98,8 +98,8 @@ describe 'ZK::Client#locker' do
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
-
thread.join(2).
|
102
|
-
@exc.
|
101
|
+
expect(thread.join(2)).to eq(thread)
|
102
|
+
expect(@exc).to be_kind_of(ZK::Exceptions::LockWaitTimeoutError)
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
@@ -107,26 +107,26 @@ describe 'ZK::Client#locker' do
|
|
107
107
|
it "should blocking lock" do
|
108
108
|
array = []
|
109
109
|
first_lock = @zk.locker("mylock")
|
110
|
-
first_lock.lock.
|
110
|
+
expect(first_lock.lock).to be(true)
|
111
111
|
array << :first_lock
|
112
112
|
|
113
113
|
thread = Thread.new do
|
114
114
|
@zk.locker("mylock").with_lock do
|
115
115
|
array << :second_lock
|
116
116
|
end
|
117
|
-
array.length.
|
117
|
+
expect(array.length).to eq(2)
|
118
118
|
end
|
119
119
|
|
120
|
-
array.length.
|
120
|
+
expect(array.length).to eq(1)
|
121
121
|
first_lock.unlock
|
122
122
|
thread.join(10)
|
123
|
-
array.length.
|
123
|
+
expect(array.length).to eq(2)
|
124
124
|
end
|
125
125
|
|
126
126
|
it %[should accept a :wait option] do
|
127
127
|
array = []
|
128
128
|
first_lock = @zk.locker("mylock")
|
129
|
-
first_lock.lock.
|
129
|
+
expect(first_lock.lock).to be(true)
|
130
130
|
|
131
131
|
second_lock = @zk.locker("mylock")
|
132
132
|
|
@@ -140,10 +140,32 @@ describe 'ZK::Client#locker' do
|
|
140
140
|
end
|
141
141
|
end
|
142
142
|
|
143
|
-
array.
|
144
|
-
thread.join(2).
|
145
|
-
@exc.
|
146
|
-
@exc.
|
143
|
+
expect(array).to be_empty
|
144
|
+
expect(thread.join(2)).to eq(thread)
|
145
|
+
expect(@exc).not_to be_nil
|
146
|
+
expect(@exc).to be_kind_of(ZK::Exceptions::LockWaitTimeoutError)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should interrupt a blocked lock" do
|
150
|
+
first_lock = @zk.locker("mylock")
|
151
|
+
expect(first_lock.lock).to be(true)
|
152
|
+
|
153
|
+
second_lock = @zk.locker("mylock")
|
154
|
+
thread = Thread.new do
|
155
|
+
begin
|
156
|
+
second_lock.with_lock do
|
157
|
+
raise "NO NO NO!! should not have called the block!!"
|
158
|
+
end
|
159
|
+
rescue Exception => e
|
160
|
+
@exc = e
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
Thread.pass until second_lock.waiting?
|
165
|
+
|
166
|
+
second_lock.interrupt!
|
167
|
+
thread.join(2)
|
168
|
+
expect(@exc).to be_kind_of(ZK::Exceptions::WakeUpException)
|
147
169
|
end
|
148
170
|
end
|
149
171
|
end # with_lock
|
@@ -10,25 +10,32 @@ shared_examples_for 'ZK::Locker::Semaphore' do
|
|
10
10
|
it_should_behave_like 'LockerBase#assert!'
|
11
11
|
end
|
12
12
|
|
13
|
+
context %[invalid semaphore_size] do
|
14
|
+
let(:semaphore_size) { :boom }
|
15
|
+
it 'should raise' do
|
16
|
+
expect{ locker }.to raise_error(ZK::Exceptions::BadArguments)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
13
20
|
describe :acquirable? do
|
14
21
|
describe %[with default options] do
|
15
22
|
it %[should work if the lock root doesn't exist] do
|
16
23
|
zk.rm_rf(ZK::Locker::Semaphore.default_root_node)
|
17
|
-
locker.
|
24
|
+
expect(locker).to be_acquirable
|
18
25
|
end
|
19
26
|
|
20
27
|
it %[should check local state of lockedness] do
|
21
|
-
locker.lock.
|
22
|
-
locker.
|
28
|
+
expect(locker.lock).to be(true)
|
29
|
+
expect(locker).to be_acquirable
|
23
30
|
end
|
24
31
|
|
25
32
|
it %[should check if any participants would prevent us from acquiring the lock] do
|
26
|
-
locker3.lock.
|
27
|
-
locker.
|
28
|
-
locker2.lock.
|
29
|
-
locker.
|
33
|
+
expect(locker3.lock).to be(true)
|
34
|
+
expect(locker).to be_acquirable # total locks given less than semaphore_size
|
35
|
+
expect(locker2.lock).to be(true)
|
36
|
+
expect(locker).not_to be_acquirable # total locks given equal to semaphore size
|
30
37
|
locker3.unlock
|
31
|
-
locker.
|
38
|
+
expect(locker).to be_acquirable # total locks given less than semaphore_size
|
32
39
|
end
|
33
40
|
end
|
34
41
|
end
|
@@ -41,13 +48,13 @@ shared_examples_for 'ZK::Locker::Semaphore' do
|
|
41
48
|
end
|
42
49
|
|
43
50
|
it %[should acquire the first lock] do
|
44
|
-
@rval.
|
45
|
-
locker.
|
51
|
+
expect(@rval).to be(true)
|
52
|
+
expect(locker).to be_locked
|
46
53
|
end
|
47
54
|
|
48
55
|
it %[should acquire the second lock] do
|
49
|
-
@rval2.
|
50
|
-
locker2.
|
56
|
+
expect(@rval2).to be(true)
|
57
|
+
expect(locker2).to be_locked
|
51
58
|
end
|
52
59
|
end
|
53
60
|
|
@@ -61,15 +68,15 @@ shared_examples_for 'ZK::Locker::Semaphore' do
|
|
61
68
|
end
|
62
69
|
|
63
70
|
it %[should return false] do
|
64
|
-
@rval.
|
71
|
+
expect(@rval).to be(false)
|
65
72
|
end
|
66
73
|
|
67
74
|
it %[should not be locked] do
|
68
|
-
locker.
|
75
|
+
expect(locker).not_to be_locked
|
69
76
|
end
|
70
77
|
|
71
78
|
it %[should not have a lock_path] do
|
72
|
-
locker.lock_path.
|
79
|
+
expect(locker.lock_path).to be_nil
|
73
80
|
end
|
74
81
|
end
|
75
82
|
|
@@ -87,7 +94,7 @@ shared_examples_for 'ZK::Locker::Semaphore' do
|
|
87
94
|
it %[should acquire the lock after the write lock is released] do
|
88
95
|
ary = []
|
89
96
|
|
90
|
-
locker.lock.
|
97
|
+
expect(locker.lock).to be(false)
|
91
98
|
|
92
99
|
th = Thread.new do
|
93
100
|
locker.lock(:wait => true)
|
@@ -95,18 +102,18 @@ shared_examples_for 'ZK::Locker::Semaphore' do
|
|
95
102
|
end
|
96
103
|
|
97
104
|
locker.wait_until_blocked(5)
|
98
|
-
locker.
|
99
|
-
locker.
|
100
|
-
ary.
|
105
|
+
expect(locker).to be_waiting
|
106
|
+
expect(locker).not_to be_locked
|
107
|
+
expect(ary).to be_empty
|
101
108
|
|
102
109
|
zk.delete(@existing_locks.shuffle.first)
|
103
110
|
|
104
|
-
th.join(2).
|
111
|
+
expect(th.join(2)).to eq(th)
|
105
112
|
|
106
|
-
ary.
|
107
|
-
ary.length.
|
113
|
+
expect(ary).not_to be_empty
|
114
|
+
expect(ary.length).to eq(1)
|
108
115
|
|
109
|
-
locker.
|
116
|
+
expect(locker).to be_locked
|
110
117
|
end
|
111
118
|
end
|
112
119
|
|
@@ -116,9 +123,9 @@ shared_examples_for 'ZK::Locker::Semaphore' do
|
|
116
123
|
|
117
124
|
write_lock_dir = File.dirname(@existing_locks.first)
|
118
125
|
|
119
|
-
zk.children(write_lock_dir).length.
|
126
|
+
expect(zk.children(write_lock_dir).length).to eq(semaphore_size)
|
120
127
|
|
121
|
-
locker.lock.
|
128
|
+
expect(locker.lock).to be(false)
|
122
129
|
|
123
130
|
th = Thread.new do
|
124
131
|
begin
|
@@ -130,16 +137,16 @@ shared_examples_for 'ZK::Locker::Semaphore' do
|
|
130
137
|
end
|
131
138
|
|
132
139
|
locker.wait_until_blocked(5)
|
133
|
-
locker.
|
134
|
-
locker.
|
135
|
-
ary.
|
140
|
+
expect(locker).to be_waiting
|
141
|
+
expect(locker).not_to be_locked
|
142
|
+
expect(ary).to be_empty
|
136
143
|
|
137
|
-
th.join(2).
|
144
|
+
expect(th.join(2)).to eq(th)
|
138
145
|
|
139
|
-
zk.children(write_lock_dir).length.
|
146
|
+
expect(zk.children(write_lock_dir).length).to eq(semaphore_size)
|
140
147
|
|
141
|
-
ary.
|
142
|
-
@exc.
|
148
|
+
expect(ary).to be_empty
|
149
|
+
expect(@exc).to be_kind_of(ZK::Exceptions::LockWaitTimeoutError)
|
143
150
|
end
|
144
151
|
|
145
152
|
end
|
@@ -8,12 +8,12 @@ shared_examples_for :shared_exclusive_integration do
|
|
8
8
|
|
9
9
|
describe 'shared lock acquired first' do
|
10
10
|
it %[should block exclusive locks from acquiring until released] do
|
11
|
-
@sh_lock.lock.
|
12
|
-
@ex_lock.lock.
|
11
|
+
expect(@sh_lock.lock).to be(true)
|
12
|
+
expect(@ex_lock.lock).to be(false)
|
13
13
|
|
14
14
|
mutex = Monitor.new
|
15
15
|
cond = mutex.new_cond
|
16
|
-
|
16
|
+
|
17
17
|
th = Thread.new do
|
18
18
|
logger.debug { "@ex_lock trying to acquire acquire lock" }
|
19
19
|
@ex_lock.with_lock do
|
@@ -28,29 +28,29 @@ shared_examples_for :shared_exclusive_integration do
|
|
28
28
|
|
29
29
|
mutex.synchronize do
|
30
30
|
logger.debug { "unlocking the shared lock" }
|
31
|
-
@sh_lock.unlock.
|
31
|
+
expect(@sh_lock.unlock).to be(true)
|
32
32
|
cond.wait_until { th[:got_lock] } # make sure they actually received the lock (avoid race)
|
33
|
-
th[:got_lock].
|
33
|
+
expect(th[:got_lock]).to be(true)
|
34
34
|
logger.debug { "ok, they got the lock" }
|
35
35
|
end
|
36
36
|
|
37
|
-
th.join(5).
|
37
|
+
expect(th.join(5)).to eq(th)
|
38
38
|
|
39
39
|
logger.debug { "thread joined, exclusive lock should be releasd" }
|
40
40
|
|
41
|
-
@ex_lock.
|
41
|
+
expect(@ex_lock).not_to be_locked
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
45
|
describe 'multiple shared locks acquired first' do
|
46
46
|
before do
|
47
|
-
zk3.
|
48
|
-
@sh_lock2 = ZK::Locker.shared_locker(zk3, path)
|
47
|
+
expect(zk3).not_to be_nil
|
48
|
+
@sh_lock2 = ZK::Locker.shared_locker(zk3, path)
|
49
49
|
end
|
50
50
|
it %[should not aquire a lock when highest-numbered released but others remain] do
|
51
|
-
@sh_lock.lock.
|
52
|
-
@sh_lock2.lock.
|
53
|
-
@ex_lock.lock.
|
51
|
+
expect(@sh_lock.lock).to be(true)
|
52
|
+
expect(@sh_lock2.lock).to be(true)
|
53
|
+
expect(@ex_lock.lock).to be(false)
|
54
54
|
|
55
55
|
mutex = Monitor.new
|
56
56
|
cond = mutex.new_cond
|
@@ -75,28 +75,28 @@ shared_examples_for :shared_exclusive_integration do
|
|
75
75
|
mutex.synchronize do
|
76
76
|
@ex_lock.wait_until_blocked(1)
|
77
77
|
logger.debug { "unlocking the highest shared lock" }
|
78
|
-
@sh_lock2.unlock.
|
78
|
+
expect(@sh_lock2.unlock).to be(true)
|
79
79
|
cond.wait_until { (!th[:got_lock].nil?) } # make sure they actually received the lock (avoid race)
|
80
|
-
th[:got_lock].
|
80
|
+
expect(th[:got_lock]).to be(false)
|
81
81
|
logger.debug { "they didn't get the lock." }
|
82
82
|
end
|
83
83
|
|
84
|
-
th.join(5).
|
84
|
+
expect(th.join(5)).to eq(th)
|
85
85
|
|
86
86
|
logger.debug { "thread joined, exclusive lock should be releasd" }
|
87
|
-
@sh_lock.unlock.
|
88
|
-
@ex_lock.
|
87
|
+
expect(@sh_lock.unlock).to be(true)
|
88
|
+
expect(@ex_lock).not_to be_locked
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
92
|
describe 'exclusive lock acquired first' do
|
93
93
|
it %[should block shared lock from acquiring until released] do
|
94
|
-
@ex_lock.lock.
|
95
|
-
@sh_lock.lock.
|
94
|
+
expect(@ex_lock.lock).to be(true)
|
95
|
+
expect(@sh_lock.lock).to be(false)
|
96
96
|
|
97
97
|
mutex = Monitor.new
|
98
98
|
cond = mutex.new_cond
|
99
|
-
|
99
|
+
|
100
100
|
th = Thread.new do
|
101
101
|
logger.debug { "@ex_lock trying to acquire acquire lock" }
|
102
102
|
@sh_lock.with_lock do
|
@@ -111,31 +111,31 @@ shared_examples_for :shared_exclusive_integration do
|
|
111
111
|
|
112
112
|
mutex.synchronize do
|
113
113
|
logger.debug { "unlocking the shared lock" }
|
114
|
-
@ex_lock.unlock.
|
114
|
+
expect(@ex_lock.unlock).to be(true)
|
115
115
|
cond.wait_until { th[:got_lock] } # make sure they actually received the lock (avoid race)
|
116
|
-
th[:got_lock].
|
116
|
+
expect(th[:got_lock]).to be(true)
|
117
117
|
logger.debug { "ok, they got the lock" }
|
118
118
|
end
|
119
119
|
|
120
|
-
th.join(5).
|
120
|
+
expect(th.join(5)).to eq(th)
|
121
121
|
|
122
122
|
logger.debug { "thread joined, exclusive lock should be releasd" }
|
123
123
|
|
124
|
-
@sh_lock.
|
124
|
+
expect(@sh_lock).not_to be_locked
|
125
125
|
end
|
126
126
|
end
|
127
127
|
|
128
128
|
describe 'shared-exclusive-shared' do
|
129
129
|
before do
|
130
|
-
zk3.
|
131
|
-
@sh_lock2 = ZK::Locker.shared_locker(zk3, path)
|
130
|
+
expect(zk3).not_to be_nil
|
131
|
+
@sh_lock2 = ZK::Locker.shared_locker(zk3, path)
|
132
132
|
end
|
133
133
|
|
134
134
|
it %[should act something like a queue] do
|
135
135
|
@array = []
|
136
136
|
|
137
|
-
@sh_lock.lock.
|
138
|
-
@sh_lock.
|
137
|
+
expect(@sh_lock.lock).to be(true)
|
138
|
+
expect(@sh_lock).to be_locked
|
139
139
|
|
140
140
|
ex_th = Thread.new do
|
141
141
|
begin
|
@@ -150,15 +150,15 @@ shared_examples_for :shared_exclusive_integration do
|
|
150
150
|
logger.debug { "about to wait for @ex_lock to be blocked" }
|
151
151
|
|
152
152
|
@ex_lock.wait_until_blocked(5)
|
153
|
-
@ex_lock.
|
153
|
+
expect(@ex_lock).to be_waiting
|
154
154
|
|
155
155
|
logger.debug { "@ex_lock is waiting" }
|
156
156
|
|
157
|
-
@ex_lock.
|
157
|
+
expect(@ex_lock).not_to be_locked
|
158
158
|
|
159
159
|
# this is the important one, does the second shared lock get blocked by
|
160
160
|
# the exclusive lock
|
161
|
-
@sh_lock2.lock.
|
161
|
+
expect(@sh_lock2.lock).not_to be(true)
|
162
162
|
|
163
163
|
sh2_th = Thread.new do
|
164
164
|
begin
|
@@ -173,19 +173,19 @@ shared_examples_for :shared_exclusive_integration do
|
|
173
173
|
logger.debug { "about to wait for @sh_lock2 to be blocked" }
|
174
174
|
|
175
175
|
@sh_lock2.wait_until_blocked(5)
|
176
|
-
@sh_lock2.
|
176
|
+
expect(@sh_lock2).to be_waiting
|
177
177
|
|
178
178
|
logger.debug { "@sh_lock2 is waiting" }
|
179
179
|
|
180
180
|
# ok, now unlock the first in the chain
|
181
181
|
@sh_lock.assert!
|
182
|
-
@sh_lock.unlock.
|
182
|
+
expect(@sh_lock.unlock).to be(true)
|
183
183
|
|
184
|
-
ex_th.join(5).
|
185
|
-
sh2_th.join(5).
|
184
|
+
expect(ex_th.join(5)).to eq(ex_th)
|
185
|
+
expect(sh2_th.join(5)).to eq(sh2_th)
|
186
186
|
|
187
|
-
@array.length.
|
188
|
-
@array.
|
187
|
+
expect(@array.length).to eq(2)
|
188
|
+
expect(@array).to eq([:ex_lock, :sh_lock2])
|
189
189
|
end
|
190
190
|
end
|
191
191
|
end # shared_exclusive_integration
|