Fix rare error in `ReadWriteLock` when writers complete immediately (#12105)
Signed-off-by: Sean Quah <seanq@element.io>babolivier/sign_json_module
parent
300ed0b8a6
commit
4d6b6c17c8
|
@ -0,0 +1 @@
|
|||
Fix an extremely rare, long-standing bug in `ReadWriteLock` that would cause an error when a newly unblocked writer completes instantly.
|
|
@ -555,7 +555,10 @@ class ReadWriteLock:
|
|||
finally:
|
||||
with PreserveLoggingContext():
|
||||
new_defer.callback(None)
|
||||
if self.key_to_current_writer[key] == new_defer:
|
||||
# `self.key_to_current_writer[key]` may be missing if there was another
|
||||
# writer waiting for us and it completed entirely within the
|
||||
# `new_defer.callback()` call above.
|
||||
if self.key_to_current_writer.get(key) == new_defer:
|
||||
self.key_to_current_writer.pop(key)
|
||||
|
||||
return _ctx_manager()
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# limitations under the License.
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.defer import Deferred
|
||||
|
||||
from synapse.util.async_helpers import ReadWriteLock
|
||||
|
||||
|
@ -83,3 +84,32 @@ class ReadWriteLockTestCase(unittest.TestCase):
|
|||
self.assertTrue(d.called)
|
||||
with d.result:
|
||||
pass
|
||||
|
||||
def test_lock_handoff_to_nonblocking_writer(self):
|
||||
"""Test a writer handing the lock to another writer that completes instantly."""
|
||||
rwlock = ReadWriteLock()
|
||||
key = "key"
|
||||
|
||||
unblock: "Deferred[None]" = Deferred()
|
||||
|
||||
async def blocking_write():
|
||||
with await rwlock.write(key):
|
||||
await unblock
|
||||
|
||||
async def nonblocking_write():
|
||||
with await rwlock.write(key):
|
||||
pass
|
||||
|
||||
d1 = defer.ensureDeferred(blocking_write())
|
||||
d2 = defer.ensureDeferred(nonblocking_write())
|
||||
self.assertFalse(d1.called)
|
||||
self.assertFalse(d2.called)
|
||||
|
||||
# Unblock the first writer. The second writer will complete without blocking.
|
||||
unblock.callback(None)
|
||||
self.assertTrue(d1.called)
|
||||
self.assertTrue(d2.called)
|
||||
|
||||
# The `ReadWriteLock` should operate as normal.
|
||||
d3 = defer.ensureDeferred(nonblocking_write())
|
||||
self.assertTrue(d3.called)
|
||||
|
|
Loading…
Reference in New Issue