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

Commit a2db0ff

Browse files
erikjohnstonhughns
authored andcommitted
Fix rare bug that broke looping calls (#16210)
* Fix rare bug that broke looping calls We can't interact with the reactor from the main thread via looping call. Introduced in v1.90.0 / #15791. * Newsfile
1 parent 0021f74 commit a2db0ff

File tree

3 files changed

+25
-14
lines changed

3 files changed

+25
-14
lines changed

changelog.d/16210.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix rare bug that broke looping calls, which could lead to e.g. linearly increasing memory usage. Introduced in v1.90.0.

synapse/storage/databases/main/lock.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from typing import TYPE_CHECKING, Collection, Optional, Set, Tuple, Type
1818
from weakref import WeakValueDictionary
1919

20-
from twisted.internet.interfaces import IReactorCore
20+
from twisted.internet.task import LoopingCall
2121

2222
from synapse.metrics.background_process_metrics import wrap_as_background_process
2323
from synapse.storage._base import SQLBaseStore
@@ -26,6 +26,7 @@
2626
LoggingDatabaseConnection,
2727
LoggingTransaction,
2828
)
29+
from synapse.types import ISynapseReactor
2930
from synapse.util import Clock
3031
from synapse.util.stringutils import random_string
3132

@@ -358,7 +359,7 @@ class Lock:
358359

359360
def __init__(
360361
self,
361-
reactor: IReactorCore,
362+
reactor: ISynapseReactor,
362363
clock: Clock,
363364
store: LockStore,
364365
read_write: bool,
@@ -377,19 +378,25 @@ def __init__(
377378

378379
self._table = "worker_read_write_locks" if read_write else "worker_locks"
379380

380-
self._looping_call = clock.looping_call(
381+
# We might be called from a non-main thread, so we defer setting up the
382+
# looping call.
383+
self._looping_call: Optional[LoopingCall] = None
384+
reactor.callFromThread(self._setup_looping_call)
385+
386+
self._dropped = False
387+
388+
def _setup_looping_call(self) -> None:
389+
self._looping_call = self._clock.looping_call(
381390
self._renew,
382391
_RENEWAL_INTERVAL_MS,
383-
store,
384-
clock,
385-
read_write,
386-
lock_name,
387-
lock_key,
388-
token,
392+
self._store,
393+
self._clock,
394+
self._read_write,
395+
self._lock_name,
396+
self._lock_key,
397+
self._token,
389398
)
390399

391-
self._dropped = False
392-
393400
@staticmethod
394401
@wrap_as_background_process("Lock._renew")
395402
async def _renew(
@@ -459,7 +466,7 @@ async def release(self) -> None:
459466
if self._dropped:
460467
return
461468

462-
if self._looping_call.running:
469+
if self._looping_call and self._looping_call.running:
463470
self._looping_call.stop()
464471

465472
await self._store.db_pool.simple_delete(
@@ -486,8 +493,9 @@ def __del__(self) -> None:
486493
# We should not be dropped without the lock being released (unless
487494
# we're shutting down), but if we are then let's at least stop
488495
# renewing the lock.
489-
if self._looping_call.running:
490-
self._looping_call.stop()
496+
if self._looping_call and self._looping_call.running:
497+
# We might be called from a non-main thread.
498+
self._reactor.callFromThread(self._looping_call.stop)
491499

492500
if self._reactor.running:
493501
logger.error(

tests/storage/databases/main/test_lock.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def test_timeout_lock(self) -> None:
132132

133133
# We simulate the process getting stuck by cancelling the looping call
134134
# that keeps the lock active.
135+
assert lock._looping_call
135136
lock._looping_call.stop()
136137

137138
# Wait for the lock to timeout.
@@ -403,6 +404,7 @@ def test_timeout_lock(self) -> None:
403404

404405
# We simulate the process getting stuck by cancelling the looping call
405406
# that keeps the lock active.
407+
assert lock._looping_call
406408
lock._looping_call.stop()
407409

408410
# Wait for the lock to timeout.

0 commit comments

Comments
 (0)