|
|
@@ -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 |
|
|
|
|
|
|
@@ -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. |
|
|
|