Skip to content

Commit f2c77de

Browse files
committed
Refactored bitwise operations in ReadWriteLock to methods with meaningful names.
1 parent c79a53b commit f2c77de

File tree

2 files changed

+179
-13
lines changed

2 files changed

+179
-13
lines changed

lib/concurrent/atomic/read_write_lock.rb

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,23 +108,23 @@ def with_write_lock
108108
def acquire_read_lock
109109
while(true)
110110
c = @counter.value
111-
raise ResourceLimitError.new('Too many reader threads') if (c & MAX_READERS) == MAX_READERS
111+
raise ResourceLimitError.new('Too many reader threads') if max_readers?(c)
112112

113113
# If a writer is waiting when we first queue up, we need to wait
114-
if c >= WAITING_WRITER
114+
if waiting_writer?(c)
115115
# But it is possible that the writer could finish and decrement @counter right here...
116116
@reader_mutex.synchronize do
117117
# So check again inside the synchronized section
118-
@reader_q.wait(@reader_mutex) if @counter.value >= WAITING_WRITER
118+
@reader_q.wait(@reader_mutex) if waiting_writer?
119119
end
120120

121121
# after a reader has waited once, they are allowed to "barge" ahead of waiting writers
122122
# but if a writer is *running*, the reader still needs to wait (naturally)
123123
while(true)
124124
c = @counter.value
125-
if c >= RUNNING_WRITER
125+
if running_writer?(c)
126126
@reader_mutex.synchronize do
127-
@reader_q.wait(@reader_mutex) if @counter.value >= RUNNING_WRITER
127+
@reader_q.wait(@reader_mutex) if running_writer?
128128
end
129129
else
130130
return if @counter.compare_and_swap(c,c+1)
@@ -145,7 +145,7 @@ def release_read_lock
145145
c = @counter.value
146146
if @counter.compare_and_swap(c,c-1)
147147
# If one or more writers were waiting, and we were the last reader, wake a writer up
148-
if c >= WAITING_WRITER && (c & MAX_READERS) == 1
148+
if waiting_writer?(c) && running_readers(c) == 1
149149
@writer_mutex.synchronize { @writer_q.signal }
150150
end
151151
break
@@ -163,7 +163,7 @@ def release_read_lock
163163
def acquire_write_lock
164164
while(true)
165165
c = @counter.value
166-
raise ResourceLimitError.new('Too many writer threads') if (c & MAX_WRITERS) == MAX_WRITERS
166+
raise ResourceLimitError.new('Too many writer threads') if max_writers?(c)
167167

168168
if c == 0 # no readers OR writers running
169169
# if we successfully swap the RUNNING_WRITER bit on, then we can go ahead
@@ -177,7 +177,7 @@ def acquire_write_lock
177177
# So we have to do another check inside the synchronized section
178178
# If a writer OR reader is running, then go to sleep
179179
c = @counter.value
180-
@writer_q.wait(@writer_mutex) if (c >= RUNNING_WRITER) || ((c & MAX_READERS) > 0)
180+
@writer_q.wait(@writer_mutex) if running_writer?(c) || running_readers?(c)
181181
end
182182

183183
# We just came out of a wait
@@ -203,7 +203,7 @@ def release_write_lock
203203
c = @counter.value
204204
if @counter.compare_and_swap(c,c-RUNNING_WRITER)
205205
@reader_mutex.synchronize { @reader_q.broadcast }
206-
if (c & MAX_WRITERS) > 0 # if any writers are waiting...
206+
if waiting_writers(c) > 0 # if any writers are waiting...
207207
@writer_mutex.synchronize { @writer_q.signal }
208208
end
209209
break
@@ -216,15 +216,66 @@ def release_write_lock
216216
# writer counts.
217217
def to_s
218218
c = @counter.value
219-
s = if c >= RUNNING_WRITER
219+
s = if running_writer?(c)
220220
"1 writer running, "
221-
elsif (c & MAX_READERS) > 0
222-
"#{c & MAX_READERS} readers running, "
221+
elsif running_readers(c) > 0
222+
"#{running_readers(c)} readers running, "
223223
else
224224
""
225225
end
226226

227-
"#<ReadWriteLock:#{object_id.to_s(16)} #{s}#{(c & MAX_WRITERS) / WAITING_WRITER} writers waiting>"
227+
"#<ReadWriteLock:#{object_id.to_s(16)} #{s}#{waiting_writers(c)} writers waiting>"
228+
end
229+
230+
# Queries if the write lock is held by any thread.
231+
#
232+
# @return [Boolean] true if the write lock is held else false`
233+
def write_locked?
234+
@counter.value >= RUNNING_WRITER
235+
end
236+
237+
# Queries whether any threads are waiting to acquire the read or write lock.
238+
#
239+
# @return [Boolean] true if any threads are waiting for a lock else false
240+
def has_waiters?
241+
waiting_writer?(@counter.value)
242+
end
243+
244+
private
245+
246+
# @!visibility private
247+
def running_readers(c)
248+
c & MAX_READERS
249+
end
250+
251+
# @!visibility private
252+
def running_readers?(c)
253+
(c & MAX_READERS) > 0
254+
end
255+
256+
# @!visibility private
257+
def running_writer?(c = @counter.value)
258+
c >= RUNNING_WRITER
259+
end
260+
261+
# @!visibility private
262+
def waiting_writers(c)
263+
(c & MAX_WRITERS) / WAITING_WRITER
264+
end
265+
266+
# @!visibility private
267+
def waiting_writer?(c = @counter.value)
268+
c >= WAITING_WRITER
269+
end
270+
271+
# @!visibility private
272+
def max_readers?(c)
273+
(c & MAX_READERS) == MAX_READERS
274+
end
275+
276+
# @!visibility private
277+
def max_writers?(c)
278+
(c & MAX_WRITERS) == MAX_WRITERS
228279
end
229280
end
230281
end

spec/concurrent/atomic/read_write_lock_spec.rb

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,121 @@ module Concurrent
22

33
describe ReadWriteLock do
44

5+
context '#write_locked?' do
6+
7+
it 'returns true when the write lock is held' do
8+
latch_1 = Concurrent::CountDownLatch.new(1)
9+
latch_2 = Concurrent::CountDownLatch.new(1)
10+
11+
thread = Thread.new do
12+
subject.with_write_lock do
13+
latch_1.count_down
14+
latch_2.wait(1)
15+
end
16+
end
17+
18+
latch_1.wait(1)
19+
expect(subject).to be_write_locked
20+
latch_2.count_down
21+
thread.join
22+
end
23+
24+
it 'returns false when the write lock is not held' do
25+
expect(subject).to_not be_write_locked
26+
end
27+
28+
it 'returns false when the write lock is not held but there are readers' do
29+
latch = Concurrent::CountDownLatch.new(1)
30+
31+
thread = Thread.new do
32+
subject.with_read_lock do
33+
latch.wait(1)
34+
end
35+
end
36+
37+
expect(subject).to_not be_write_locked
38+
latch.count_down
39+
thread.join
40+
end
41+
end
42+
43+
context '#has_waiters?' do
44+
45+
it 'returns false when no locks are held' do
46+
expect(subject).to_not have_waiters
47+
end
48+
49+
it 'returns false when there are readers but no writers' do
50+
latch = Concurrent::CountDownLatch.new(1)
51+
52+
thread = Thread.new do
53+
subject.with_read_lock do
54+
latch.wait(1)
55+
end
56+
end
57+
58+
expect(subject).to_not have_waiters
59+
latch.count_down
60+
thread.join
61+
end
62+
63+
it 'returns true when the write lock is held and there are waiting readers' do
64+
latch_1 = Concurrent::CountDownLatch.new(1)
65+
latch_2 = Concurrent::CountDownLatch.new(1)
66+
latch_3 = Concurrent::CountDownLatch.new(1)
67+
68+
thread_1 = Thread.new do
69+
latch_1.wait(1)
70+
subject.acquire_write_lock
71+
latch_2.count_down
72+
latch_3.wait(1)
73+
subject.release_write_lock
74+
end
75+
76+
thread_2 = Thread.new do
77+
latch_2.wait(1)
78+
subject.acquire_read_lock
79+
subject.release_read_lock
80+
end
81+
82+
latch_1.count_down
83+
latch_2.wait(1)
84+
85+
expect(subject).to have_waiters
86+
87+
latch_3.count_down
88+
[thread_1, thread_2].each(&:join)
89+
end
90+
91+
it 'returns true when the write lock is held and there are waiting writers' do
92+
latch_1 = Concurrent::CountDownLatch.new(1)
93+
latch_2 = Concurrent::CountDownLatch.new(1)
94+
latch_3 = Concurrent::CountDownLatch.new(1)
95+
96+
thread_1 = Thread.new do
97+
latch_1.wait(1)
98+
subject.acquire_write_lock
99+
latch_2.count_down
100+
latch_3.wait(1)
101+
subject.release_write_lock
102+
end
103+
104+
thread_2 = Thread.new do
105+
latch_2.wait(1)
106+
subject.acquire_write_lock
107+
subject.release_write_lock
108+
end
109+
110+
latch_1.count_down
111+
latch_2.wait(1)
112+
113+
expect(subject).to have_waiters
114+
115+
latch_3.count_down
116+
[thread_1, thread_2].each(&:join)
117+
end
118+
end
119+
5120
context '#with_read_lock' do
6121

7122
it 'acquires the lock' do

0 commit comments

Comments
 (0)