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

Commit

Permalink
tests/locks: add failing test for try_acquire_lock
Browse files Browse the repository at this point in the history
This commit proves that there is a bug in LockStore.try_acquire_lock,
and provides a test case that must pass.

Signed-off-by: Sumner Evans <[email protected]>
Co-authored-by: Sean Quah <[email protected]>
  • Loading branch information
sumnerevans and Sean Quah committed May 23, 2022
1 parent 67aae05 commit 68f7873
Showing 1 changed file with 54 additions and 0 deletions.
54 changes: 54 additions & 0 deletions tests/storage/databases/main/test_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from twisted.internet import defer, reactor
from twisted.internet.base import ReactorBase
from twisted.internet.defer import Deferred

from synapse.server import HomeServer
from synapse.storage.databases.main.lock import _LOCK_TIMEOUT_MS

Expand All @@ -22,6 +26,56 @@ class LockTestCase(unittest.HomeserverTestCase):
def prepare(self, reactor, clock, hs: HomeServer):
self.store = hs.get_datastores().main

def test_acquire_contention(self):
# Track the number of tasks holding the lock.
# Should be at most 1.
in_lock = 0
max_in_lock = 0

release_lock: "Deferred[None]" = Deferred()

async def task():
nonlocal in_lock
nonlocal max_in_lock

lock = await self.store.try_acquire_lock("name", "key")
if not lock:
return

async with lock:
in_lock += 1
max_in_lock = max(max_in_lock, in_lock)

# Block to allow other tasks to attempt to take the lock.
await release_lock

in_lock -= 1

# Start 3 tasks.
task1 = defer.ensureDeferred(task())
task2 = defer.ensureDeferred(task())
task3 = defer.ensureDeferred(task())

# Give the reactor a kick so that the database transaction returns.
self.pump()

release_lock.callback(None)

# Run the tasks to completion.
# To work around `Linearizer`s using a different reactor to sleep when
# contended (#12841), we call `runUntilCurrent` on
# `twisted.internet.reactor`, which is a different reactor to that used
# by the homeserver.
assert isinstance(reactor, ReactorBase)
self.get_success(task1)
reactor.runUntilCurrent()
self.get_success(task2)
reactor.runUntilCurrent()
self.get_success(task3)

# At most one task should have held the lock at a time.
self.assertEqual(max_in_lock, 1)

def test_simple_lock(self):
"""Test that we can take out a lock and that while we hold it nobody
else can take it out.
Expand Down

0 comments on commit 68f7873

Please sign in to comment.