Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit cadfe0a

Browse files
author
Sean Quah
committed
Simplify release of write lock by delaying cancellation until the lock is acquired
1 parent 65f97fa commit cadfe0a

File tree

2 files changed

+24
-29
lines changed

2 files changed

+24
-29
lines changed

synapse/util/async_helpers.py

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -562,31 +562,22 @@ async def _ctx_manager() -> AsyncIterator[None]:
562562
to_wait_on_defer = defer.gatherResults(to_wait_on)
563563
try:
564564
# Wait for all current readers and the latest writer to finish.
565-
# May raise a `CancelledError` if the `Deferred` wrapping us is
566-
# cancelled. The `Deferred`s we are waiting on must not be cancelled,
567-
# since we do not own them.
568-
await make_deferred_yieldable(stop_cancellation(to_wait_on_defer))
565+
# May raise a `CancelledError` immediately after the wait if the
566+
# `Deferred` wrapping us is cancelled. We must only release the lock
567+
# once we have acquired it, hence the delay.
568+
await make_deferred_yieldable(
569+
delay_cancellation(to_wait_on_defer, all=True)
570+
)
569571
yield
570572
finally:
571-
572-
def release() -> None:
573-
with PreserveLoggingContext():
574-
new_defer.callback(None)
575-
# `self.key_to_current_writer[key]` may be missing if there was another
576-
# writer waiting for us and it completed entirely within the
577-
# `new_defer.callback()` call above.
578-
if self.key_to_current_writer.get(key) == new_defer:
579-
self.key_to_current_writer.pop(key)
580-
581-
if to_wait_on_defer.called:
582-
release()
583-
else:
584-
# We don't have the lock yet, probably because we were cancelled
585-
# while waiting for it. We can't call `release()` yet, since
586-
# `new_defer` must only resolve once all previous readers and
587-
# writers have finished.
588-
# NB: `release()` won't have a logcontext in this path.
589-
to_wait_on_defer.addBoth(lambda _: release())
573+
# Release the lock.
574+
with PreserveLoggingContext():
575+
new_defer.callback(None)
576+
# `self.key_to_current_writer[key]` may be missing if there was another
577+
# writer waiting for us and it completed entirely within the
578+
# `new_defer.callback()` call above.
579+
if self.key_to_current_writer.get(key) == new_defer:
580+
self.key_to_current_writer.pop(key)
590581

591582
return _ctx_manager()
592583

tests/util/test_rwlock.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -353,13 +353,12 @@ def test_cancellation_while_waiting_for_write_lock(self):
353353
writer3_d, _ = self._start_nonblocking_writer(rwlock, key, "write 3 completed")
354354
self.assertFalse(writer3_d.called)
355355

356-
# 5. The second writer is cancelled.
356+
# 5. The second writer is cancelled, but continues waiting for the lock.
357357
# The reader, first writer and third writer should not be cancelled.
358358
# The first writer should still be waiting on the reader.
359-
# The third writer should still be waiting, even though the second writer has
360-
# been cancelled.
359+
# The third writer should still be waiting on the second writer.
361360
writer2_d.cancel()
362-
self.failureResultOf(writer2_d, CancelledError)
361+
self.assertNoResult(writer2_d)
363362
self.assertFalse(reader_d.called, "Reader was unexpectedly cancelled")
364363
self.assertFalse(writer1_d.called, "First writer was unexpectedly cancelled")
365364
self.assertFalse(
@@ -370,9 +369,10 @@ def test_cancellation_while_waiting_for_write_lock(self):
370369

371370
# 6. Unblock the reader, which should complete.
372371
# The first writer should be given the lock and block.
373-
# The third writer should still be waiting.
372+
# The third writer should still be waiting on the second writer.
374373
unblock_reader.callback(None)
375374
self.assertEqual("read completed", self.successResultOf(reader_d))
375+
self.assertNoResult(writer2_d)
376376
self.assertFalse(
377377
writer3_d.called,
378378
"Third writer was unexpectedly given the lock before the first writer "
@@ -383,7 +383,11 @@ def test_cancellation_while_waiting_for_write_lock(self):
383383
unblock_writer1.callback(None)
384384
self.assertEqual("write 1 completed", self.successResultOf(writer1_d))
385385

386-
# 8. The third writer should take the lock and complete.
386+
# 8. The second writer should take the lock and release it immediately, since it
387+
# has been cancelled.
388+
self.failureResultOf(writer2_d, CancelledError)
389+
390+
# 9. The third writer should take the lock and complete.
387391
self.assertTrue(
388392
writer3_d.called, "Third writer is stuck waiting for a cancelled writer"
389393
)

0 commit comments

Comments
 (0)