Skip to content

Commit 7260172

Browse files
committed
Wrote tests for ReadWriteLock. Tests requiring background threads still pending.
1 parent 204cf7b commit 7260172

File tree

2 files changed

+202
-54
lines changed

2 files changed

+202
-54
lines changed

lib/concurrent/atomic/read_write_lock.rb

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,9 @@ def initialize
7272
def with_read_lock
7373
raise ArgumentError.new('no block given') unless block_given?
7474
acquire_read_lock
75-
yield
76-
rescue => ex
77-
raise ex
78-
ensure
79-
if block_given? && ! ex.is_a?(Concurrent::ResourceLimitError)
75+
begin
76+
yield
77+
ensure
8078
release_read_lock
8179
end
8280
end
@@ -93,16 +91,17 @@ def with_read_lock
9391
def with_write_lock
9492
raise ArgumentError.new('no block given') unless block_given?
9593
acquire_write_lock
96-
yield
97-
rescue => ex
98-
raise ex
99-
ensure
100-
if block_given? && ! ex.is_a?(Concurrent::ResourceLimitError)
94+
begin
95+
yield
96+
ensure
10197
release_write_lock
10298
end
10399
end
104100

105-
# Acquire a read lock.
101+
# Acquire a read lock. If a write lock has been acquired will block until
102+
# it is released. Will not block if other read locks have been acquired.
103+
#
104+
# @return [Boolean] true if the lock is successfully acquired
106105
#
107106
# @raise [Concurrent::ResourceLimitError] if the maximum number of readers
108107
# is exceeded.
@@ -139,6 +138,8 @@ def acquire_read_lock
139138
end
140139

141140
# Release a previously acquired read lock.
141+
#
142+
# @return [Boolean] true if the lock is successfully released
142143
def release_read_lock
143144
while(true)
144145
c = @counter.value
@@ -153,7 +154,9 @@ def release_read_lock
153154
true
154155
end
155156

156-
# Acquire a write lock.
157+
# Acquire a write lock. Will block and wait for all active readers and writers.
158+
#
159+
# @return [Boolean] true if the lock is successfully acquired
157160
#
158161
# @raise [Concurrent::ResourceLimitError] if the maximum number of writers
159162
# is exceeded.
@@ -193,6 +196,8 @@ def acquire_write_lock
193196
end
194197

195198
# Release a previously acquired write lock.
199+
#
200+
# @return [Boolean] true if the lock is successfully released
196201
def release_write_lock
197202
while(true)
198203
c = @counter.value

spec/concurrent/atomic/read_write_lock_spec.rb

Lines changed: 185 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,140 @@ module Concurrent
44

55
context '#with_read_lock' do
66

7-
it 'acquires the lock'
8-
9-
it 'returns the value of the block operation'
10-
11-
it 'releases the lock'
12-
13-
it 'raises an exception if no block is given'
14-
15-
it 'raises an exception if maximum lock limit is exceeded'
16-
17-
it 'does not release the lock when an exception is raised'
7+
it 'acquires the lock' do
8+
expect(subject).to receive(:acquire_read_lock).with(no_args)
9+
subject.with_read_lock { nil }
10+
end
11+
12+
it 'returns the value of the block operation' do
13+
expected = 100
14+
actual = subject.with_read_lock { expected }
15+
expect(actual).to eq expected
16+
end
17+
18+
it 'releases the lock' do
19+
expect(subject).to receive(:release_read_lock).with(no_args)
20+
subject.with_read_lock { nil }
21+
end
22+
23+
it 'raises an exception if no block is given' do
24+
expect {
25+
subject.with_read_lock
26+
}.to raise_error(ArgumentError)
27+
end
28+
29+
it 'raises an exception if maximum lock limit is exceeded' do
30+
counter = Concurrent::Atomic.new(ReadWriteLock::MAX_READERS)
31+
allow(Concurrent::Atomic).to receive(:new).with(anything).and_return(counter)
32+
expect {
33+
subject.with_read_lock { nil }
34+
}.to raise_error(Concurrent::ResourceLimitError)
35+
end
36+
37+
it 'does not release the lock when an exception is raised' do
38+
expect(subject).to_not receive(:release_read_lock).with(any_args)
39+
lambda do
40+
subject.with_read_lock { raise StandardError }
41+
end
42+
end
1843
end
1944

2045
context '#with_write_lock' do
2146

22-
it 'acquires the lock'
23-
24-
it 'returns the value of the block operation'
25-
26-
it 'releases the lock'
27-
28-
it 'raises an exception if no block is given'
29-
30-
it 'raises an exception if maximum lock limit is exceeded'
31-
32-
it 'does not release the lock when an exception is raised'
47+
it 'acquires the lock' do
48+
expect(subject).to receive(:acquire_write_lock).with(no_args)
49+
subject.with_write_lock { nil }
50+
end
51+
52+
it 'returns the value of the block operation' do
53+
expected = 100
54+
actual = subject.with_write_lock { expected }
55+
expect(actual).to eq expected
56+
end
57+
58+
it 'releases the lock' do
59+
expect(subject).to receive(:release_write_lock).with(no_args)
60+
subject.with_write_lock { nil }
61+
end
62+
63+
it 'raises an exception if no block is given' do
64+
expect {
65+
subject.with_write_lock
66+
}.to raise_error(ArgumentError)
67+
end
68+
69+
it 'raises an exception if maximum lock limit is exceeded' do
70+
counter = Concurrent::Atomic.new(ReadWriteLock::MAX_WRITERS)
71+
allow(Concurrent::Atomic).to receive(:new).with(anything).and_return(counter)
72+
expect {
73+
subject.with_write_lock { nil }
74+
}.to raise_error(Concurrent::ResourceLimitError)
75+
end
76+
77+
it 'does not release the lock when an exception is raised' do
78+
expect(subject).to_not receive(:release_write_lock).with(any_args)
79+
lambda do
80+
subject.with_write_lock { raise StandardError }
81+
end
82+
end
3383
end
3484

3585
context '#acquire_read_lock' do
3686

37-
it 'increments the lock count'
38-
39-
it 'waits for a running writer to finish'
40-
41-
it 'does not wait for any running readers'
42-
43-
it 'raises an exception if maximum lock limit is exceeded'
87+
it 'increments the lock count' do
88+
counter = Concurrent::Atomic.new(0)
89+
allow(Concurrent::Atomic).to receive(:new).with(anything).and_return(counter)
90+
subject.acquire_read_lock
91+
expect(counter.value).to eq 1
92+
end
93+
94+
it 'waits for a running writer to finish' do
95+
pending('need to figure out the timing')
96+
expect(true).to be false
97+
end
98+
99+
it 'does not wait for any running readers' do
100+
pending('need to figure out the timing')
101+
expect(true).to be false
102+
end
103+
104+
it 'raises an exception if maximum lock limit is exceeded' do
105+
counter = Concurrent::Atomic.new(ReadWriteLock::MAX_WRITERS)
106+
allow(Concurrent::Atomic).to receive(:new).with(anything).and_return(counter)
107+
expect {
108+
subject.acquire_write_lock { nil }
109+
}.to raise_error(Concurrent::ResourceLimitError)
110+
end
111+
112+
it 'returns true if the lock is acquired' do
113+
expect(subject.acquire_read_lock).to be true
114+
end
44115
end
45116

46117
context '#release_read_lock' do
47118

48-
it 'decrements the counter'
49-
50-
it 'unblocks running writers'
119+
it 'decrements the counter' do
120+
counter = Concurrent::Atomic.new(0)
121+
allow(Concurrent::Atomic).to receive(:new).with(anything).and_return(counter)
122+
subject.acquire_read_lock
123+
expect(counter.value).to eq 1
124+
subject.release_read_lock
125+
expect(counter.value).to eq 0
126+
end
127+
128+
it 'unblocks running writers' do
129+
pending('need to figure out the timing')
130+
expect(true).to be false
131+
end
132+
133+
it 'returns true if the lock is released' do
134+
subject.acquire_read_lock
135+
expect(subject.release_read_lock).to be true
136+
end
137+
138+
it 'returns true if the lock was never set' do
139+
expect(subject.release_read_lock).to be true
140+
end
51141
end
52142

53143
context '#acquire_write_lock' do
@@ -59,24 +149,77 @@ module Concurrent
59149
it 'waits for a running reader to finish'
60150

61151
it 'raises an exception if maximum lock limit is exceeded'
152+
153+
it 'returns true if the lock is acquired'
62154
end
63155

64156
context '#release_write_lock' do
65157

66-
it 'decrements the counter'
67-
68-
it 'unblocks running readers'
69-
70-
it 'unblocks running writers'
158+
it 'decrements the counter' do
159+
counter = Concurrent::Atomic.new(0)
160+
allow(Concurrent::Atomic).to receive(:new).with(anything).and_return(counter)
161+
subject.acquire_write_lock
162+
expect(counter.value).to be > 1
163+
subject.release_write_lock
164+
expect(counter.value).to eq 0
165+
end
166+
167+
it 'unblocks running readers' do
168+
pending('need to figure out the timing')
169+
expect(true).to be false
170+
end
171+
172+
it 'unblocks running writers' do
173+
pending('need to figure out the timing')
174+
expect(true).to be false
175+
end
176+
177+
it 'returns true if the lock is released' do
178+
subject.acquire_write_lock
179+
expect(subject.release_write_lock).to be true
180+
end
181+
182+
it 'returns true if the lock was never set' do
183+
expect(subject.release_write_lock).to be true
184+
end
71185
end
72186

73187
context '#to_s' do
74188

75-
it 'includes the running reader count'
76-
77-
it 'includes the running writer count'
78-
79-
it 'includes the waiting writer count'
189+
it 'includes the running reader count' do
190+
subject.with_read_lock do
191+
expect(subject.to_s).to match(/1 readers running/)
192+
end
193+
expect(subject.to_s).to_not match(/readers running/)
194+
end
195+
196+
it 'includes the running writer count' do
197+
subject.with_write_lock do
198+
expect(subject.to_s).to match(/1 writer running/)
199+
end
200+
expect(subject.to_s).to_not match(/writer running/)
201+
end
202+
203+
it 'includes the waiting writer count' do
204+
start_latch = Concurrent::CountDownLatch.new(1)
205+
end_latch = Concurrent::CountDownLatch.new(1)
206+
207+
thread = Thread.new do
208+
start_latch.wait(1)
209+
subject.acquire_write_lock
210+
subject.release_write_lock
211+
end_latch.count_down
212+
end
213+
214+
subject.with_write_lock do
215+
start_latch.count_down
216+
sleep(0.1)
217+
expect(subject.to_s).to match(/1 writers waiting/)
218+
end
219+
expect(subject.to_s).to match(/1 writers waiting/)
220+
end_latch.wait(1)
221+
expect(subject.to_s).to match(/0 writers waiting/)
222+
end
80223
end
81224
end
82225
end

0 commit comments

Comments
 (0)