Fix deadlock on waiting for pending_writers to be empty
Deadlock can occur when a reader X waits for pending writers to be empty but the reader X already owns a read lock (since it will then wait for pending writers to be empty which it will never be, since pending writers is only empty when there are no readers - but since the current thread X is getting another read lock it result in deadlock for the reader and deadlock for the writer). Closes-Bug: #1276868 Change-Id: Ibe13eafa4bd0bc817e8bfe3cf303c54298778b37
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from concurrent import futures
|
from concurrent import futures
|
||||||
@@ -89,6 +90,57 @@ class ReadWriteLockTest(test.TestCase):
|
|||||||
self.assertRaises(RuntimeError, blow_up)
|
self.assertRaises(RuntimeError, blow_up)
|
||||||
self.assertFalse(lock.owner)
|
self.assertFalse(lock.owner)
|
||||||
|
|
||||||
|
def test_double_reader_abort(self):
|
||||||
|
lock = lock_utils.ReaderWriterLock()
|
||||||
|
activated = collections.deque()
|
||||||
|
|
||||||
|
def double_bad_reader():
|
||||||
|
with lock.read_lock():
|
||||||
|
with lock.read_lock():
|
||||||
|
raise RuntimeError("Broken")
|
||||||
|
|
||||||
|
def happy_writer():
|
||||||
|
with lock.write_lock():
|
||||||
|
activated.append(lock.owner)
|
||||||
|
|
||||||
|
with futures.ThreadPoolExecutor(max_workers=20) as e:
|
||||||
|
for i in range(0, 20):
|
||||||
|
if i % 2 == 0:
|
||||||
|
e.submit(double_bad_reader)
|
||||||
|
else:
|
||||||
|
e.submit(happy_writer)
|
||||||
|
|
||||||
|
self.assertEqual(10, len([a for a in activated if a == 'w']))
|
||||||
|
|
||||||
|
def test_double_reader_writer(self):
|
||||||
|
lock = lock_utils.ReaderWriterLock()
|
||||||
|
activated = collections.deque()
|
||||||
|
active = threading.Event()
|
||||||
|
|
||||||
|
def double_reader():
|
||||||
|
with lock.read_lock():
|
||||||
|
active.set()
|
||||||
|
while lock.pending_writers == 0:
|
||||||
|
time.sleep(0.001)
|
||||||
|
with lock.read_lock():
|
||||||
|
activated.append(lock.owner)
|
||||||
|
|
||||||
|
def happy_writer():
|
||||||
|
with lock.write_lock():
|
||||||
|
activated.append(lock.owner)
|
||||||
|
|
||||||
|
reader = threading.Thread(target=double_reader)
|
||||||
|
reader.start()
|
||||||
|
active.wait()
|
||||||
|
|
||||||
|
writer = threading.Thread(target=happy_writer)
|
||||||
|
writer.start()
|
||||||
|
|
||||||
|
reader.join()
|
||||||
|
writer.join()
|
||||||
|
self.assertEqual(2, len(activated))
|
||||||
|
self.assertEqual(['r', 'w'], list(activated))
|
||||||
|
|
||||||
def test_reader_chaotic(self):
|
def test_reader_chaotic(self):
|
||||||
lock = lock_utils.ReaderWriterLock()
|
lock = lock_utils.ReaderWriterLock()
|
||||||
activated = collections.deque()
|
activated = collections.deque()
|
||||||
|
|||||||
@@ -86,6 +86,14 @@ class ReaderWriterLock(object):
|
|||||||
self._readers = collections.deque()
|
self._readers = collections.deque()
|
||||||
self._cond = threading.Condition()
|
self._cond = threading.Condition()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pending_writers(self):
|
||||||
|
self._cond.acquire()
|
||||||
|
try:
|
||||||
|
return len(self._pending_writers)
|
||||||
|
finally:
|
||||||
|
self._cond.release()
|
||||||
|
|
||||||
def is_writer(self, check_pending=True):
|
def is_writer(self, check_pending=True):
|
||||||
"""Returns if the caller is the active writer or a pending writer."""
|
"""Returns if the caller is the active writer or a pending writer."""
|
||||||
self._cond.acquire()
|
self._cond.acquire()
|
||||||
@@ -138,11 +146,11 @@ class ReaderWriterLock(object):
|
|||||||
self._cond.acquire()
|
self._cond.acquire()
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
# No active or pending writers; we are good to become a reader.
|
# No active writer; we are good to become a reader.
|
||||||
if self._writer is None and len(self._pending_writers) == 0:
|
if self._writer is None:
|
||||||
self._readers.append(me)
|
self._readers.append(me)
|
||||||
break
|
break
|
||||||
# Some writers; guess we have to wait.
|
# An active writer; guess we have to wait.
|
||||||
self._cond.wait()
|
self._cond.wait()
|
||||||
finally:
|
finally:
|
||||||
self._cond.release()
|
self._cond.release()
|
||||||
|
|||||||
Reference in New Issue
Block a user