mirror of
https://github.com/element-hq/synapse.git
synced 2025-10-03 00:01:04 -04:00
Compare commits
13 Commits
5f59fefa3b
...
ac223e1fc2
Author | SHA1 | Date | |
---|---|---|---|
|
ac223e1fc2 | ||
|
1c093509ce | ||
|
0615b64bb4 | ||
|
c284d8cb24 | ||
|
5fff5a1893 | ||
|
765817a1ad | ||
|
396de6544a | ||
|
d1c96ee0f2 | ||
|
5adb08f3c9 | ||
|
2aab171042 | ||
|
0aeb95fb07 | ||
|
72020f3f2c | ||
|
ad8dcc2119 |
15
CHANGES.md
15
CHANGES.md
@ -1,3 +1,18 @@
|
||||
# Synapse 1.139.0 (2025-09-30)
|
||||
|
||||
### `/register` requests from old application service implementations may break when using MAS
|
||||
|
||||
If you are using Matrix Authentication Service (MAS), as of this release any
|
||||
Application Services that do not set `inhibit_login=true` when calling `POST
|
||||
/_matrix/client/v3/register` will receive the error
|
||||
`IO.ELEMENT.MSC4190.M_APPSERVICE_LOGIN_UNSUPPORTED` in response. Please see [the
|
||||
upgrade
|
||||
notes](https://element-hq.github.io/synapse/develop/upgrade.html#register-requests-from-old-application-service-implementations-may-break-when-using-mas)
|
||||
for more information.
|
||||
|
||||
No significant changes since 1.139.0rc3.
|
||||
|
||||
|
||||
# Synapse 1.139.0rc3 (2025-09-25)
|
||||
|
||||
## Bugfixes
|
||||
|
1
changelog.d/18828.feature
Normal file
1
changelog.d/18828.feature
Normal file
@ -0,0 +1 @@
|
||||
Cleanly shutdown `SynapseHomeServer` object.
|
1
changelog.d/18988.misc
Normal file
1
changelog.d/18988.misc
Normal file
@ -0,0 +1 @@
|
||||
Remove internal `ReplicationUploadKeysForUserRestServlet` as a follow-up to the work in https://github.com/element-hq/synapse/pull/18581 that moved device changes off the main process.
|
1
changelog.d/18990.misc
Normal file
1
changelog.d/18990.misc
Normal file
@ -0,0 +1 @@
|
||||
Switch task scheduler from raw logcontext manipulation to using the dedicated logcontext utils.
|
1
changelog.d/18992.misc
Normal file
1
changelog.d/18992.misc
Normal file
@ -0,0 +1 @@
|
||||
Remove `MockClock()` in tests.
|
1
changelog.d/18998.doc
Normal file
1
changelog.d/18998.doc
Normal file
@ -0,0 +1 @@
|
||||
Fix documentation for `rc_room_creation` and `rc_reports` to clarify that a `per_user` rate limit is not supported.
|
38
debian/changelog
vendored
38
debian/changelog
vendored
@ -1,4 +1,16 @@
|
||||
matrix-synapse-py3 (1.139.0~rc3+nmu1) UNRELEASED; urgency=medium
|
||||
matrix-synapse-py3 (1.139.0) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.139.0.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 30 Sep 2025 11:58:55 +0100
|
||||
|
||||
matrix-synapse-py3 (1.139.0~rc3) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.139.0rc3.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Thu, 25 Sep 2025 12:13:23 +0100
|
||||
|
||||
matrix-synapse-py3 (1.138.2) stable; urgency=medium
|
||||
|
||||
* The licensing specifier has been updated to add an optional
|
||||
`LicenseRef-Element-Commercial` license. The code was already licensed in
|
||||
@ -6,11 +18,11 @@ matrix-synapse-py3 (1.139.0~rc3+nmu1) UNRELEASED; urgency=medium
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Thu, 25 Sep 2025 12:17:17 +0100
|
||||
|
||||
matrix-synapse-py3 (1.139.0~rc3) stable; urgency=medium
|
||||
matrix-synapse-py3 (1.138.1) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.139.0rc3.
|
||||
* New Synapse release 1.138.1.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Thu, 25 Sep 2025 12:13:23 +0100
|
||||
-- Synapse Packaging team <packages@matrix.org> Wed, 24 Sep 2025 11:32:38 +0100
|
||||
|
||||
matrix-synapse-py3 (1.139.0~rc2) stable; urgency=medium
|
||||
|
||||
@ -24,24 +36,6 @@ matrix-synapse-py3 (1.139.0~rc1) stable; urgency=medium
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 23 Sep 2025 13:24:50 +0100
|
||||
|
||||
matrix-synapse-py3 (1.138.2) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.138.2.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Wed, 24 Sep 2025 12:26:16 +0100
|
||||
|
||||
matrix-synapse-py3 (1.138.1) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.138.1.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Wed, 24 Sep 2025 11:32:38 +0100
|
||||
|
||||
matrix-synapse-py3 (1.138.0) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.138.0.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 09 Sep 2025 11:21:25 +0100
|
||||
|
||||
matrix-synapse-py3 (1.138.0~rc1) stable; urgency=medium
|
||||
|
||||
* New synapse release 1.138.0rc1.
|
||||
|
@ -2006,9 +2006,8 @@ This setting has the following sub-options:
|
||||
Default configuration:
|
||||
```yaml
|
||||
rc_reports:
|
||||
per_user:
|
||||
per_second: 1.0
|
||||
burst_count: 5.0
|
||||
per_second: 1.0
|
||||
burst_count: 5.0
|
||||
```
|
||||
|
||||
Example configuration:
|
||||
@ -2031,9 +2030,8 @@ This setting has the following sub-options:
|
||||
Default configuration:
|
||||
```yaml
|
||||
rc_room_creation:
|
||||
per_user:
|
||||
per_second: 0.016
|
||||
burst_count: 10.0
|
||||
per_second: 0.016
|
||||
burst_count: 10.0
|
||||
```
|
||||
|
||||
Example configuration:
|
||||
|
6
poetry.lock
generated
6
poetry.lock
generated
@ -1589,14 +1589,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "phonenumbers"
|
||||
version = "9.0.14"
|
||||
version = "9.0.15"
|
||||
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "phonenumbers-9.0.14-py2.py3-none-any.whl", hash = "sha256:6bdf5c46dbfefa1d941d122432d1958418d1dfe3f8c8c81d4c8e80f5442ea41f"},
|
||||
{file = "phonenumbers-9.0.14.tar.gz", hash = "sha256:98afb3e86bf9ae02cc7c98ca44fa8827babb72842f90da9884c5d998937572ae"},
|
||||
{file = "phonenumbers-9.0.15-py2.py3-none-any.whl", hash = "sha256:269b73bc05258e8fd57582770b9559307099ea677c8f1dc5272476f661344776"},
|
||||
{file = "phonenumbers-9.0.15.tar.gz", hash = "sha256:345ff7f23768332d866f37732f815cdf1d33c7f0961246562a5c5b78c12c3ff3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -101,7 +101,7 @@ module-name = "synapse.synapse_rust"
|
||||
|
||||
[tool.poetry]
|
||||
name = "matrix-synapse"
|
||||
version = "1.139.0rc3"
|
||||
version = "1.139.0"
|
||||
description = "Homeserver for the Matrix decentralised comms protocol"
|
||||
authors = ["Matrix.org Team and Contributors <packages@matrix.org>"]
|
||||
license = "AGPL-3.0-or-later OR LicenseRef-Element-Commercial"
|
||||
|
@ -2259,9 +2259,8 @@ properties:
|
||||
Setting this to a high value allows users to report content quickly, possibly in
|
||||
duplicate. This can result in higher database usage.
|
||||
default:
|
||||
per_user:
|
||||
per_second: 1.0
|
||||
burst_count: 5.0
|
||||
per_second: 1.0
|
||||
burst_count: 5.0
|
||||
examples:
|
||||
- per_second: 2.0
|
||||
burst_count: 20.0
|
||||
@ -2270,9 +2269,8 @@ properties:
|
||||
description: >-
|
||||
Sets rate limits for how often users are able to create rooms.
|
||||
default:
|
||||
per_user:
|
||||
per_second: 0.016
|
||||
burst_count: 10.0
|
||||
per_second: 0.016
|
||||
burst_count: 10.0
|
||||
examples:
|
||||
- per_second: 1.0
|
||||
burst_count: 5.0
|
||||
|
@ -68,18 +68,42 @@ PROMETHEUS_METRIC_MISSING_FROM_LIST_TO_CHECK = ErrorCode(
|
||||
category="per-homeserver-tenant-metrics",
|
||||
)
|
||||
|
||||
PREFER_SYNAPSE_CLOCK_CALL_LATER = ErrorCode(
|
||||
"call-later-not-tracked",
|
||||
"Prefer using `synapse.util.Clock.call_later` instead of `reactor.callLater`",
|
||||
category="synapse-reactor-clock",
|
||||
)
|
||||
|
||||
PREFER_SYNAPSE_CLOCK_LOOPING_CALL = ErrorCode(
|
||||
"prefer-synapse-clock-looping-call",
|
||||
"Prefer using `synapse.util.Clock.looping_call` instead of `task.LoopingCall`",
|
||||
category="synapse-reactor-clock",
|
||||
)
|
||||
|
||||
PREFER_SYNAPSE_CLOCK_CALL_WHEN_RUNNING = ErrorCode(
|
||||
"prefer-synapse-clock-call-when-running",
|
||||
"`synapse.util.Clock.call_when_running` should be used instead of `reactor.callWhenRunning`",
|
||||
"Prefer using `synapse.util.Clock.call_when_running` instead of `reactor.callWhenRunning`",
|
||||
category="synapse-reactor-clock",
|
||||
)
|
||||
|
||||
PREFER_SYNAPSE_CLOCK_ADD_SYSTEM_EVENT_TRIGGER = ErrorCode(
|
||||
"prefer-synapse-clock-add-system-event-trigger",
|
||||
"`synapse.util.Clock.add_system_event_trigger` should be used instead of `reactor.addSystemEventTrigger`",
|
||||
"Prefer using `synapse.util.Clock.add_system_event_trigger` instead of `reactor.addSystemEventTrigger`",
|
||||
category="synapse-reactor-clock",
|
||||
)
|
||||
|
||||
MULTIPLE_INTERNAL_CLOCKS_CREATED = ErrorCode(
|
||||
"multiple-internal-clocks",
|
||||
"Only one instance of `clock.Clock` should be created",
|
||||
category="synapse-reactor-clock",
|
||||
)
|
||||
|
||||
UNTRACKED_BACKGROUND_PROCESS = ErrorCode(
|
||||
"untracked-background-process",
|
||||
"Prefer using `HomeServer.run_as_background_process` method over the bare `run_as_background_process`",
|
||||
category="synapse-tracked-calls",
|
||||
)
|
||||
|
||||
|
||||
class Sentinel(enum.Enum):
|
||||
# defining a sentinel in this way allows mypy to correctly handle the
|
||||
@ -222,6 +246,18 @@ class SynapsePlugin(Plugin):
|
||||
# callback, let's just pass it in while we have it.
|
||||
return lambda ctx: check_prometheus_metric_instantiation(ctx, fullname)
|
||||
|
||||
if fullname == "twisted.internet.task.LoopingCall":
|
||||
return check_looping_call
|
||||
|
||||
if fullname == "synapse.util.clock.Clock":
|
||||
return check_clock_creation
|
||||
|
||||
if (
|
||||
fullname
|
||||
== "synapse.metrics.background_process_metrics.run_as_background_process"
|
||||
):
|
||||
return check_background_process
|
||||
|
||||
return None
|
||||
|
||||
def get_method_signature_hook(
|
||||
@ -241,6 +277,13 @@ class SynapsePlugin(Plugin):
|
||||
):
|
||||
return check_is_cacheable_wrapper
|
||||
|
||||
if fullname in (
|
||||
"twisted.internet.interfaces.IReactorTime.callLater",
|
||||
"synapse.types.ISynapseThreadlessReactor.callLater",
|
||||
"synapse.types.ISynapseReactor.callLater",
|
||||
):
|
||||
return check_call_later
|
||||
|
||||
if fullname in (
|
||||
"twisted.internet.interfaces.IReactorCore.callWhenRunning",
|
||||
"synapse.types.ISynapseThreadlessReactor.callWhenRunning",
|
||||
@ -258,6 +301,78 @@ class SynapsePlugin(Plugin):
|
||||
return None
|
||||
|
||||
|
||||
def check_clock_creation(ctx: FunctionSigContext) -> CallableType:
|
||||
"""
|
||||
Ensure that the only `clock.Clock` instance is the one used by the `HomeServer`.
|
||||
This is so that the `HomeServer` can cancel any tracked delayed or looping calls
|
||||
during server shutdown.
|
||||
|
||||
Args:
|
||||
ctx: The `FunctionSigContext` from mypy.
|
||||
"""
|
||||
signature: CallableType = ctx.default_signature
|
||||
ctx.api.fail(
|
||||
"Expected the only `clock.Clock` instance to be the one used by the `HomeServer`. "
|
||||
"This is so that the `HomeServer` can cancel any tracked delayed or looping calls "
|
||||
"during server shutdown",
|
||||
ctx.context,
|
||||
code=MULTIPLE_INTERNAL_CLOCKS_CREATED,
|
||||
)
|
||||
|
||||
return signature
|
||||
|
||||
|
||||
def check_call_later(ctx: MethodSigContext) -> CallableType:
|
||||
"""
|
||||
Ensure that the `reactor.callLater` callsites aren't used.
|
||||
|
||||
`synapse.util.Clock.call_later` should always be used instead of `reactor.callLater`.
|
||||
This is because the `synapse.util.Clock` tracks delayed calls in order to cancel any
|
||||
outstanding calls during server shutdown. Delayed calls which are either short lived
|
||||
(<~60s) or frequently called and can be tracked via other means could be candidates for
|
||||
using `synapse.util.Clock.call_later` with `call_later_cancel_on_shutdown` set to
|
||||
`False`. There shouldn't be a need to use `reactor.callLater` outside of tests or the
|
||||
`Clock` class itself. If a need arises, you can use a type ignore comment to disable the
|
||||
check, e.g. `# type: ignore[call-later-not-tracked]`.
|
||||
|
||||
Args:
|
||||
ctx: The `FunctionSigContext` from mypy.
|
||||
"""
|
||||
signature: CallableType = ctx.default_signature
|
||||
ctx.api.fail(
|
||||
"Expected all `reactor.callLater` calls to use `synapse.util.Clock.call_later` "
|
||||
"instead. This is so that long lived calls can be tracked for cancellation during "
|
||||
"server shutdown",
|
||||
ctx.context,
|
||||
code=PREFER_SYNAPSE_CLOCK_CALL_LATER,
|
||||
)
|
||||
|
||||
return signature
|
||||
|
||||
|
||||
def check_looping_call(ctx: FunctionSigContext) -> CallableType:
|
||||
"""
|
||||
Ensure that the `task.LoopingCall` callsites aren't used.
|
||||
|
||||
`synapse.util.Clock.looping_call` should always be used instead of `task.LoopingCall`.
|
||||
`synapse.util.Clock` tracks looping calls in order to cancel any outstanding calls
|
||||
during server shutdown.
|
||||
|
||||
Args:
|
||||
ctx: The `FunctionSigContext` from mypy.
|
||||
"""
|
||||
signature: CallableType = ctx.default_signature
|
||||
ctx.api.fail(
|
||||
"Expected all `task.LoopingCall` instances to use `synapse.util.Clock.looping_call` "
|
||||
"instead. This is so that long lived calls can be tracked for cancellation during "
|
||||
"server shutdown",
|
||||
ctx.context,
|
||||
code=PREFER_SYNAPSE_CLOCK_LOOPING_CALL,
|
||||
)
|
||||
|
||||
return signature
|
||||
|
||||
|
||||
def check_call_when_running(ctx: MethodSigContext) -> CallableType:
|
||||
"""
|
||||
Ensure that the `reactor.callWhenRunning` callsites aren't used.
|
||||
@ -312,6 +427,27 @@ def check_add_system_event_trigger(ctx: MethodSigContext) -> CallableType:
|
||||
return signature
|
||||
|
||||
|
||||
def check_background_process(ctx: FunctionSigContext) -> CallableType:
|
||||
"""
|
||||
Ensure that calls to `run_as_background_process` use the `HomeServer` method.
|
||||
This is so that the `HomeServer` can cancel any running background processes during
|
||||
server shutdown.
|
||||
|
||||
Args:
|
||||
ctx: The `FunctionSigContext` from mypy.
|
||||
"""
|
||||
signature: CallableType = ctx.default_signature
|
||||
ctx.api.fail(
|
||||
"Prefer using `HomeServer.run_as_background_process` method over the bare "
|
||||
"`run_as_background_process`. This is so that the `HomeServer` can cancel "
|
||||
"any background processes during server shutdown",
|
||||
ctx.context,
|
||||
code=UNTRACKED_BACKGROUND_PROCESS,
|
||||
)
|
||||
|
||||
return signature
|
||||
|
||||
|
||||
def analyze_prometheus_metric_classes(ctx: ClassDefContext) -> None:
|
||||
"""
|
||||
Cross-check the list of Prometheus metric classes against the
|
||||
|
@ -157,7 +157,12 @@ def get_registered_paths_for_default(
|
||||
# TODO We only do this to avoid an error, but don't need the database etc
|
||||
hs.setup()
|
||||
registered_paths = get_registered_paths_for_hs(hs)
|
||||
hs.cleanup()
|
||||
# NOTE: a more robust implementation would properly shutdown/cleanup each server
|
||||
# to avoid resource buildup.
|
||||
# However, the call to `shutdown` is `async` so it would require additional complexity here.
|
||||
# We are intentionally skipping this cleanup because this is a short-lived, one-off
|
||||
# utility script where the simpler approach is sufficient and we shouldn't run into
|
||||
# any resource buildup issues.
|
||||
|
||||
return registered_paths
|
||||
|
||||
|
@ -28,7 +28,6 @@ import yaml
|
||||
from twisted.internet import defer, reactor as reactor_
|
||||
|
||||
from synapse.config.homeserver import HomeServerConfig
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.server import HomeServer
|
||||
from synapse.storage import DataStore
|
||||
from synapse.types import ISynapseReactor
|
||||
@ -53,7 +52,6 @@ class MockHomeserver(HomeServer):
|
||||
|
||||
|
||||
def run_background_updates(hs: HomeServer) -> None:
|
||||
server_name = hs.hostname
|
||||
main = hs.get_datastores().main
|
||||
state = hs.get_datastores().state
|
||||
|
||||
@ -67,9 +65,8 @@ def run_background_updates(hs: HomeServer) -> None:
|
||||
def run() -> None:
|
||||
# Apply all background updates on the database.
|
||||
defer.ensureDeferred(
|
||||
run_as_background_process(
|
||||
hs.run_as_background_process(
|
||||
"background_updates",
|
||||
server_name,
|
||||
run_background_updates,
|
||||
)
|
||||
)
|
||||
|
@ -28,6 +28,7 @@ import sys
|
||||
import traceback
|
||||
import warnings
|
||||
from textwrap import indent
|
||||
from threading import Thread
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
@ -40,6 +41,7 @@ from typing import (
|
||||
Tuple,
|
||||
cast,
|
||||
)
|
||||
from wsgiref.simple_server import WSGIServer
|
||||
|
||||
from cryptography.utils import CryptographyDeprecationWarning
|
||||
from typing_extensions import ParamSpec
|
||||
@ -97,22 +99,47 @@ reactor = cast(ISynapseReactor, _reactor)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# list of tuples of function, args list, kwargs dict
|
||||
_sighup_callbacks: List[
|
||||
Tuple[Callable[..., None], Tuple[object, ...], Dict[str, object]]
|
||||
] = []
|
||||
_instance_id_to_sighup_callbacks_map: Dict[
|
||||
str, List[Tuple[Callable[..., None], Tuple[object, ...], Dict[str, object]]]
|
||||
] = {}
|
||||
"""
|
||||
Map from homeserver instance_id to a list of callbacks.
|
||||
|
||||
We use `instance_id` instead of `server_name` because it's possible to have multiple
|
||||
workers running in the same process with the same `server_name`.
|
||||
"""
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
def register_sighup(func: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None:
|
||||
def register_sighup(
|
||||
homeserver_instance_id: str,
|
||||
func: Callable[P, None],
|
||||
*args: P.args,
|
||||
**kwargs: P.kwargs,
|
||||
) -> None:
|
||||
"""
|
||||
Register a function to be called when a SIGHUP occurs.
|
||||
|
||||
Args:
|
||||
homeserver_instance_id: The unique ID for this Synapse process instance
|
||||
(`hs.get_instance_id()`) that this hook is associated with.
|
||||
func: Function to be called when sent a SIGHUP signal.
|
||||
*args, **kwargs: args and kwargs to be passed to the target function.
|
||||
"""
|
||||
_sighup_callbacks.append((func, args, kwargs))
|
||||
|
||||
_instance_id_to_sighup_callbacks_map.setdefault(homeserver_instance_id, []).append(
|
||||
(func, args, kwargs)
|
||||
)
|
||||
|
||||
|
||||
def unregister_sighups(instance_id: str) -> None:
|
||||
"""
|
||||
Unregister all sighup functions associated with this Synapse instance.
|
||||
|
||||
Args:
|
||||
instance_id: Unique ID for this Synapse process instance.
|
||||
"""
|
||||
_instance_id_to_sighup_callbacks_map.pop(instance_id, [])
|
||||
|
||||
|
||||
def start_worker_reactor(
|
||||
@ -281,7 +308,9 @@ def register_start(
|
||||
clock.call_when_running(lambda: defer.ensureDeferred(wrapper()))
|
||||
|
||||
|
||||
def listen_metrics(bind_addresses: StrCollection, port: int) -> None:
|
||||
def listen_metrics(
|
||||
bind_addresses: StrCollection, port: int
|
||||
) -> List[Tuple[WSGIServer, Thread]]:
|
||||
"""
|
||||
Start Prometheus metrics server.
|
||||
|
||||
@ -294,14 +323,22 @@ def listen_metrics(bind_addresses: StrCollection, port: int) -> None:
|
||||
bytecode at a time), this still works because the metrics thread can preempt the
|
||||
Twisted reactor thread between bytecode boundaries and the metrics thread gets
|
||||
scheduled with roughly equal priority to the Twisted reactor thread.
|
||||
|
||||
Returns:
|
||||
List of WSGIServer with the thread they are running on.
|
||||
"""
|
||||
from prometheus_client import start_http_server as start_http_server_prometheus
|
||||
|
||||
from synapse.metrics import RegistryProxy
|
||||
|
||||
servers: List[Tuple[WSGIServer, Thread]] = []
|
||||
for host in bind_addresses:
|
||||
logger.info("Starting metrics listener on %s:%d", host, port)
|
||||
start_http_server_prometheus(port, addr=host, registry=RegistryProxy)
|
||||
server, thread = start_http_server_prometheus(
|
||||
port, addr=host, registry=RegistryProxy
|
||||
)
|
||||
servers.append((server, thread))
|
||||
return servers
|
||||
|
||||
|
||||
def listen_manhole(
|
||||
@ -309,7 +346,7 @@ def listen_manhole(
|
||||
port: int,
|
||||
manhole_settings: ManholeConfig,
|
||||
manhole_globals: dict,
|
||||
) -> None:
|
||||
) -> List[Port]:
|
||||
# twisted.conch.manhole 21.1.0 uses "int_from_bytes", which produces a confusing
|
||||
# warning. It's fixed by https://github.com/twisted/twisted/pull/1522), so
|
||||
# suppress the warning for now.
|
||||
@ -321,7 +358,7 @@ def listen_manhole(
|
||||
|
||||
from synapse.util.manhole import manhole
|
||||
|
||||
listen_tcp(
|
||||
return listen_tcp(
|
||||
bind_addresses,
|
||||
port,
|
||||
manhole(settings=manhole_settings, globals=manhole_globals),
|
||||
@ -498,7 +535,7 @@ def refresh_certificate(hs: "HomeServer") -> None:
|
||||
logger.info("Context factories updated.")
|
||||
|
||||
|
||||
async def start(hs: "HomeServer") -> None:
|
||||
async def start(hs: "HomeServer", freeze: bool = True) -> None:
|
||||
"""
|
||||
Start a Synapse server or worker.
|
||||
|
||||
@ -509,6 +546,11 @@ async def start(hs: "HomeServer") -> None:
|
||||
|
||||
Args:
|
||||
hs: homeserver instance
|
||||
freeze: whether to freeze the homeserver base objects in the garbage collector.
|
||||
May improve garbage collection performance by marking objects with an effectively
|
||||
static lifetime as frozen so they don't need to be considered for cleanup.
|
||||
If you ever want to `shutdown` the homeserver, this needs to be
|
||||
False otherwise the homeserver cannot be garbage collected after `shutdown`.
|
||||
"""
|
||||
server_name = hs.hostname
|
||||
reactor = hs.get_reactor()
|
||||
@ -541,12 +583,17 @@ async def start(hs: "HomeServer") -> None:
|
||||
# we're not using systemd.
|
||||
sdnotify(b"RELOADING=1")
|
||||
|
||||
for i, args, kwargs in _sighup_callbacks:
|
||||
i(*args, **kwargs)
|
||||
for sighup_callbacks in _instance_id_to_sighup_callbacks_map.values():
|
||||
for func, args, kwargs in sighup_callbacks:
|
||||
func(*args, **kwargs)
|
||||
|
||||
sdnotify(b"READY=1")
|
||||
|
||||
return run_as_background_process(
|
||||
# It's okay to ignore the linter error here and call
|
||||
# `run_as_background_process` directly because `_handle_sighup` operates
|
||||
# outside of the scope of a specific `HomeServer` instance and holds no
|
||||
# references to it which would prevent a clean shutdown.
|
||||
return run_as_background_process( # type: ignore[untracked-background-process]
|
||||
"sighup",
|
||||
server_name,
|
||||
_handle_sighup,
|
||||
@ -564,8 +611,8 @@ async def start(hs: "HomeServer") -> None:
|
||||
|
||||
signal.signal(signal.SIGHUP, run_sighup)
|
||||
|
||||
register_sighup(refresh_certificate, hs)
|
||||
register_sighup(reload_cache_config, hs.config)
|
||||
register_sighup(hs.get_instance_id(), refresh_certificate, hs)
|
||||
register_sighup(hs.get_instance_id(), reload_cache_config, hs.config)
|
||||
|
||||
# Apply the cache config.
|
||||
hs.config.caches.resize_all_caches()
|
||||
@ -603,7 +650,11 @@ async def start(hs: "HomeServer") -> None:
|
||||
logger.info("Shutting down...")
|
||||
|
||||
# Log when we start the shut down process.
|
||||
hs.get_clock().add_system_event_trigger("before", "shutdown", log_shutdown)
|
||||
hs.register_sync_shutdown_handler(
|
||||
phase="before",
|
||||
eventType="shutdown",
|
||||
shutdown_func=log_shutdown,
|
||||
)
|
||||
|
||||
setup_sentry(hs)
|
||||
setup_sdnotify(hs)
|
||||
@ -632,18 +683,24 @@ async def start(hs: "HomeServer") -> None:
|
||||
# `REQUIRED_ON_BACKGROUND_TASK_STARTUP`
|
||||
start_phone_stats_home(hs)
|
||||
|
||||
# We now freeze all allocated objects in the hopes that (almost)
|
||||
# everything currently allocated are things that will be used for the
|
||||
# rest of time. Doing so means less work each GC (hopefully).
|
||||
#
|
||||
# PyPy does not (yet?) implement gc.freeze()
|
||||
if hasattr(gc, "freeze"):
|
||||
gc.collect()
|
||||
gc.freeze()
|
||||
if freeze:
|
||||
# We now freeze all allocated objects in the hopes that (almost)
|
||||
# everything currently allocated are things that will be used for the
|
||||
# rest of time. Doing so means less work each GC (hopefully).
|
||||
#
|
||||
# Note that freezing the homeserver object means that it won't be able to be
|
||||
# garbage collected in the case of attempting an in-memory `shutdown`. This only
|
||||
# needs to be considered if such a case is desirable. Exiting the entire Python
|
||||
# process will function expectedly either way.
|
||||
#
|
||||
# PyPy does not (yet?) implement gc.freeze()
|
||||
if hasattr(gc, "freeze"):
|
||||
gc.collect()
|
||||
gc.freeze()
|
||||
|
||||
# Speed up shutdowns by freezing all allocated objects. This moves everything
|
||||
# into the permanent generation and excludes them from the final GC.
|
||||
atexit.register(gc.freeze)
|
||||
# Speed up process exit by freezing all allocated objects. This moves everything
|
||||
# into the permanent generation and excludes them from the final GC.
|
||||
atexit.register(gc.freeze)
|
||||
|
||||
|
||||
def reload_cache_config(config: HomeServerConfig) -> None:
|
||||
|
@ -278,11 +278,13 @@ class GenericWorkerServer(HomeServer):
|
||||
self._listen_http(listener)
|
||||
elif listener.type == "manhole":
|
||||
if isinstance(listener, TCPListenerConfig):
|
||||
_base.listen_manhole(
|
||||
listener.bind_addresses,
|
||||
listener.port,
|
||||
manhole_settings=self.config.server.manhole_settings,
|
||||
manhole_globals={"hs": self},
|
||||
self._listening_services.extend(
|
||||
_base.listen_manhole(
|
||||
listener.bind_addresses,
|
||||
listener.port,
|
||||
manhole_settings=self.config.server.manhole_settings,
|
||||
manhole_globals={"hs": self},
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise ConfigError(
|
||||
@ -296,9 +298,11 @@ class GenericWorkerServer(HomeServer):
|
||||
)
|
||||
else:
|
||||
if isinstance(listener, TCPListenerConfig):
|
||||
_base.listen_metrics(
|
||||
listener.bind_addresses,
|
||||
listener.port,
|
||||
self._metrics_listeners.extend(
|
||||
_base.listen_metrics(
|
||||
listener.bind_addresses,
|
||||
listener.port,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise ConfigError(
|
||||
|
@ -22,7 +22,7 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import Dict, Iterable, List
|
||||
from typing import Dict, Iterable, List, Optional
|
||||
|
||||
from twisted.internet.tcp import Port
|
||||
from twisted.web.resource import EncodingResourceWrapper, Resource
|
||||
@ -70,6 +70,7 @@ from synapse.rest.synapse.client import build_synapse_client_resource_tree
|
||||
from synapse.rest.well_known import well_known_resource
|
||||
from synapse.server import HomeServer
|
||||
from synapse.storage import DataStore
|
||||
from synapse.types import ISynapseReactor
|
||||
from synapse.util.check_dependencies import VERSION, check_requirements
|
||||
from synapse.util.httpresourcetree import create_resource_tree
|
||||
from synapse.util.module_loader import load_module
|
||||
@ -277,11 +278,13 @@ class SynapseHomeServer(HomeServer):
|
||||
)
|
||||
elif listener.type == "manhole":
|
||||
if isinstance(listener, TCPListenerConfig):
|
||||
_base.listen_manhole(
|
||||
listener.bind_addresses,
|
||||
listener.port,
|
||||
manhole_settings=self.config.server.manhole_settings,
|
||||
manhole_globals={"hs": self},
|
||||
self._listening_services.extend(
|
||||
_base.listen_manhole(
|
||||
listener.bind_addresses,
|
||||
listener.port,
|
||||
manhole_settings=self.config.server.manhole_settings,
|
||||
manhole_globals={"hs": self},
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise ConfigError(
|
||||
@ -294,9 +297,11 @@ class SynapseHomeServer(HomeServer):
|
||||
)
|
||||
else:
|
||||
if isinstance(listener, TCPListenerConfig):
|
||||
_base.listen_metrics(
|
||||
listener.bind_addresses,
|
||||
listener.port,
|
||||
self._metrics_listeners.extend(
|
||||
_base.listen_metrics(
|
||||
listener.bind_addresses,
|
||||
listener.port,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise ConfigError(
|
||||
@ -340,12 +345,23 @@ def load_or_generate_config(argv_options: List[str]) -> HomeServerConfig:
|
||||
return config
|
||||
|
||||
|
||||
def setup(config: HomeServerConfig) -> SynapseHomeServer:
|
||||
def setup(
|
||||
config: HomeServerConfig,
|
||||
reactor: Optional[ISynapseReactor] = None,
|
||||
freeze: bool = True,
|
||||
) -> SynapseHomeServer:
|
||||
"""
|
||||
Create and setup a Synapse homeserver instance given a configuration.
|
||||
|
||||
Args:
|
||||
config: The configuration for the homeserver.
|
||||
reactor: Optionally provide a reactor to use. Can be useful in different
|
||||
scenarios that you want control over the reactor, such as tests.
|
||||
freeze: whether to freeze the homeserver base objects in the garbage collector.
|
||||
May improve garbage collection performance by marking objects with an effectively
|
||||
static lifetime as frozen so they don't need to be considered for cleanup.
|
||||
If you ever want to `shutdown` the homeserver, this needs to be
|
||||
False otherwise the homeserver cannot be garbage collected after `shutdown`.
|
||||
|
||||
Returns:
|
||||
A homeserver instance.
|
||||
@ -384,6 +400,7 @@ def setup(config: HomeServerConfig) -> SynapseHomeServer:
|
||||
config.server.server_name,
|
||||
config=config,
|
||||
version_string=f"Synapse/{VERSION}",
|
||||
reactor=reactor,
|
||||
)
|
||||
|
||||
setup_logging(hs, config, use_worker_options=False)
|
||||
@ -405,7 +422,7 @@ def setup(config: HomeServerConfig) -> SynapseHomeServer:
|
||||
# Loading the provider metadata also ensures the provider config is valid.
|
||||
await oidc.load_metadata()
|
||||
|
||||
await _base.start(hs)
|
||||
await _base.start(hs, freeze)
|
||||
|
||||
hs.get_datastores().main.db_pool.updates.start_doing_background_updates()
|
||||
|
||||
|
@ -29,19 +29,18 @@ from prometheus_client import Gauge
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
run_as_background_process,
|
||||
)
|
||||
from synapse.types import JsonDict
|
||||
from synapse.util.constants import ONE_HOUR_SECONDS, ONE_MINUTE_SECONDS
|
||||
from synapse.util.constants import (
|
||||
MILLISECONDS_PER_SECOND,
|
||||
ONE_HOUR_SECONDS,
|
||||
ONE_MINUTE_SECONDS,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from synapse.server import HomeServer
|
||||
|
||||
logger = logging.getLogger("synapse.app.homeserver")
|
||||
|
||||
MILLISECONDS_PER_SECOND = 1000
|
||||
|
||||
INITIAL_DELAY_BEFORE_FIRST_PHONE_HOME_SECONDS = 5 * ONE_MINUTE_SECONDS
|
||||
"""
|
||||
We wait 5 minutes to send the first set of stats as the server can be quite busy the
|
||||
@ -85,8 +84,6 @@ def phone_stats_home(
|
||||
stats: JsonDict,
|
||||
stats_process: List[Tuple[int, "resource.struct_rusage"]] = _stats_process,
|
||||
) -> "defer.Deferred[None]":
|
||||
server_name = hs.hostname
|
||||
|
||||
async def _phone_stats_home(
|
||||
hs: "HomeServer",
|
||||
stats: JsonDict,
|
||||
@ -200,8 +197,8 @@ def phone_stats_home(
|
||||
except Exception as e:
|
||||
logger.warning("Error reporting stats: %s", e)
|
||||
|
||||
return run_as_background_process(
|
||||
"phone_stats_home", server_name, _phone_stats_home, hs, stats, stats_process
|
||||
return hs.run_as_background_process(
|
||||
"phone_stats_home", _phone_stats_home, hs, stats, stats_process
|
||||
)
|
||||
|
||||
|
||||
@ -263,9 +260,8 @@ def start_phone_stats_home(hs: "HomeServer") -> None:
|
||||
float(hs.config.server.max_mau_value)
|
||||
)
|
||||
|
||||
return run_as_background_process(
|
||||
return hs.run_as_background_process(
|
||||
"generate_monthly_active_users",
|
||||
server_name,
|
||||
_generate_monthly_active_users,
|
||||
)
|
||||
|
||||
@ -285,10 +281,16 @@ def start_phone_stats_home(hs: "HomeServer") -> None:
|
||||
|
||||
# We need to defer this init for the cases that we daemonize
|
||||
# otherwise the process ID we get is that of the non-daemon process
|
||||
clock.call_later(0, performance_stats_init)
|
||||
clock.call_later(
|
||||
0,
|
||||
performance_stats_init,
|
||||
)
|
||||
|
||||
# We wait 5 minutes to send the first set of stats as the server can
|
||||
# be quite busy the first few minutes
|
||||
clock.call_later(
|
||||
INITIAL_DELAY_BEFORE_FIRST_PHONE_HOME_SECONDS, phone_stats_home, hs, stats
|
||||
INITIAL_DELAY_BEFORE_FIRST_PHONE_HOME_SECONDS,
|
||||
phone_stats_home,
|
||||
hs,
|
||||
stats,
|
||||
)
|
||||
|
@ -23,15 +23,33 @@
|
||||
import logging
|
||||
import re
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Pattern, Sequence
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Pattern,
|
||||
Sequence,
|
||||
cast,
|
||||
)
|
||||
|
||||
import attr
|
||||
from netaddr import IPSet
|
||||
|
||||
from twisted.internet import reactor
|
||||
|
||||
from synapse.api.constants import EventTypes
|
||||
from synapse.events import EventBase
|
||||
from synapse.types import DeviceListUpdates, JsonDict, JsonMapping, UserID
|
||||
from synapse.types import (
|
||||
DeviceListUpdates,
|
||||
ISynapseThreadlessReactor,
|
||||
JsonDict,
|
||||
JsonMapping,
|
||||
UserID,
|
||||
)
|
||||
from synapse.util.caches.descriptors import _CacheContext, cached
|
||||
from synapse.util.clock import Clock
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from synapse.appservice.api import ApplicationServiceApi
|
||||
@ -98,6 +116,15 @@ class ApplicationService:
|
||||
self.sender = sender
|
||||
# The application service user should be part of the server's domain.
|
||||
self.server_name = sender.domain # nb must be called this for @cached
|
||||
|
||||
# Ideally we would require passing in the `HomeServer` `Clock` instance.
|
||||
# However this is not currently possible as there are places which use
|
||||
# `@cached` that aren't aware of the `HomeServer` instance.
|
||||
# nb must be called this for @cached
|
||||
self.clock = Clock(
|
||||
cast(ISynapseThreadlessReactor, reactor), server_name=self.server_name
|
||||
) # type: ignore[multiple-internal-clocks]
|
||||
|
||||
self.namespaces = self._check_namespaces(namespaces)
|
||||
self.id = id
|
||||
self.ip_range_whitelist = ip_range_whitelist
|
||||
|
@ -81,7 +81,6 @@ from synapse.appservice import (
|
||||
from synapse.appservice.api import ApplicationServiceApi
|
||||
from synapse.events import EventBase
|
||||
from synapse.logging.context import run_in_background
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.storage.databases.main import DataStore
|
||||
from synapse.types import DeviceListUpdates, JsonMapping
|
||||
from synapse.util.clock import Clock
|
||||
@ -200,6 +199,7 @@ class _ServiceQueuer:
|
||||
)
|
||||
self.server_name = hs.hostname
|
||||
self.clock = hs.get_clock()
|
||||
self.hs = hs
|
||||
self._store = hs.get_datastores().main
|
||||
|
||||
def start_background_request(self, service: ApplicationService) -> None:
|
||||
@ -207,9 +207,7 @@ class _ServiceQueuer:
|
||||
if service.id in self.requests_in_flight:
|
||||
return
|
||||
|
||||
run_as_background_process(
|
||||
"as-sender", self.server_name, self._send_request, service
|
||||
)
|
||||
self.hs.run_as_background_process("as-sender", self._send_request, service)
|
||||
|
||||
async def _send_request(self, service: ApplicationService) -> None:
|
||||
# sanity-check: we shouldn't get here if this service already has a sender
|
||||
@ -361,6 +359,7 @@ class _TransactionController:
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.server_name = hs.hostname
|
||||
self.clock = hs.get_clock()
|
||||
self.hs = hs
|
||||
self.store = hs.get_datastores().main
|
||||
self.as_api = hs.get_application_service_api()
|
||||
|
||||
@ -448,6 +447,7 @@ class _TransactionController:
|
||||
recoverer = self.RECOVERER_CLASS(
|
||||
self.server_name,
|
||||
self.clock,
|
||||
self.hs,
|
||||
self.store,
|
||||
self.as_api,
|
||||
service,
|
||||
@ -494,6 +494,7 @@ class _Recoverer:
|
||||
self,
|
||||
server_name: str,
|
||||
clock: Clock,
|
||||
hs: "HomeServer",
|
||||
store: DataStore,
|
||||
as_api: ApplicationServiceApi,
|
||||
service: ApplicationService,
|
||||
@ -501,6 +502,7 @@ class _Recoverer:
|
||||
):
|
||||
self.server_name = server_name
|
||||
self.clock = clock
|
||||
self.hs = hs
|
||||
self.store = store
|
||||
self.as_api = as_api
|
||||
self.service = service
|
||||
@ -513,9 +515,8 @@ class _Recoverer:
|
||||
logger.info("Scheduling retries on %s in %fs", self.service.id, delay)
|
||||
self.scheduled_recovery = self.clock.call_later(
|
||||
delay,
|
||||
run_as_background_process,
|
||||
self.hs.run_as_background_process,
|
||||
"as-recoverer",
|
||||
self.server_name,
|
||||
self.retry,
|
||||
)
|
||||
|
||||
@ -535,9 +536,8 @@ class _Recoverer:
|
||||
if self.scheduled_recovery:
|
||||
self.clock.cancel_call_later(self.scheduled_recovery)
|
||||
# Run a retry, which will resechedule a recovery if it fails.
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"retry",
|
||||
self.server_name,
|
||||
self.retry,
|
||||
)
|
||||
|
||||
|
@ -345,7 +345,9 @@ def setup_logging(
|
||||
# Add a SIGHUP handler to reload the logging configuration, if one is available.
|
||||
from synapse.app import _base as appbase
|
||||
|
||||
appbase.register_sighup(_reload_logging_config, log_config_path)
|
||||
appbase.register_sighup(
|
||||
hs.get_instance_id(), _reload_logging_config, log_config_path
|
||||
)
|
||||
|
||||
# Log immediately so we can grep backwards.
|
||||
logger.warning("***** STARTING SERVER *****")
|
||||
|
@ -172,7 +172,7 @@ class Keyring:
|
||||
_FetchKeyRequest, Dict[str, Dict[str, FetchKeyResult]]
|
||||
] = BatchingQueue(
|
||||
name="keyring_server",
|
||||
server_name=self.server_name,
|
||||
hs=hs,
|
||||
clock=hs.get_clock(),
|
||||
# The method called to fetch each key
|
||||
process_batch_callback=self._inner_fetch_key_requests,
|
||||
@ -194,6 +194,14 @@ class Keyring:
|
||||
valid_until_ts=2**63, # fake future timestamp
|
||||
)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
Prepares the KeyRing for garbage collection by shutting down it's queues.
|
||||
"""
|
||||
self._fetch_keys_queue.shutdown()
|
||||
for key_fetcher in self._key_fetchers:
|
||||
key_fetcher.shutdown()
|
||||
|
||||
async def verify_json_for_server(
|
||||
self,
|
||||
server_name: str,
|
||||
@ -479,11 +487,17 @@ class KeyFetcher(metaclass=abc.ABCMeta):
|
||||
self.server_name = hs.hostname
|
||||
self._queue = BatchingQueue(
|
||||
name=self.__class__.__name__,
|
||||
server_name=self.server_name,
|
||||
hs=hs,
|
||||
clock=hs.get_clock(),
|
||||
process_batch_callback=self._fetch_keys,
|
||||
)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
Prepares the KeyFetcher for garbage collection by shutting down it's queue.
|
||||
"""
|
||||
self._queue.shutdown()
|
||||
|
||||
async def get_keys(
|
||||
self, server_name: str, key_ids: List[str], minimum_valid_until_ts: int
|
||||
) -> Dict[str, FetchKeyResult]:
|
||||
|
@ -148,6 +148,7 @@ class FederationClient(FederationBase):
|
||||
self._get_pdu_cache: ExpiringCache[str, Tuple[EventBase, str]] = ExpiringCache(
|
||||
cache_name="get_pdu_cache",
|
||||
server_name=self.server_name,
|
||||
hs=self.hs,
|
||||
clock=self._clock,
|
||||
max_len=1000,
|
||||
expiry_ms=120 * 1000,
|
||||
@ -167,6 +168,7 @@ class FederationClient(FederationBase):
|
||||
] = ExpiringCache(
|
||||
cache_name="get_room_hierarchy_cache",
|
||||
server_name=self.server_name,
|
||||
hs=self.hs,
|
||||
clock=self._clock,
|
||||
max_len=1000,
|
||||
expiry_ms=5 * 60 * 1000,
|
||||
|
@ -144,6 +144,9 @@ class FederationRemoteSendQueue(AbstractFederationSender):
|
||||
|
||||
self.clock.looping_call(self._clear_queue, 30 * 1000)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""Stops this federation sender instance from sending further transactions."""
|
||||
|
||||
def _next_pos(self) -> int:
|
||||
pos = self.pos
|
||||
self.pos += 1
|
||||
|
@ -168,7 +168,6 @@ from synapse.metrics import (
|
||||
events_processed_counter,
|
||||
)
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
run_as_background_process,
|
||||
wrap_as_background_process,
|
||||
)
|
||||
from synapse.types import (
|
||||
@ -232,6 +231,11 @@ WAKEUP_INTERVAL_BETWEEN_DESTINATIONS_SEC = 5
|
||||
|
||||
|
||||
class AbstractFederationSender(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def shutdown(self) -> None:
|
||||
"""Stops this federation sender instance from sending further transactions."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def notify_new_events(self, max_token: RoomStreamToken) -> None:
|
||||
"""This gets called when we have some new events we might want to
|
||||
@ -326,6 +330,7 @@ class _DestinationWakeupQueue:
|
||||
_MAX_TIME_IN_QUEUE = 30.0
|
||||
|
||||
sender: "FederationSender" = attr.ib()
|
||||
hs: "HomeServer" = attr.ib()
|
||||
server_name: str = attr.ib()
|
||||
"""
|
||||
Our homeserver name (used to label metrics) (`hs.hostname`).
|
||||
@ -453,18 +458,30 @@ class FederationSender(AbstractFederationSender):
|
||||
1.0 / hs.config.ratelimiting.federation_rr_transactions_per_room_per_second
|
||||
)
|
||||
self._destination_wakeup_queue = _DestinationWakeupQueue(
|
||||
self, self.server_name, self.clock, max_delay_s=rr_txn_interval_per_room_s
|
||||
self,
|
||||
hs,
|
||||
self.server_name,
|
||||
self.clock,
|
||||
max_delay_s=rr_txn_interval_per_room_s,
|
||||
)
|
||||
|
||||
# It is important for `_is_shutdown` to be instantiated before the looping call
|
||||
# for `wake_destinations_needing_catchup`.
|
||||
self._is_shutdown = False
|
||||
|
||||
# Regularly wake up destinations that have outstanding PDUs to be caught up
|
||||
self.clock.looping_call_now(
|
||||
run_as_background_process,
|
||||
self.hs.run_as_background_process,
|
||||
WAKEUP_RETRY_PERIOD_SEC * 1000.0,
|
||||
"wake_destinations_needing_catchup",
|
||||
self.server_name,
|
||||
self._wake_destinations_needing_catchup,
|
||||
)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
self._is_shutdown = True
|
||||
for queue in self._per_destination_queues.values():
|
||||
queue.shutdown()
|
||||
|
||||
def _get_per_destination_queue(
|
||||
self, destination: str
|
||||
) -> Optional[PerDestinationQueue]:
|
||||
@ -503,16 +520,15 @@ class FederationSender(AbstractFederationSender):
|
||||
return
|
||||
|
||||
# fire off a processing loop in the background
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"process_event_queue_for_federation",
|
||||
self.server_name,
|
||||
self._process_event_queue_loop,
|
||||
)
|
||||
|
||||
async def _process_event_queue_loop(self) -> None:
|
||||
try:
|
||||
self._is_processing = True
|
||||
while True:
|
||||
while not self._is_shutdown:
|
||||
last_token = await self.store.get_federation_out_pos("events")
|
||||
(
|
||||
next_token,
|
||||
@ -1123,7 +1139,7 @@ class FederationSender(AbstractFederationSender):
|
||||
|
||||
last_processed: Optional[str] = None
|
||||
|
||||
while True:
|
||||
while not self._is_shutdown:
|
||||
destinations_to_wake = (
|
||||
await self.store.get_catch_up_outstanding_destinations(last_processed)
|
||||
)
|
||||
|
@ -28,6 +28,8 @@ from typing import TYPE_CHECKING, Dict, Hashable, Iterable, List, Optional, Tupl
|
||||
import attr
|
||||
from prometheus_client import Counter
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.constants import EduTypes
|
||||
from synapse.api.errors import (
|
||||
FederationDeniedError,
|
||||
@ -41,7 +43,6 @@ from synapse.handlers.presence import format_user_presence_state
|
||||
from synapse.logging import issue9533_logger
|
||||
from synapse.logging.opentracing import SynapseTags, set_tag
|
||||
from synapse.metrics import SERVER_NAME_LABEL, sent_transactions_counter
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.types import JsonDict, ReadReceipt
|
||||
from synapse.util.retryutils import NotRetryingDestination, get_retry_limiter
|
||||
from synapse.visibility import filter_events_for_server
|
||||
@ -79,6 +80,7 @@ MAX_PRESENCE_STATES_PER_EDU = 50
|
||||
class PerDestinationQueue:
|
||||
"""
|
||||
Manages the per-destination transmission queues.
|
||||
Runs until `shutdown()` is called on the queue.
|
||||
|
||||
Args:
|
||||
hs
|
||||
@ -94,6 +96,7 @@ class PerDestinationQueue:
|
||||
destination: str,
|
||||
):
|
||||
self.server_name = hs.hostname
|
||||
self._hs = hs
|
||||
self._clock = hs.get_clock()
|
||||
self._storage_controllers = hs.get_storage_controllers()
|
||||
self._store = hs.get_datastores().main
|
||||
@ -117,6 +120,8 @@ class PerDestinationQueue:
|
||||
|
||||
self._destination = destination
|
||||
self.transmission_loop_running = False
|
||||
self._transmission_loop_enabled = True
|
||||
self.active_transmission_loop: Optional[defer.Deferred] = None
|
||||
|
||||
# Flag to signal to any running transmission loop that there is new data
|
||||
# queued up to be sent.
|
||||
@ -171,6 +176,20 @@ class PerDestinationQueue:
|
||||
def __str__(self) -> str:
|
||||
return "PerDestinationQueue[%s]" % self._destination
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""Instruct the queue to stop processing any further requests"""
|
||||
self._transmission_loop_enabled = False
|
||||
# The transaction manager must be shutdown before cancelling the active
|
||||
# transmission loop. Otherwise the transmission loop can enter a new cycle of
|
||||
# sleeping before retrying since the shutdown flag of the _transaction_manager
|
||||
# hasn't been set yet.
|
||||
self._transaction_manager.shutdown()
|
||||
try:
|
||||
if self.active_transmission_loop is not None:
|
||||
self.active_transmission_loop.cancel()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def pending_pdu_count(self) -> int:
|
||||
return len(self._pending_pdus)
|
||||
|
||||
@ -309,11 +328,14 @@ class PerDestinationQueue:
|
||||
)
|
||||
return
|
||||
|
||||
if not self._transmission_loop_enabled:
|
||||
logger.warning("Shutdown has been requested. Not sending transaction")
|
||||
return
|
||||
|
||||
logger.debug("TX [%s] Starting transaction loop", self._destination)
|
||||
|
||||
run_as_background_process(
|
||||
self.active_transmission_loop = self._hs.run_as_background_process(
|
||||
"federation_transaction_transmission_loop",
|
||||
self.server_name,
|
||||
self._transaction_transmission_loop,
|
||||
)
|
||||
|
||||
@ -321,13 +343,13 @@ class PerDestinationQueue:
|
||||
pending_pdus: List[EventBase] = []
|
||||
try:
|
||||
self.transmission_loop_running = True
|
||||
|
||||
# This will throw if we wouldn't retry. We do this here so we fail
|
||||
# quickly, but we will later check this again in the http client,
|
||||
# hence why we throw the result away.
|
||||
await get_retry_limiter(
|
||||
destination=self._destination,
|
||||
our_server_name=self.server_name,
|
||||
hs=self._hs,
|
||||
clock=self._clock,
|
||||
store=self._store,
|
||||
)
|
||||
@ -339,7 +361,7 @@ class PerDestinationQueue:
|
||||
# not caught up yet
|
||||
return
|
||||
|
||||
while True:
|
||||
while self._transmission_loop_enabled:
|
||||
self._new_data_to_send = False
|
||||
|
||||
async with _TransactionQueueManager(self) as (
|
||||
@ -352,8 +374,8 @@ class PerDestinationQueue:
|
||||
# If we've gotten told about new things to send during
|
||||
# checking for things to send, we try looking again.
|
||||
# Otherwise new PDUs or EDUs might arrive in the meantime,
|
||||
# but not get sent because we hold the
|
||||
# `transmission_loop_running` flag.
|
||||
# but not get sent because we currently have an
|
||||
# `_active_transmission_loop` running.
|
||||
if self._new_data_to_send:
|
||||
continue
|
||||
else:
|
||||
@ -442,6 +464,7 @@ class PerDestinationQueue:
|
||||
)
|
||||
finally:
|
||||
# We want to be *very* sure we clear this after we stop processing
|
||||
self.active_transmission_loop = None
|
||||
self.transmission_loop_running = False
|
||||
|
||||
async def _catch_up_transmission_loop(self) -> None:
|
||||
@ -469,7 +492,7 @@ class PerDestinationQueue:
|
||||
last_successful_stream_ordering: int = _tmp_last_successful_stream_ordering
|
||||
|
||||
# get at most 50 catchup room/PDUs
|
||||
while True:
|
||||
while self._transmission_loop_enabled:
|
||||
event_ids = await self._store.get_catch_up_room_event_ids(
|
||||
self._destination, last_successful_stream_ordering
|
||||
)
|
||||
|
@ -72,6 +72,12 @@ class TransactionManager:
|
||||
# HACK to get unique tx id
|
||||
self._next_txn_id = int(self.clock.time_msec())
|
||||
|
||||
self._is_shutdown = False
|
||||
|
||||
def shutdown(self) -> None:
|
||||
self._is_shutdown = True
|
||||
self._transport_layer.shutdown()
|
||||
|
||||
@measure_func("_send_new_transaction")
|
||||
async def send_new_transaction(
|
||||
self,
|
||||
@ -86,6 +92,12 @@ class TransactionManager:
|
||||
edus: List of EDUs to send
|
||||
"""
|
||||
|
||||
if self._is_shutdown:
|
||||
logger.warning(
|
||||
"TransactionManager has been shutdown, not sending transaction"
|
||||
)
|
||||
return
|
||||
|
||||
# Make a transaction-sending opentracing span. This span follows on from
|
||||
# all the edus in that transaction. This needs to be done since there is
|
||||
# no active span here, so if the edus were not received by the remote the
|
||||
|
@ -70,6 +70,9 @@ class TransportLayerClient:
|
||||
self.client = hs.get_federation_http_client()
|
||||
self._is_mine_server_name = hs.is_mine_server_name
|
||||
|
||||
def shutdown(self) -> None:
|
||||
self.client.shutdown()
|
||||
|
||||
async def get_room_state_ids(
|
||||
self, destination: str, room_id: str, event_id: str
|
||||
) -> JsonDict:
|
||||
|
@ -37,10 +37,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class AccountValidityHandler:
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.hs = hs
|
||||
self.server_name = (
|
||||
hs.hostname
|
||||
) # nb must be called this for @wrap_as_background_process
|
||||
self.hs = hs # nb must be called this for @wrap_as_background_process
|
||||
self.server_name = hs.hostname
|
||||
self.config = hs.config
|
||||
self.store = hs.get_datastores().main
|
||||
self.send_email_handler = hs.get_send_email_handler()
|
||||
|
@ -47,7 +47,6 @@ from synapse.metrics import (
|
||||
event_processing_loop_room_count,
|
||||
)
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
run_as_background_process,
|
||||
wrap_as_background_process,
|
||||
)
|
||||
from synapse.storage.databases.main.directory import RoomAliasMapping
|
||||
@ -76,9 +75,8 @@ events_processed_counter = Counter(
|
||||
|
||||
class ApplicationServicesHandler:
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.server_name = (
|
||||
hs.hostname
|
||||
) # nb must be called this for @wrap_as_background_process
|
||||
self.server_name = hs.hostname
|
||||
self.hs = hs # nb must be called this for @wrap_as_background_process
|
||||
self.store = hs.get_datastores().main
|
||||
self.is_mine_id = hs.is_mine_id
|
||||
self.appservice_api = hs.get_application_service_api()
|
||||
@ -171,8 +169,8 @@ class ApplicationServicesHandler:
|
||||
except Exception:
|
||||
logger.error("Application Services Failure")
|
||||
|
||||
run_as_background_process(
|
||||
"as_scheduler", self.server_name, start_scheduler
|
||||
self.hs.run_as_background_process(
|
||||
"as_scheduler", start_scheduler
|
||||
)
|
||||
self.started_scheduler = True
|
||||
|
||||
|
@ -24,7 +24,6 @@ from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from synapse.api.constants import Membership
|
||||
from synapse.api.errors import SynapseError
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.replication.http.deactivate_account import (
|
||||
ReplicationNotifyAccountDeactivatedServlet,
|
||||
)
|
||||
@ -272,8 +271,8 @@ class DeactivateAccountHandler:
|
||||
pending deactivation, if it isn't already running.
|
||||
"""
|
||||
if not self._user_parter_running:
|
||||
run_as_background_process(
|
||||
"user_parter_loop", self.server_name, self._user_parter_loop
|
||||
self.hs.run_as_background_process(
|
||||
"user_parter_loop", self._user_parter_loop
|
||||
)
|
||||
|
||||
async def _user_parter_loop(self) -> None:
|
||||
|
@ -24,9 +24,6 @@ from synapse.config.workers import MAIN_PROCESS_INSTANCE_NAME
|
||||
from synapse.logging.context import make_deferred_yieldable
|
||||
from synapse.logging.opentracing import set_tag
|
||||
from synapse.metrics import SERVER_NAME_LABEL, event_processing_positions
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
run_as_background_process,
|
||||
)
|
||||
from synapse.replication.http.delayed_events import (
|
||||
ReplicationAddedDelayedEventRestServlet,
|
||||
)
|
||||
@ -58,6 +55,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class DelayedEventsHandler:
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.hs = hs
|
||||
self.server_name = hs.hostname
|
||||
self._store = hs.get_datastores().main
|
||||
self._storage_controllers = hs.get_storage_controllers()
|
||||
@ -94,7 +92,10 @@ class DelayedEventsHandler:
|
||||
hs.get_notifier().add_replication_callback(self.notify_new_event)
|
||||
# Kick off again (without blocking) to catch any missed notifications
|
||||
# that may have fired before the callback was added.
|
||||
self._clock.call_later(0, self.notify_new_event)
|
||||
self._clock.call_later(
|
||||
0,
|
||||
self.notify_new_event,
|
||||
)
|
||||
|
||||
# Delayed events that are already marked as processed on startup might not have been
|
||||
# sent properly on the last run of the server, so unmark them to send them again.
|
||||
@ -112,15 +113,14 @@ class DelayedEventsHandler:
|
||||
self._schedule_next_at(next_send_ts)
|
||||
|
||||
# Can send the events in background after having awaited on marking them as processed
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"_send_events",
|
||||
self.server_name,
|
||||
self._send_events,
|
||||
events,
|
||||
)
|
||||
|
||||
self._initialized_from_db = run_as_background_process(
|
||||
"_schedule_db_events", self.server_name, _schedule_db_events
|
||||
self._initialized_from_db = self.hs.run_as_background_process(
|
||||
"_schedule_db_events", _schedule_db_events
|
||||
)
|
||||
else:
|
||||
self._repl_client = ReplicationAddedDelayedEventRestServlet.make_client(hs)
|
||||
@ -145,9 +145,7 @@ class DelayedEventsHandler:
|
||||
finally:
|
||||
self._event_processing = False
|
||||
|
||||
run_as_background_process(
|
||||
"delayed_events.notify_new_event", self.server_name, process
|
||||
)
|
||||
self.hs.run_as_background_process("delayed_events.notify_new_event", process)
|
||||
|
||||
async def _unsafe_process_new_event(self) -> None:
|
||||
# We purposefully fetch the current max room stream ordering before
|
||||
@ -542,9 +540,8 @@ class DelayedEventsHandler:
|
||||
if self._next_delayed_event_call is None:
|
||||
self._next_delayed_event_call = self._clock.call_later(
|
||||
delay_sec,
|
||||
run_as_background_process,
|
||||
self.hs.run_as_background_process,
|
||||
"_send_on_timeout",
|
||||
self.server_name,
|
||||
self._send_on_timeout,
|
||||
)
|
||||
else:
|
||||
|
@ -47,7 +47,6 @@ from synapse.api.errors import (
|
||||
)
|
||||
from synapse.logging.opentracing import log_kv, set_tag, trace
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
run_as_background_process,
|
||||
wrap_as_background_process,
|
||||
)
|
||||
from synapse.replication.http.devices import (
|
||||
@ -125,7 +124,7 @@ class DeviceHandler:
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.server_name = hs.hostname # nb must be called this for @measure_func
|
||||
self.clock = hs.get_clock() # nb must be called this for @measure_func
|
||||
self.hs = hs
|
||||
self.hs = hs # nb must be called this for @wrap_as_background_process
|
||||
self.store = cast("GenericWorkerStore", hs.get_datastores().main)
|
||||
self.notifier = hs.get_notifier()
|
||||
self.state = hs.get_state_handler()
|
||||
@ -191,10 +190,9 @@ class DeviceHandler:
|
||||
and self._delete_stale_devices_after is not None
|
||||
):
|
||||
self.clock.looping_call(
|
||||
run_as_background_process,
|
||||
self.hs.run_as_background_process,
|
||||
DELETE_STALE_DEVICES_INTERVAL_MS,
|
||||
desc="delete_stale_devices",
|
||||
server_name=self.server_name,
|
||||
func=self._delete_stale_devices,
|
||||
)
|
||||
|
||||
@ -963,10 +961,9 @@ class DeviceWriterHandler(DeviceHandler):
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
super().__init__(hs)
|
||||
self.server_name = hs.hostname # nb must be called this for @measure_func
|
||||
self.hs = hs # nb must be called this for @wrap_as_background_process
|
||||
|
||||
self.server_name = (
|
||||
hs.hostname
|
||||
) # nb must be called this for @measure_func and @wrap_as_background_process
|
||||
# We only need to poke the federation sender explicitly if its on the
|
||||
# same instance. Other federation sender instances will get notified by
|
||||
# `synapse.app.generic_worker.FederationSenderHandler` when it sees it
|
||||
@ -1444,7 +1441,7 @@ class DeviceListUpdater(DeviceListWorkerUpdater):
|
||||
def __init__(self, hs: "HomeServer", device_handler: DeviceWriterHandler):
|
||||
super().__init__(hs)
|
||||
|
||||
self.server_name = hs.hostname
|
||||
self.hs = hs
|
||||
self.federation = hs.get_federation_client()
|
||||
self.server_name = hs.hostname # nb must be called this for @measure_func
|
||||
self.clock = hs.get_clock() # nb must be called this for @measure_func
|
||||
@ -1468,6 +1465,7 @@ class DeviceListUpdater(DeviceListWorkerUpdater):
|
||||
self._seen_updates: ExpiringCache[str, Set[str]] = ExpiringCache(
|
||||
cache_name="device_update_edu",
|
||||
server_name=self.server_name,
|
||||
hs=self.hs,
|
||||
clock=self.clock,
|
||||
max_len=10000,
|
||||
expiry_ms=30 * 60 * 1000,
|
||||
@ -1477,9 +1475,8 @@ class DeviceListUpdater(DeviceListWorkerUpdater):
|
||||
# Attempt to resync out of sync device lists every 30s.
|
||||
self._resync_retry_lock = Lock()
|
||||
self.clock.looping_call(
|
||||
run_as_background_process,
|
||||
self.hs.run_as_background_process,
|
||||
30 * 1000,
|
||||
server_name=self.server_name,
|
||||
func=self._maybe_retry_device_resync,
|
||||
desc="_maybe_retry_device_resync",
|
||||
)
|
||||
@ -1599,9 +1596,8 @@ class DeviceListUpdater(DeviceListWorkerUpdater):
|
||||
if resync:
|
||||
# We mark as stale up front in case we get restarted.
|
||||
await self.store.mark_remote_users_device_caches_as_stale([user_id])
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"_maybe_retry_device_resync",
|
||||
self.server_name,
|
||||
self.multi_user_device_resync,
|
||||
[user_id],
|
||||
False,
|
||||
|
@ -872,9 +872,7 @@ class E2eKeysHandler:
|
||||
log_kv(
|
||||
{"message": "Did not update one_time_keys", "reason": "no keys given"}
|
||||
)
|
||||
fallback_keys = keys.get("fallback_keys") or keys.get(
|
||||
"org.matrix.msc2732.fallback_keys"
|
||||
)
|
||||
fallback_keys = keys.get("fallback_keys")
|
||||
if fallback_keys and isinstance(fallback_keys, dict):
|
||||
log_kv(
|
||||
{
|
||||
|
@ -72,7 +72,6 @@ from synapse.http.servlet import assert_params_in_dict
|
||||
from synapse.logging.context import nested_logging_context
|
||||
from synapse.logging.opentracing import SynapseTags, set_tag, tag_args, trace
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.module_api import NOT_SPAM
|
||||
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
|
||||
from synapse.storage.invite_rule import InviteRule
|
||||
@ -188,9 +187,8 @@ class FederationHandler:
|
||||
# any partial-state-resync operations which were in flight when we
|
||||
# were shut down.
|
||||
if not hs.config.worker.worker_app:
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"resume_sync_partial_state_room",
|
||||
self.server_name,
|
||||
self._resume_partial_state_room_sync,
|
||||
)
|
||||
|
||||
@ -318,9 +316,8 @@ class FederationHandler:
|
||||
logger.debug(
|
||||
"_maybe_backfill_inner: all backfill points are *after* current depth. Trying again with later backfill points."
|
||||
)
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"_maybe_backfill_inner_anyway_with_max_depth",
|
||||
self.server_name,
|
||||
self.maybe_backfill,
|
||||
room_id=room_id,
|
||||
# We use `MAX_DEPTH` so that we find all backfill points next
|
||||
@ -802,9 +799,8 @@ class FederationHandler:
|
||||
# lots of requests for missing prev_events which we do actually
|
||||
# have. Hence we fire off the background task, but don't wait for it.
|
||||
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"handle_queued_pdus",
|
||||
self.server_name,
|
||||
self._handle_queued_pdus,
|
||||
room_queue,
|
||||
)
|
||||
@ -1877,9 +1873,8 @@ class FederationHandler:
|
||||
room_id=room_id,
|
||||
)
|
||||
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
desc="sync_partial_state_room",
|
||||
server_name=self.server_name,
|
||||
func=_sync_partial_state_room_wrapper,
|
||||
)
|
||||
|
||||
|
@ -81,7 +81,6 @@ from synapse.logging.opentracing import (
|
||||
trace,
|
||||
)
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.replication.http.federation import (
|
||||
ReplicationFederationSendEventsRestServlet,
|
||||
)
|
||||
@ -153,6 +152,7 @@ class FederationEventHandler:
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.server_name = hs.hostname
|
||||
self.hs = hs
|
||||
self._clock = hs.get_clock()
|
||||
self._store = hs.get_datastores().main
|
||||
self._state_store = hs.get_datastores().state
|
||||
@ -175,6 +175,7 @@ class FederationEventHandler:
|
||||
)
|
||||
self._notifier = hs.get_notifier()
|
||||
|
||||
self._server_name = hs.hostname
|
||||
self._is_mine_id = hs.is_mine_id
|
||||
self._is_mine_server_name = hs.is_mine_server_name
|
||||
self._instance_name = hs.get_instance_name()
|
||||
@ -974,9 +975,8 @@ class FederationEventHandler:
|
||||
# Process previously failed backfill events in the background to not waste
|
||||
# time on something that is likely to fail again.
|
||||
if len(events_with_failed_pull_attempts) > 0:
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"_process_new_pulled_events_with_failed_pull_attempts",
|
||||
self.server_name,
|
||||
_process_new_pulled_events,
|
||||
events_with_failed_pull_attempts,
|
||||
)
|
||||
@ -1568,9 +1568,8 @@ class FederationEventHandler:
|
||||
resync = True
|
||||
|
||||
if resync:
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"resync_device_due_to_pdu",
|
||||
self.server_name,
|
||||
self._resync_device,
|
||||
event.sender,
|
||||
)
|
||||
|
@ -67,7 +67,6 @@ from synapse.handlers.directory import DirectoryHandler
|
||||
from synapse.handlers.worker_lock import NEW_EVENT_DURING_PURGE_LOCK_NAME
|
||||
from synapse.logging import opentracing
|
||||
from synapse.logging.context import make_deferred_yieldable, run_in_background
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.replication.http.send_events import ReplicationSendEventsRestServlet
|
||||
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
|
||||
from synapse.types import (
|
||||
@ -99,6 +98,7 @@ class MessageHandler:
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.server_name = hs.hostname
|
||||
self.hs = hs
|
||||
self.auth = hs.get_auth()
|
||||
self.clock = hs.get_clock()
|
||||
self.state = hs.get_state_handler()
|
||||
@ -113,8 +113,8 @@ class MessageHandler:
|
||||
self._scheduled_expiry: Optional[IDelayedCall] = None
|
||||
|
||||
if not hs.config.worker.worker_app:
|
||||
run_as_background_process(
|
||||
"_schedule_next_expiry", self.server_name, self._schedule_next_expiry
|
||||
self.hs.run_as_background_process(
|
||||
"_schedule_next_expiry", self._schedule_next_expiry
|
||||
)
|
||||
|
||||
async def get_room_data(
|
||||
@ -444,9 +444,8 @@ class MessageHandler:
|
||||
|
||||
self._scheduled_expiry = self.clock.call_later(
|
||||
delay,
|
||||
run_as_background_process,
|
||||
self.hs.run_as_background_process,
|
||||
"_expire_event",
|
||||
self.server_name,
|
||||
self._expire_event,
|
||||
event_id,
|
||||
)
|
||||
@ -548,9 +547,8 @@ class EventCreationHandler:
|
||||
and self.config.server.cleanup_extremities_with_dummy_events
|
||||
):
|
||||
self.clock.looping_call(
|
||||
lambda: run_as_background_process(
|
||||
lambda: self.hs.run_as_background_process(
|
||||
"send_dummy_events_to_fill_extremities",
|
||||
self.server_name,
|
||||
self._send_dummy_events_to_fill_extremities,
|
||||
),
|
||||
5 * 60 * 1000,
|
||||
@ -570,6 +568,7 @@ class EventCreationHandler:
|
||||
self._external_cache_joined_hosts_updates = ExpiringCache(
|
||||
cache_name="_external_cache_joined_hosts_updates",
|
||||
server_name=self.server_name,
|
||||
hs=self.hs,
|
||||
clock=self.clock,
|
||||
expiry_ms=30 * 60 * 1000,
|
||||
)
|
||||
@ -2113,9 +2112,8 @@ class EventCreationHandler:
|
||||
if event.type == EventTypes.Message:
|
||||
# We don't want to block sending messages on any presence code. This
|
||||
# matters as sometimes presence code can take a while.
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"bump_presence_active_time",
|
||||
self.server_name,
|
||||
self._bump_active_time,
|
||||
requester.user,
|
||||
requester.device_id,
|
||||
|
@ -29,7 +29,6 @@ from synapse.api.filtering import Filter
|
||||
from synapse.events.utils import SerializeEventConfig
|
||||
from synapse.handlers.worker_lock import NEW_EVENT_DURING_PURGE_LOCK_NAME
|
||||
from synapse.logging.opentracing import trace
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.rest.admin._base import assert_user_is_admin
|
||||
from synapse.streams.config import PaginationConfig
|
||||
from synapse.types import (
|
||||
@ -116,10 +115,9 @@ class PaginationHandler:
|
||||
logger.info("Setting up purge job with config: %s", job)
|
||||
|
||||
self.clock.looping_call(
|
||||
run_as_background_process,
|
||||
self.hs.run_as_background_process,
|
||||
job.interval,
|
||||
"purge_history_for_rooms_in_range",
|
||||
self.server_name,
|
||||
self.purge_history_for_rooms_in_range,
|
||||
job.shortest_max_lifetime,
|
||||
job.longest_max_lifetime,
|
||||
@ -244,9 +242,8 @@ class PaginationHandler:
|
||||
# We want to purge everything, including local events, and to run the purge in
|
||||
# the background so that it's not blocking any other operation apart from
|
||||
# other purges in the same room.
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
PURGE_HISTORY_ACTION_NAME,
|
||||
self.server_name,
|
||||
self.purge_history,
|
||||
room_id,
|
||||
token,
|
||||
@ -604,9 +601,8 @@ class PaginationHandler:
|
||||
# Otherwise, we can backfill in the background for eventual
|
||||
# consistency's sake but we don't need to block the client waiting
|
||||
# for a costly federation call and processing.
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"maybe_backfill_in_the_background",
|
||||
self.server_name,
|
||||
self.hs.get_federation_handler().maybe_backfill,
|
||||
room_id,
|
||||
curr_topo,
|
||||
|
@ -107,7 +107,6 @@ from synapse.events.presence_router import PresenceRouter
|
||||
from synapse.logging.context import run_in_background
|
||||
from synapse.metrics import SERVER_NAME_LABEL, LaterGauge
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
run_as_background_process,
|
||||
wrap_as_background_process,
|
||||
)
|
||||
from synapse.replication.http.presence import (
|
||||
@ -537,19 +536,15 @@ class WorkerPresenceHandler(BasePresenceHandler):
|
||||
self._bump_active_client = ReplicationBumpPresenceActiveTime.make_client(hs)
|
||||
self._set_state_client = ReplicationPresenceSetState.make_client(hs)
|
||||
|
||||
self._send_stop_syncing_loop = self.clock.looping_call(
|
||||
self.send_stop_syncing, UPDATE_SYNCING_USERS_MS
|
||||
)
|
||||
|
||||
hs.get_clock().add_system_event_trigger(
|
||||
"before",
|
||||
"shutdown",
|
||||
run_as_background_process,
|
||||
"generic_presence.on_shutdown",
|
||||
self.server_name,
|
||||
self._on_shutdown,
|
||||
self.clock.looping_call(self.send_stop_syncing, UPDATE_SYNCING_USERS_MS)
|
||||
|
||||
hs.register_async_shutdown_handler(
|
||||
phase="before",
|
||||
eventType="shutdown",
|
||||
shutdown_func=self._on_shutdown,
|
||||
)
|
||||
|
||||
@wrap_as_background_process("WorkerPresenceHandler._on_shutdown")
|
||||
async def _on_shutdown(self) -> None:
|
||||
if self._track_presence:
|
||||
self.hs.get_replication_command_handler().send_command(
|
||||
@ -779,9 +774,7 @@ class WorkerPresenceHandler(BasePresenceHandler):
|
||||
class PresenceHandler(BasePresenceHandler):
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
super().__init__(hs)
|
||||
self.server_name = (
|
||||
hs.hostname
|
||||
) # nb must be called this for @wrap_as_background_process
|
||||
self.server_name = hs.hostname
|
||||
self.wheel_timer: WheelTimer[str] = WheelTimer()
|
||||
self.notifier = hs.get_notifier()
|
||||
|
||||
@ -842,13 +835,10 @@ class PresenceHandler(BasePresenceHandler):
|
||||
# have not yet been persisted
|
||||
self.unpersisted_users_changes: Set[str] = set()
|
||||
|
||||
hs.get_clock().add_system_event_trigger(
|
||||
"before",
|
||||
"shutdown",
|
||||
run_as_background_process,
|
||||
"presence.on_shutdown",
|
||||
self.server_name,
|
||||
self._on_shutdown,
|
||||
hs.register_async_shutdown_handler(
|
||||
phase="before",
|
||||
eventType="shutdown",
|
||||
shutdown_func=self._on_shutdown,
|
||||
)
|
||||
|
||||
# Keeps track of the number of *ongoing* syncs on this process. While
|
||||
@ -881,7 +871,10 @@ class PresenceHandler(BasePresenceHandler):
|
||||
# The initial delay is to allow disconnected clients a chance to
|
||||
# reconnect before we treat them as offline.
|
||||
self.clock.call_later(
|
||||
30, self.clock.looping_call, self._handle_timeouts, 5000
|
||||
30,
|
||||
self.clock.looping_call,
|
||||
self._handle_timeouts,
|
||||
5000,
|
||||
)
|
||||
|
||||
# Presence information is persisted, whether or not it is being tracked
|
||||
@ -908,6 +901,7 @@ class PresenceHandler(BasePresenceHandler):
|
||||
self._event_pos = self.store.get_room_max_stream_ordering()
|
||||
self._event_processing = False
|
||||
|
||||
@wrap_as_background_process("PresenceHandler._on_shutdown")
|
||||
async def _on_shutdown(self) -> None:
|
||||
"""Gets called when shutting down. This lets us persist any updates that
|
||||
we haven't yet persisted, e.g. updates that only changes some internal
|
||||
@ -1539,8 +1533,8 @@ class PresenceHandler(BasePresenceHandler):
|
||||
finally:
|
||||
self._event_processing = False
|
||||
|
||||
run_as_background_process(
|
||||
"presence.notify_new_event", self.server_name, _process_presence
|
||||
self.hs.run_as_background_process(
|
||||
"presence.notify_new_event", _process_presence
|
||||
)
|
||||
|
||||
async def _unsafe_process(self) -> None:
|
||||
|
@ -56,8 +56,8 @@ class ProfileHandler:
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.server_name = hs.hostname # nb must be called this for @cached
|
||||
self.clock = hs.get_clock() # nb must be called this for @cached
|
||||
self.store = hs.get_datastores().main
|
||||
self.clock = hs.get_clock()
|
||||
self.hs = hs
|
||||
|
||||
self.federation = hs.get_federation_client()
|
||||
|
@ -23,7 +23,14 @@
|
||||
"""Contains functions for registering clients."""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple, TypedDict
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
TypedDict,
|
||||
)
|
||||
|
||||
from prometheus_client import Counter
|
||||
|
||||
|
@ -50,7 +50,6 @@ from synapse.handlers.state_deltas import MatchChange, StateDeltasHandler
|
||||
from synapse.handlers.worker_lock import NEW_EVENT_DURING_PURGE_LOCK_NAME
|
||||
from synapse.logging import opentracing
|
||||
from synapse.metrics import SERVER_NAME_LABEL, event_processing_positions
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.replication.http.push import ReplicationCopyPusherRestServlet
|
||||
from synapse.storage.databases.main.state_deltas import StateDelta
|
||||
from synapse.storage.invite_rule import InviteRule
|
||||
@ -2190,7 +2189,10 @@ class RoomForgetterHandler(StateDeltasHandler):
|
||||
self._notifier.add_replication_callback(self.notify_new_event)
|
||||
|
||||
# We kick this off to pick up outstanding work from before the last restart.
|
||||
self._clock.call_later(0, self.notify_new_event)
|
||||
self._clock.call_later(
|
||||
0,
|
||||
self.notify_new_event,
|
||||
)
|
||||
|
||||
def notify_new_event(self) -> None:
|
||||
"""Called when there may be more deltas to process"""
|
||||
@ -2205,9 +2207,7 @@ class RoomForgetterHandler(StateDeltasHandler):
|
||||
finally:
|
||||
self._is_processing = False
|
||||
|
||||
run_as_background_process(
|
||||
"room_forgetter.notify_new_event", self.server_name, process
|
||||
)
|
||||
self._hs.run_as_background_process("room_forgetter.notify_new_event", process)
|
||||
|
||||
async def _unsafe_process(self) -> None:
|
||||
# If self.pos is None then means we haven't fetched it from DB
|
||||
|
@ -224,7 +224,7 @@ class SsoHandler:
|
||||
)
|
||||
|
||||
# a lock on the mappings
|
||||
self._mapping_lock = Linearizer(name="sso_user_mapping", clock=hs.get_clock())
|
||||
self._mapping_lock = Linearizer(clock=hs.get_clock(), name="sso_user_mapping")
|
||||
|
||||
# a map from session id to session data
|
||||
self._username_mapping_sessions: Dict[str, UsernameMappingSession] = {}
|
||||
|
@ -33,7 +33,6 @@ from typing import (
|
||||
|
||||
from synapse.api.constants import EventContentFields, EventTypes, Membership
|
||||
from synapse.metrics import SERVER_NAME_LABEL, event_processing_positions
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.storage.databases.main.state_deltas import StateDelta
|
||||
from synapse.types import JsonDict
|
||||
from synapse.util.events import get_plain_text_topic_from_event_content
|
||||
@ -75,7 +74,10 @@ class StatsHandler:
|
||||
|
||||
# We kick this off so that we don't have to wait for a change before
|
||||
# we start populating stats
|
||||
self.clock.call_later(0, self.notify_new_event)
|
||||
self.clock.call_later(
|
||||
0,
|
||||
self.notify_new_event,
|
||||
)
|
||||
|
||||
def notify_new_event(self) -> None:
|
||||
"""Called when there may be more deltas to process"""
|
||||
@ -90,7 +92,7 @@ class StatsHandler:
|
||||
finally:
|
||||
self._is_processing = False
|
||||
|
||||
run_as_background_process("stats.notify_new_event", self.server_name, process)
|
||||
self.hs.run_as_background_process("stats.notify_new_event", process)
|
||||
|
||||
async def _unsafe_process(self) -> None:
|
||||
# If self.pos is None then means we haven't fetched it from DB
|
||||
|
@ -323,6 +323,7 @@ class SyncHandler:
|
||||
] = ExpiringCache(
|
||||
cache_name="lazy_loaded_members_cache",
|
||||
server_name=self.server_name,
|
||||
hs=hs,
|
||||
clock=self.clock,
|
||||
max_len=0,
|
||||
expiry_ms=LAZY_LOADED_MEMBERS_CACHE_MAX_AGE,
|
||||
@ -982,6 +983,7 @@ class SyncHandler:
|
||||
logger.debug("creating LruCache for %r", cache_key)
|
||||
cache = LruCache(
|
||||
max_size=LAZY_LOADED_MEMBERS_CACHE_MAX_SIZE,
|
||||
clock=self.clock,
|
||||
server_name=self.server_name,
|
||||
)
|
||||
self.lazy_loaded_members_cache[cache_key] = cache
|
||||
|
@ -28,7 +28,6 @@ from synapse.api.constants import EduTypes
|
||||
from synapse.api.errors import AuthError, ShadowBanError, SynapseError
|
||||
from synapse.appservice import ApplicationService
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
run_as_background_process,
|
||||
wrap_as_background_process,
|
||||
)
|
||||
from synapse.replication.tcp.streams import TypingStream
|
||||
@ -78,11 +77,10 @@ class FollowerTypingHandler:
|
||||
"""
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.hs = hs # nb must be called this for @wrap_as_background_process
|
||||
self.store = hs.get_datastores().main
|
||||
self._storage_controllers = hs.get_storage_controllers()
|
||||
self.server_name = (
|
||||
hs.hostname
|
||||
) # nb must be called this for @wrap_as_background_process
|
||||
self.server_name = hs.hostname
|
||||
self.clock = hs.get_clock()
|
||||
self.is_mine_id = hs.is_mine_id
|
||||
self.is_mine_server_name = hs.is_mine_server_name
|
||||
@ -144,9 +142,8 @@ class FollowerTypingHandler:
|
||||
if self.federation and self.is_mine_id(member.user_id):
|
||||
last_fed_poke = self._member_last_federation_poke.get(member, None)
|
||||
if not last_fed_poke or last_fed_poke + FEDERATION_PING_INTERVAL <= now:
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"typing._push_remote",
|
||||
self.server_name,
|
||||
self._push_remote,
|
||||
member=member,
|
||||
typing=True,
|
||||
@ -220,9 +217,8 @@ class FollowerTypingHandler:
|
||||
self._rooms_updated.add(row.room_id)
|
||||
|
||||
if self.federation:
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"_send_changes_in_typing_to_remotes",
|
||||
self.server_name,
|
||||
self._send_changes_in_typing_to_remotes,
|
||||
row.room_id,
|
||||
prev_typing,
|
||||
@ -384,9 +380,8 @@ class TypingWriterHandler(FollowerTypingHandler):
|
||||
def _push_update(self, member: RoomMember, typing: bool) -> None:
|
||||
if self.hs.is_mine_id(member.user_id):
|
||||
# Only send updates for changes to our own users.
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"typing._push_remote",
|
||||
self.server_name,
|
||||
self._push_remote,
|
||||
member,
|
||||
typing,
|
||||
|
@ -36,7 +36,6 @@ from synapse.api.constants import (
|
||||
from synapse.api.errors import Codes, SynapseError
|
||||
from synapse.handlers.state_deltas import MatchChange, StateDeltasHandler
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.storage.databases.main.state_deltas import StateDelta
|
||||
from synapse.storage.databases.main.user_directory import SearchResult
|
||||
from synapse.storage.roommember import ProfileInfo
|
||||
@ -137,11 +136,15 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||
|
||||
# We kick this off so that we don't have to wait for a change before
|
||||
# we start populating the user directory
|
||||
self.clock.call_later(0, self.notify_new_event)
|
||||
self.clock.call_later(
|
||||
0,
|
||||
self.notify_new_event,
|
||||
)
|
||||
|
||||
# Kick off the profile refresh process on startup
|
||||
self._refresh_remote_profiles_call_later = self.clock.call_later(
|
||||
10, self.kick_off_remote_profile_refresh_process
|
||||
10,
|
||||
self.kick_off_remote_profile_refresh_process,
|
||||
)
|
||||
|
||||
async def search_users(
|
||||
@ -193,9 +196,7 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||
self._is_processing = False
|
||||
|
||||
self._is_processing = True
|
||||
run_as_background_process(
|
||||
"user_directory.notify_new_event", self.server_name, process
|
||||
)
|
||||
self._hs.run_as_background_process("user_directory.notify_new_event", process)
|
||||
|
||||
async def handle_local_profile_change(
|
||||
self, user_id: str, profile: ProfileInfo
|
||||
@ -609,8 +610,8 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||
self._is_refreshing_remote_profiles = False
|
||||
|
||||
self._is_refreshing_remote_profiles = True
|
||||
run_as_background_process(
|
||||
"user_directory.refresh_remote_profiles", self.server_name, process
|
||||
self._hs.run_as_background_process(
|
||||
"user_directory.refresh_remote_profiles", process
|
||||
)
|
||||
|
||||
async def _unsafe_refresh_remote_profiles(self) -> None:
|
||||
@ -655,8 +656,9 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||
if not users:
|
||||
return
|
||||
_, _, next_try_at_ts = users[0]
|
||||
delay = ((next_try_at_ts - self.clock.time_msec()) // 1000) + 2
|
||||
self._refresh_remote_profiles_call_later = self.clock.call_later(
|
||||
((next_try_at_ts - self.clock.time_msec()) // 1000) + 2,
|
||||
delay,
|
||||
self.kick_off_remote_profile_refresh_process,
|
||||
)
|
||||
|
||||
@ -692,9 +694,8 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||
self._is_refreshing_remote_profiles_for_servers.remove(server_name)
|
||||
|
||||
self._is_refreshing_remote_profiles_for_servers.add(server_name)
|
||||
run_as_background_process(
|
||||
self._hs.run_as_background_process(
|
||||
"user_directory.refresh_remote_profiles_for_remote_server",
|
||||
self.server_name,
|
||||
process,
|
||||
)
|
||||
|
||||
|
@ -37,13 +37,13 @@ from weakref import WeakSet
|
||||
import attr
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.interfaces import IReactorTime
|
||||
|
||||
from synapse.logging.context import PreserveLoggingContext
|
||||
from synapse.logging.opentracing import start_active_span
|
||||
from synapse.metrics.background_process_metrics import wrap_as_background_process
|
||||
from synapse.storage.databases.main.lock import Lock, LockStore
|
||||
from synapse.util.async_helpers import timeout_deferred
|
||||
from synapse.util.clock import Clock
|
||||
from synapse.util.constants import ONE_MINUTE_SECONDS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -66,10 +66,8 @@ class WorkerLocksHandler:
|
||||
"""
|
||||
|
||||
def __init__(self, hs: "HomeServer") -> None:
|
||||
self.server_name = (
|
||||
hs.hostname
|
||||
) # nb must be called this for @wrap_as_background_process
|
||||
self._reactor = hs.get_reactor()
|
||||
self.hs = hs # nb must be called this for @wrap_as_background_process
|
||||
self._clock = hs.get_clock()
|
||||
self._store = hs.get_datastores().main
|
||||
self._clock = hs.get_clock()
|
||||
self._notifier = hs.get_notifier()
|
||||
@ -98,7 +96,7 @@ class WorkerLocksHandler:
|
||||
"""
|
||||
|
||||
lock = WaitingLock(
|
||||
reactor=self._reactor,
|
||||
clock=self._clock,
|
||||
store=self._store,
|
||||
handler=self,
|
||||
lock_name=lock_name,
|
||||
@ -129,7 +127,7 @@ class WorkerLocksHandler:
|
||||
"""
|
||||
|
||||
lock = WaitingLock(
|
||||
reactor=self._reactor,
|
||||
clock=self._clock,
|
||||
store=self._store,
|
||||
handler=self,
|
||||
lock_name=lock_name,
|
||||
@ -160,7 +158,7 @@ class WorkerLocksHandler:
|
||||
lock = WaitingMultiLock(
|
||||
lock_names=lock_names,
|
||||
write=write,
|
||||
reactor=self._reactor,
|
||||
clock=self._clock,
|
||||
store=self._store,
|
||||
handler=self,
|
||||
)
|
||||
@ -197,7 +195,11 @@ class WorkerLocksHandler:
|
||||
if not deferred.called:
|
||||
deferred.callback(None)
|
||||
|
||||
self._clock.call_later(0, _wake_all_locks, locks)
|
||||
self._clock.call_later(
|
||||
0,
|
||||
_wake_all_locks,
|
||||
locks,
|
||||
)
|
||||
|
||||
@wrap_as_background_process("_cleanup_locks")
|
||||
async def _cleanup_locks(self) -> None:
|
||||
@ -207,7 +209,7 @@ class WorkerLocksHandler:
|
||||
|
||||
@attr.s(auto_attribs=True, eq=False)
|
||||
class WaitingLock:
|
||||
reactor: IReactorTime
|
||||
clock: Clock
|
||||
store: LockStore
|
||||
handler: WorkerLocksHandler
|
||||
lock_name: str
|
||||
@ -246,10 +248,11 @@ class WaitingLock:
|
||||
# periodically wake up in case the lock was released but we
|
||||
# weren't notified.
|
||||
with PreserveLoggingContext():
|
||||
timeout = self._get_next_retry_interval()
|
||||
await timeout_deferred(
|
||||
deferred=self.deferred,
|
||||
timeout=self._get_next_retry_interval(),
|
||||
reactor=self.reactor,
|
||||
timeout=timeout,
|
||||
clock=self.clock,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@ -290,7 +293,7 @@ class WaitingMultiLock:
|
||||
|
||||
write: bool
|
||||
|
||||
reactor: IReactorTime
|
||||
clock: Clock
|
||||
store: LockStore
|
||||
handler: WorkerLocksHandler
|
||||
|
||||
@ -323,10 +326,11 @@ class WaitingMultiLock:
|
||||
# periodically wake up in case the lock was released but we
|
||||
# weren't notified.
|
||||
with PreserveLoggingContext():
|
||||
timeout = self._get_next_retry_interval()
|
||||
await timeout_deferred(
|
||||
deferred=self.deferred,
|
||||
timeout=self._get_next_retry_interval(),
|
||||
reactor=self.reactor,
|
||||
timeout=timeout,
|
||||
clock=self.clock,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
@ -54,7 +54,6 @@ from twisted.internet.interfaces import (
|
||||
IOpenSSLContextFactory,
|
||||
IReactorCore,
|
||||
IReactorPluggableNameResolver,
|
||||
IReactorTime,
|
||||
IResolutionReceiver,
|
||||
ITCPTransport,
|
||||
)
|
||||
@ -88,6 +87,7 @@ from synapse.logging.opentracing import set_tag, start_active_span, tags
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.types import ISynapseReactor, StrSequence
|
||||
from synapse.util.async_helpers import timeout_deferred
|
||||
from synapse.util.clock import Clock
|
||||
from synapse.util.json import json_decoder
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -165,16 +165,17 @@ def _is_ip_blocked(
|
||||
_EPSILON = 0.00000001
|
||||
|
||||
|
||||
def _make_scheduler(
|
||||
reactor: IReactorTime,
|
||||
) -> Callable[[Callable[[], object]], IDelayedCall]:
|
||||
def _make_scheduler(clock: Clock) -> Callable[[Callable[[], object]], IDelayedCall]:
|
||||
"""Makes a schedular suitable for a Cooperator using the given reactor.
|
||||
|
||||
(This is effectively just a copy from `twisted.internet.task`)
|
||||
"""
|
||||
|
||||
def _scheduler(x: Callable[[], object]) -> IDelayedCall:
|
||||
return reactor.callLater(_EPSILON, x)
|
||||
return clock.call_later(
|
||||
_EPSILON,
|
||||
x,
|
||||
)
|
||||
|
||||
return _scheduler
|
||||
|
||||
@ -367,7 +368,7 @@ class BaseHttpClient:
|
||||
|
||||
# We use this for our body producers to ensure that they use the correct
|
||||
# reactor.
|
||||
self._cooperator = Cooperator(scheduler=_make_scheduler(hs.get_reactor()))
|
||||
self._cooperator = Cooperator(scheduler=_make_scheduler(hs.get_clock()))
|
||||
|
||||
async def request(
|
||||
self,
|
||||
@ -436,9 +437,9 @@ class BaseHttpClient:
|
||||
# we use our own timeout mechanism rather than treq's as a workaround
|
||||
# for https://twistedmatrix.com/trac/ticket/9534.
|
||||
request_deferred = timeout_deferred(
|
||||
request_deferred,
|
||||
60,
|
||||
self.hs.get_reactor(),
|
||||
deferred=request_deferred,
|
||||
timeout=60,
|
||||
clock=self.hs.get_clock(),
|
||||
)
|
||||
|
||||
# turn timeouts into RequestTimedOutErrors
|
||||
@ -763,7 +764,11 @@ class BaseHttpClient:
|
||||
d = read_body_with_max_size(response, output_stream, max_size)
|
||||
|
||||
# Ensure that the body is not read forever.
|
||||
d = timeout_deferred(d, 30, self.hs.get_reactor())
|
||||
d = timeout_deferred(
|
||||
deferred=d,
|
||||
timeout=30,
|
||||
clock=self.hs.get_clock(),
|
||||
)
|
||||
|
||||
length = await make_deferred_yieldable(d)
|
||||
except BodyExceededMaxSize:
|
||||
@ -957,9 +962,9 @@ class ReplicationClient(BaseHttpClient):
|
||||
# for https://twistedmatrix.com/trac/ticket/9534.
|
||||
# (Updated url https://github.com/twisted/twisted/issues/9534)
|
||||
request_deferred = timeout_deferred(
|
||||
request_deferred,
|
||||
60,
|
||||
self.hs.get_reactor(),
|
||||
deferred=request_deferred,
|
||||
timeout=60,
|
||||
clock=self.hs.get_clock(),
|
||||
)
|
||||
|
||||
# turn timeouts into RequestTimedOutErrors
|
||||
|
@ -67,6 +67,9 @@ class MatrixFederationAgent:
|
||||
Args:
|
||||
reactor: twisted reactor to use for underlying requests
|
||||
|
||||
clock: Internal `HomeServer` clock used to track delayed and looping calls.
|
||||
Should be obtained from `hs.get_clock()`.
|
||||
|
||||
tls_client_options_factory:
|
||||
factory to use for fetching client tls options, or none to disable TLS.
|
||||
|
||||
@ -97,6 +100,7 @@ class MatrixFederationAgent:
|
||||
*,
|
||||
server_name: str,
|
||||
reactor: ISynapseReactor,
|
||||
clock: Clock,
|
||||
tls_client_options_factory: Optional[FederationPolicyForHTTPS],
|
||||
user_agent: bytes,
|
||||
ip_allowlist: Optional[IPSet],
|
||||
@ -109,6 +113,7 @@ class MatrixFederationAgent:
|
||||
Args:
|
||||
server_name: Our homeserver name (used to label metrics) (`hs.hostname`).
|
||||
reactor
|
||||
clock: Should be the `hs` clock from `hs.get_clock()`
|
||||
tls_client_options_factory
|
||||
user_agent
|
||||
ip_allowlist
|
||||
@ -124,7 +129,6 @@ class MatrixFederationAgent:
|
||||
# addresses, to prevent DNS rebinding.
|
||||
reactor = BlocklistingReactorWrapper(reactor, ip_allowlist, ip_blocklist)
|
||||
|
||||
self._clock = Clock(reactor, server_name=server_name)
|
||||
self._pool = HTTPConnectionPool(reactor)
|
||||
self._pool.retryAutomatically = False
|
||||
self._pool.maxPersistentPerHost = 5
|
||||
@ -147,6 +151,7 @@ class MatrixFederationAgent:
|
||||
_well_known_resolver = WellKnownResolver(
|
||||
server_name=server_name,
|
||||
reactor=reactor,
|
||||
clock=clock,
|
||||
agent=BlocklistingAgentWrapper(
|
||||
ProxyAgent(
|
||||
reactor=reactor,
|
||||
|
@ -90,6 +90,7 @@ class WellKnownResolver:
|
||||
self,
|
||||
server_name: str,
|
||||
reactor: ISynapseThreadlessReactor,
|
||||
clock: Clock,
|
||||
agent: IAgent,
|
||||
user_agent: bytes,
|
||||
well_known_cache: Optional[TTLCache[bytes, Optional[bytes]]] = None,
|
||||
@ -99,6 +100,7 @@ class WellKnownResolver:
|
||||
Args:
|
||||
server_name: Our homeserver name (used to label metrics) (`hs.hostname`).
|
||||
reactor
|
||||
clock: Should be the `hs` clock from `hs.get_clock()`
|
||||
agent
|
||||
user_agent
|
||||
well_known_cache
|
||||
@ -107,7 +109,7 @@ class WellKnownResolver:
|
||||
|
||||
self.server_name = server_name
|
||||
self._reactor = reactor
|
||||
self._clock = Clock(reactor, server_name=server_name)
|
||||
self._clock = clock
|
||||
|
||||
if well_known_cache is None:
|
||||
well_known_cache = TTLCache(
|
||||
|
@ -90,6 +90,7 @@ from synapse.logging.opentracing import set_tag, start_active_span, tags
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.types import JsonDict
|
||||
from synapse.util.async_helpers import AwakenableSleeper, Linearizer, timeout_deferred
|
||||
from synapse.util.clock import Clock
|
||||
from synapse.util.json import json_decoder
|
||||
from synapse.util.metrics import Measure
|
||||
from synapse.util.stringutils import parse_and_validate_server_name
|
||||
@ -270,6 +271,7 @@ class LegacyJsonSendParser(_BaseJsonParser[Tuple[int, JsonDict]]):
|
||||
|
||||
|
||||
async def _handle_response(
|
||||
clock: Clock,
|
||||
reactor: IReactorTime,
|
||||
timeout_sec: float,
|
||||
request: MatrixFederationRequest,
|
||||
@ -299,7 +301,11 @@ async def _handle_response(
|
||||
check_content_type_is(response.headers, parser.CONTENT_TYPE)
|
||||
|
||||
d = read_body_with_max_size(response, parser, max_response_size)
|
||||
d = timeout_deferred(d, timeout=timeout_sec, reactor=reactor)
|
||||
d = timeout_deferred(
|
||||
deferred=d,
|
||||
timeout=timeout_sec,
|
||||
clock=clock,
|
||||
)
|
||||
|
||||
length = await make_deferred_yieldable(d)
|
||||
|
||||
@ -411,6 +417,7 @@ class MatrixFederationHttpClient:
|
||||
self.server_name = hs.hostname
|
||||
|
||||
self.reactor = hs.get_reactor()
|
||||
self.clock = hs.get_clock()
|
||||
|
||||
user_agent = hs.version_string
|
||||
if hs.config.server.user_agent_suffix:
|
||||
@ -424,6 +431,7 @@ class MatrixFederationHttpClient:
|
||||
federation_agent: IAgent = MatrixFederationAgent(
|
||||
server_name=self.server_name,
|
||||
reactor=self.reactor,
|
||||
clock=self.clock,
|
||||
tls_client_options_factory=tls_client_options_factory,
|
||||
user_agent=user_agent.encode("ascii"),
|
||||
ip_allowlist=hs.config.server.federation_ip_range_allowlist,
|
||||
@ -457,7 +465,6 @@ class MatrixFederationHttpClient:
|
||||
ip_blocklist=hs.config.server.federation_ip_range_blocklist,
|
||||
)
|
||||
|
||||
self.clock = hs.get_clock()
|
||||
self._store = hs.get_datastores().main
|
||||
self.version_string_bytes = hs.version_string.encode("ascii")
|
||||
self.default_timeout_seconds = hs.config.federation.client_timeout_ms / 1000
|
||||
@ -470,9 +477,9 @@ class MatrixFederationHttpClient:
|
||||
self.max_long_retries = hs.config.federation.max_long_retries
|
||||
self.max_short_retries = hs.config.federation.max_short_retries
|
||||
|
||||
self._cooperator = Cooperator(scheduler=_make_scheduler(self.reactor))
|
||||
self._cooperator = Cooperator(scheduler=_make_scheduler(self.clock))
|
||||
|
||||
self._sleeper = AwakenableSleeper(self.reactor)
|
||||
self._sleeper = AwakenableSleeper(self.clock)
|
||||
|
||||
self._simple_http_client = SimpleHttpClient(
|
||||
hs,
|
||||
@ -484,6 +491,10 @@ class MatrixFederationHttpClient:
|
||||
self.remote_download_linearizer = Linearizer(
|
||||
name="remote_download_linearizer", max_count=6, clock=self.clock
|
||||
)
|
||||
self._is_shutdown = False
|
||||
|
||||
def shutdown(self) -> None:
|
||||
self._is_shutdown = True
|
||||
|
||||
def wake_destination(self, destination: str) -> None:
|
||||
"""Called when the remote server may have come back online."""
|
||||
@ -629,6 +640,7 @@ class MatrixFederationHttpClient:
|
||||
limiter = await synapse.util.retryutils.get_retry_limiter(
|
||||
destination=request.destination,
|
||||
our_server_name=self.server_name,
|
||||
hs=self.hs,
|
||||
clock=self.clock,
|
||||
store=self._store,
|
||||
backoff_on_404=backoff_on_404,
|
||||
@ -675,7 +687,7 @@ class MatrixFederationHttpClient:
|
||||
(b"", b"", path_bytes, None, query_bytes, b"")
|
||||
)
|
||||
|
||||
while True:
|
||||
while not self._is_shutdown:
|
||||
try:
|
||||
json = request.get_json()
|
||||
if json:
|
||||
@ -733,9 +745,9 @@ class MatrixFederationHttpClient:
|
||||
bodyProducer=producer,
|
||||
)
|
||||
request_deferred = timeout_deferred(
|
||||
request_deferred,
|
||||
deferred=request_deferred,
|
||||
timeout=_sec_timeout,
|
||||
reactor=self.reactor,
|
||||
clock=self.clock,
|
||||
)
|
||||
|
||||
response = await make_deferred_yieldable(request_deferred)
|
||||
@ -793,7 +805,9 @@ class MatrixFederationHttpClient:
|
||||
# Update transactions table?
|
||||
d = treq.content(response)
|
||||
d = timeout_deferred(
|
||||
d, timeout=_sec_timeout, reactor=self.reactor
|
||||
deferred=d,
|
||||
timeout=_sec_timeout,
|
||||
clock=self.clock,
|
||||
)
|
||||
|
||||
try:
|
||||
@ -862,6 +876,15 @@ class MatrixFederationHttpClient:
|
||||
delay_seconds,
|
||||
)
|
||||
|
||||
if self._is_shutdown:
|
||||
# Immediately fail sending the request instead of starting a
|
||||
# potentially long sleep after the server has requested
|
||||
# shutdown.
|
||||
# This is the code path followed when the
|
||||
# `federation_transaction_transmission_loop` has been
|
||||
# cancelled.
|
||||
raise
|
||||
|
||||
# Sleep for the calculated delay, or wake up immediately
|
||||
# if we get notified that the server is back up.
|
||||
await self._sleeper.sleep(
|
||||
@ -1074,6 +1097,7 @@ class MatrixFederationHttpClient:
|
||||
parser = cast(ByteParser[T], JsonParser())
|
||||
|
||||
body = await _handle_response(
|
||||
self.clock,
|
||||
self.reactor,
|
||||
_sec_timeout,
|
||||
request,
|
||||
@ -1152,7 +1176,13 @@ class MatrixFederationHttpClient:
|
||||
_sec_timeout = self.default_timeout_seconds
|
||||
|
||||
body = await _handle_response(
|
||||
self.reactor, _sec_timeout, request, response, start_ms, parser=JsonParser()
|
||||
self.clock,
|
||||
self.reactor,
|
||||
_sec_timeout,
|
||||
request,
|
||||
response,
|
||||
start_ms,
|
||||
parser=JsonParser(),
|
||||
)
|
||||
return body
|
||||
|
||||
@ -1358,6 +1388,7 @@ class MatrixFederationHttpClient:
|
||||
parser = cast(ByteParser[T], JsonParser())
|
||||
|
||||
body = await _handle_response(
|
||||
self.clock,
|
||||
self.reactor,
|
||||
_sec_timeout,
|
||||
request,
|
||||
@ -1431,7 +1462,13 @@ class MatrixFederationHttpClient:
|
||||
_sec_timeout = self.default_timeout_seconds
|
||||
|
||||
body = await _handle_response(
|
||||
self.reactor, _sec_timeout, request, response, start_ms, parser=JsonParser()
|
||||
self.clock,
|
||||
self.reactor,
|
||||
_sec_timeout,
|
||||
request,
|
||||
response,
|
||||
start_ms,
|
||||
parser=JsonParser(),
|
||||
)
|
||||
return body
|
||||
|
||||
|
@ -161,12 +161,12 @@ class ProxyResource(_AsyncResource):
|
||||
bodyProducer=QuieterFileBodyProducer(request.content),
|
||||
)
|
||||
request_deferred = timeout_deferred(
|
||||
request_deferred,
|
||||
deferred=request_deferred,
|
||||
# This should be set longer than the timeout in `MatrixFederationHttpClient`
|
||||
# so that it has enough time to complete and pass us the data before we give
|
||||
# up.
|
||||
timeout=90,
|
||||
reactor=self.reactor,
|
||||
clock=self._clock,
|
||||
)
|
||||
|
||||
response = await make_deferred_yieldable(request_deferred)
|
||||
|
@ -420,7 +420,14 @@ class DirectServeJsonResource(_AsyncResource):
|
||||
"""
|
||||
|
||||
if clock is None:
|
||||
clock = Clock(
|
||||
# Ideally we wouldn't ignore the linter error here and instead enforce a
|
||||
# required `Clock` be passed into the `__init__` function.
|
||||
# However, this would change the function signature which is currently being
|
||||
# exported to the module api. Since we don't want to break that api, we have
|
||||
# to settle with ignoring the linter error here.
|
||||
# As of the time of writing this, all Synapse internal usages of
|
||||
# `DirectServeJsonResource` pass in the existing homeserver clock instance.
|
||||
clock = Clock( # type: ignore[multiple-internal-clocks]
|
||||
cast(ISynapseThreadlessReactor, reactor),
|
||||
server_name="synapse_module_running_from_unknown_server",
|
||||
)
|
||||
@ -608,7 +615,14 @@ class DirectServeHtmlResource(_AsyncResource):
|
||||
Only optional for the Module API.
|
||||
"""
|
||||
if clock is None:
|
||||
clock = Clock(
|
||||
# Ideally we wouldn't ignore the linter error here and instead enforce a
|
||||
# required `Clock` be passed into the `__init__` function.
|
||||
# However, this would change the function signature which is currently being
|
||||
# exported to the module api. Since we don't want to break that api, we have
|
||||
# to settle with ignoring the linter error here.
|
||||
# As of the time of writing this, all Synapse internal usages of
|
||||
# `DirectServeHtmlResource` pass in the existing homeserver clock instance.
|
||||
clock = Clock( # type: ignore[multiple-internal-clocks]
|
||||
cast(ISynapseThreadlessReactor, reactor),
|
||||
server_name="synapse_module_running_from_unknown_server",
|
||||
)
|
||||
|
@ -22,7 +22,7 @@ import contextlib
|
||||
import logging
|
||||
import time
|
||||
from http import HTTPStatus
|
||||
from typing import TYPE_CHECKING, Any, Generator, Optional, Tuple, Union
|
||||
from typing import TYPE_CHECKING, Any, Generator, List, Optional, Tuple, Union
|
||||
|
||||
import attr
|
||||
from zope.interface import implementer
|
||||
@ -30,6 +30,7 @@ from zope.interface import implementer
|
||||
from twisted.internet.address import UNIXAddress
|
||||
from twisted.internet.defer import Deferred
|
||||
from twisted.internet.interfaces import IAddress
|
||||
from twisted.internet.protocol import Protocol
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.web.http import HTTPChannel
|
||||
from twisted.web.resource import IResource, Resource
|
||||
@ -660,6 +661,70 @@ class _XForwardedForAddress:
|
||||
host: str
|
||||
|
||||
|
||||
class SynapseProtocol(HTTPChannel):
|
||||
"""
|
||||
Synapse-specific twisted http Protocol.
|
||||
|
||||
This is a small wrapper around the twisted HTTPChannel so we can track active
|
||||
connections in order to close any outstanding connections on shutdown.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
site: "SynapseSite",
|
||||
our_server_name: str,
|
||||
max_request_body_size: int,
|
||||
request_id_header: Optional[str],
|
||||
request_class: type,
|
||||
):
|
||||
super().__init__()
|
||||
self.factory: SynapseSite = site
|
||||
self.site = site
|
||||
self.our_server_name = our_server_name
|
||||
self.max_request_body_size = max_request_body_size
|
||||
self.request_id_header = request_id_header
|
||||
self.request_class = request_class
|
||||
|
||||
def connectionMade(self) -> None:
|
||||
"""
|
||||
Called when a connection is made.
|
||||
|
||||
This may be considered the initializer of the protocol, because
|
||||
it is called when the connection is completed.
|
||||
|
||||
Add the connection to the factory's connection list when it's established.
|
||||
"""
|
||||
super().connectionMade()
|
||||
self.factory.addConnection(self)
|
||||
|
||||
def connectionLost(self, reason: Failure) -> None: # type: ignore[override]
|
||||
"""
|
||||
Called when the connection is shut down.
|
||||
|
||||
Clear any circular references here, and any external references to this
|
||||
Protocol. The connection has been closed. In our case, we need to remove the
|
||||
connection from the factory's connection list, when it's lost.
|
||||
"""
|
||||
super().connectionLost(reason)
|
||||
self.factory.removeConnection(self)
|
||||
|
||||
def requestFactory(self, http_channel: HTTPChannel, queued: bool) -> SynapseRequest: # type: ignore[override]
|
||||
"""
|
||||
A callable used to build `twisted.web.iweb.IRequest` objects.
|
||||
|
||||
Use our own custom SynapseRequest type instead of the regular
|
||||
twisted.web.server.Request.
|
||||
"""
|
||||
return self.request_class(
|
||||
self,
|
||||
self.factory,
|
||||
our_server_name=self.our_server_name,
|
||||
max_request_body_size=self.max_request_body_size,
|
||||
queued=queued,
|
||||
request_id_header=self.request_id_header,
|
||||
)
|
||||
|
||||
|
||||
class SynapseSite(ProxySite):
|
||||
"""
|
||||
Synapse-specific twisted http Site
|
||||
@ -710,23 +775,44 @@ class SynapseSite(ProxySite):
|
||||
|
||||
assert config.http_options is not None
|
||||
proxied = config.http_options.x_forwarded
|
||||
request_class = XForwardedForRequest if proxied else SynapseRequest
|
||||
self.request_class = XForwardedForRequest if proxied else SynapseRequest
|
||||
|
||||
request_id_header = config.http_options.request_id_header
|
||||
self.request_id_header = config.http_options.request_id_header
|
||||
self.max_request_body_size = max_request_body_size
|
||||
|
||||
def request_factory(channel: HTTPChannel, queued: bool) -> Request:
|
||||
return request_class(
|
||||
channel,
|
||||
self,
|
||||
our_server_name=self.server_name,
|
||||
max_request_body_size=max_request_body_size,
|
||||
queued=queued,
|
||||
request_id_header=request_id_header,
|
||||
)
|
||||
|
||||
self.requestFactory = request_factory # type: ignore
|
||||
self.access_logger = logging.getLogger(logger_name)
|
||||
self.server_version_string = server_version_string.encode("ascii")
|
||||
self.connections: List[Protocol] = []
|
||||
|
||||
def buildProtocol(self, addr: IAddress) -> SynapseProtocol:
|
||||
protocol = SynapseProtocol(
|
||||
self,
|
||||
self.server_name,
|
||||
self.max_request_body_size,
|
||||
self.request_id_header,
|
||||
self.request_class,
|
||||
)
|
||||
return protocol
|
||||
|
||||
def addConnection(self, protocol: Protocol) -> None:
|
||||
self.connections.append(protocol)
|
||||
|
||||
def removeConnection(self, protocol: Protocol) -> None:
|
||||
if protocol in self.connections:
|
||||
self.connections.remove(protocol)
|
||||
|
||||
def stopFactory(self) -> None:
|
||||
super().stopFactory()
|
||||
|
||||
# Shutdown any connections which are still active.
|
||||
# These can be long lived HTTP connections which wouldn't normally be closed
|
||||
# when calling `shutdown` on the respective `Port`.
|
||||
# Closing the connections here is required for us to fully shutdown the
|
||||
# `SynapseHomeServer` in order for it to be garbage collected.
|
||||
for protocol in self.connections[:]:
|
||||
if protocol.transport is not None:
|
||||
protocol.transport.loseConnection()
|
||||
self.connections.clear()
|
||||
|
||||
def log(self, request: SynapseRequest) -> None: # type: ignore[override]
|
||||
pass
|
||||
|
@ -704,6 +704,7 @@ class ThreadedFileSender:
|
||||
|
||||
def __init__(self, hs: "HomeServer") -> None:
|
||||
self.reactor = hs.get_reactor()
|
||||
self.clock = hs.get_clock()
|
||||
self.thread_pool = hs.get_media_sender_thread_pool()
|
||||
|
||||
self.file: Optional[BinaryIO] = None
|
||||
@ -712,7 +713,7 @@ class ThreadedFileSender:
|
||||
|
||||
# Signals if the thread should keep reading/sending data. Set means
|
||||
# continue, clear means pause.
|
||||
self.wakeup_event = DeferredEvent(self.reactor)
|
||||
self.wakeup_event = DeferredEvent(self.clock)
|
||||
|
||||
# Signals if the thread should terminate, e.g. because the consumer has
|
||||
# gone away.
|
||||
|
@ -67,7 +67,6 @@ from synapse.media.media_storage import (
|
||||
from synapse.media.storage_provider import StorageProviderWrapper
|
||||
from synapse.media.thumbnailer import Thumbnailer, ThumbnailError
|
||||
from synapse.media.url_previewer import UrlPreviewer
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.storage.databases.main.media_repository import LocalMedia, RemoteMedia
|
||||
from synapse.types import UserID
|
||||
from synapse.util.async_helpers import Linearizer
|
||||
@ -187,16 +186,14 @@ class MediaRepository:
|
||||
self.media_repository_callbacks = hs.get_module_api_callbacks().media_repository
|
||||
|
||||
def _start_update_recently_accessed(self) -> Deferred:
|
||||
return run_as_background_process(
|
||||
return self.hs.run_as_background_process(
|
||||
"update_recently_accessed_media",
|
||||
self.server_name,
|
||||
self._update_recently_accessed,
|
||||
)
|
||||
|
||||
def _start_apply_media_retention_rules(self) -> Deferred:
|
||||
return run_as_background_process(
|
||||
return self.hs.run_as_background_process(
|
||||
"apply_media_retention_rules",
|
||||
self.server_name,
|
||||
self._apply_media_retention_rules,
|
||||
)
|
||||
|
||||
|
@ -44,7 +44,6 @@ from synapse.media._base import FileInfo, get_filename_from_headers
|
||||
from synapse.media.media_storage import MediaStorage, SHA256TransparentIOWriter
|
||||
from synapse.media.oembed import OEmbedProvider
|
||||
from synapse.media.preview_html import decode_body, parse_html_to_open_graph
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.types import JsonDict, UserID
|
||||
from synapse.util.async_helpers import ObservableDeferred
|
||||
from synapse.util.caches.expiringcache import ExpiringCache
|
||||
@ -167,6 +166,7 @@ class UrlPreviewer:
|
||||
media_storage: MediaStorage,
|
||||
):
|
||||
self.clock = hs.get_clock()
|
||||
self.hs = hs
|
||||
self.filepaths = media_repo.filepaths
|
||||
self.max_spider_size = hs.config.media.max_spider_size
|
||||
self.server_name = hs.hostname
|
||||
@ -201,15 +201,14 @@ class UrlPreviewer:
|
||||
self._cache: ExpiringCache[str, ObservableDeferred] = ExpiringCache(
|
||||
cache_name="url_previews",
|
||||
server_name=self.server_name,
|
||||
hs=self.hs,
|
||||
clock=self.clock,
|
||||
# don't spider URLs more often than once an hour
|
||||
expiry_ms=ONE_HOUR,
|
||||
)
|
||||
|
||||
if self._worker_run_media_background_jobs:
|
||||
self._cleaner_loop = self.clock.looping_call(
|
||||
self._start_expire_url_cache_data, 10 * 1000
|
||||
)
|
||||
self.clock.looping_call(self._start_expire_url_cache_data, 10 * 1000)
|
||||
|
||||
async def preview(self, url: str, user: UserID, ts: int) -> bytes:
|
||||
# the in-memory cache:
|
||||
@ -739,8 +738,8 @@ class UrlPreviewer:
|
||||
return open_graph_result, oembed_response.author_name, expiration_ms
|
||||
|
||||
def _start_expire_url_cache_data(self) -> Deferred:
|
||||
return run_as_background_process(
|
||||
"expire_url_cache_data", self.server_name, self._expire_url_cache_data
|
||||
return self.hs.run_as_background_process(
|
||||
"expire_url_cache_data", self._expire_url_cache_data
|
||||
)
|
||||
|
||||
async def _expire_url_cache_data(self) -> None:
|
||||
|
@ -138,7 +138,9 @@ def install_gc_manager() -> None:
|
||||
gc_time.labels(i).observe(end - start)
|
||||
gc_unreachable.labels(i).set(unreachable)
|
||||
|
||||
gc_task = task.LoopingCall(_maybe_gc)
|
||||
# We can ignore the lint here since this looping call does not hold a `HomeServer`
|
||||
# reference so can be cleaned up by other means on shutdown.
|
||||
gc_task = task.LoopingCall(_maybe_gc) # type: ignore[prefer-synapse-clock-looping-call]
|
||||
gc_task.start(0.1)
|
||||
|
||||
|
||||
|
@ -66,6 +66,8 @@ if TYPE_CHECKING:
|
||||
# Old versions don't have `LiteralString`
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
from synapse.server import HomeServer
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -397,11 +399,11 @@ def run_as_background_process(
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
class HasServerName(Protocol):
|
||||
server_name: str
|
||||
class HasHomeServer(Protocol):
|
||||
hs: "HomeServer"
|
||||
"""
|
||||
The homeserver name that this cache is associated with (used to label the metric)
|
||||
(`hs.hostname`).
|
||||
The homeserver that this cache is associated with (used to label the metric and
|
||||
track backgroun processes for clean shutdown).
|
||||
"""
|
||||
|
||||
|
||||
@ -431,27 +433,22 @@ def wrap_as_background_process(
|
||||
"""
|
||||
|
||||
def wrapper(
|
||||
func: Callable[Concatenate[HasServerName, P], Awaitable[Optional[R]]],
|
||||
func: Callable[Concatenate[HasHomeServer, P], Awaitable[Optional[R]]],
|
||||
) -> Callable[P, "defer.Deferred[Optional[R]]"]:
|
||||
@wraps(func)
|
||||
def wrapped_func(
|
||||
self: HasServerName, *args: P.args, **kwargs: P.kwargs
|
||||
self: HasHomeServer, *args: P.args, **kwargs: P.kwargs
|
||||
) -> "defer.Deferred[Optional[R]]":
|
||||
assert self.server_name is not None, (
|
||||
"The `server_name` attribute must be set on the object where `@wrap_as_background_process` decorator is used."
|
||||
assert self.hs is not None, (
|
||||
"The `hs` attribute must be set on the object where `@wrap_as_background_process` decorator is used."
|
||||
)
|
||||
|
||||
return run_as_background_process(
|
||||
return self.hs.run_as_background_process(
|
||||
desc,
|
||||
self.server_name,
|
||||
func,
|
||||
self,
|
||||
*args,
|
||||
# type-ignore: mypy is confusing kwargs with the bg_start_span kwarg.
|
||||
# Argument 4 to "run_as_background_process" has incompatible type
|
||||
# "**P.kwargs"; expected "bool"
|
||||
# See https://github.com/python/mypy/issues/8862
|
||||
**kwargs, # type: ignore[arg-type]
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# There are some shenanigans here, because we're decorating a method but
|
||||
|
@ -23,7 +23,6 @@ from typing import TYPE_CHECKING
|
||||
import attr
|
||||
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from synapse.server import HomeServer
|
||||
@ -52,6 +51,7 @@ class CommonUsageMetricsManager:
|
||||
self.server_name = hs.hostname
|
||||
self._store = hs.get_datastores().main
|
||||
self._clock = hs.get_clock()
|
||||
self._hs = hs
|
||||
|
||||
async def get_metrics(self) -> CommonUsageMetrics:
|
||||
"""Get the CommonUsageMetrics object. If no collection has happened yet, do it
|
||||
@ -64,16 +64,14 @@ class CommonUsageMetricsManager:
|
||||
|
||||
async def setup(self) -> None:
|
||||
"""Keep the gauges for common usage metrics up to date."""
|
||||
run_as_background_process(
|
||||
self._hs.run_as_background_process(
|
||||
desc="common_usage_metrics_update_gauges",
|
||||
server_name=self.server_name,
|
||||
func=self._update_gauges,
|
||||
)
|
||||
self._clock.looping_call(
|
||||
run_as_background_process,
|
||||
self._hs.run_as_background_process,
|
||||
5 * 60 * 1000,
|
||||
desc="common_usage_metrics_update_gauges",
|
||||
server_name=self.server_name,
|
||||
func=self._update_gauges,
|
||||
)
|
||||
|
||||
|
@ -275,7 +275,15 @@ def run_as_background_process(
|
||||
# function instead.
|
||||
stub_server_name = "synapse_module_running_from_unknown_server"
|
||||
|
||||
return _run_as_background_process(
|
||||
# Ignore the linter error here. Since this is leveraging the
|
||||
# `run_as_background_process` function directly and we don't want to break the
|
||||
# module api, we need to keep the function signature the same. This means we don't
|
||||
# have access to the running `HomeServer` and cannot track this background process
|
||||
# for cleanup during shutdown.
|
||||
# This is not an issue during runtime and is only potentially problematic if the
|
||||
# application cares about being able to garbage collect `HomeServer` instances
|
||||
# during runtime.
|
||||
return _run_as_background_process( # type: ignore[untracked-background-process]
|
||||
desc,
|
||||
stub_server_name,
|
||||
func,
|
||||
@ -1402,7 +1410,7 @@ class ModuleApi:
|
||||
|
||||
if self._hs.config.worker.run_background_tasks or run_on_all_instances:
|
||||
self._clock.looping_call(
|
||||
self.run_as_background_process,
|
||||
self._hs.run_as_background_process,
|
||||
msec,
|
||||
desc,
|
||||
lambda: maybe_awaitable(f(*args, **kwargs)),
|
||||
@ -1460,7 +1468,7 @@ class ModuleApi:
|
||||
return self._clock.call_later(
|
||||
# convert ms to seconds as needed by call_later.
|
||||
msec * 0.001,
|
||||
self.run_as_background_process,
|
||||
self._hs.run_as_background_process,
|
||||
desc,
|
||||
lambda: maybe_awaitable(f(*args, **kwargs)),
|
||||
)
|
||||
@ -1701,8 +1709,8 @@ class ModuleApi:
|
||||
Note that the returned Deferred does not follow the synapse logcontext
|
||||
rules.
|
||||
"""
|
||||
return _run_as_background_process(
|
||||
desc, self.server_name, func, *args, bg_start_span=bg_start_span, **kwargs
|
||||
return self._hs.run_as_background_process(
|
||||
desc, func, *args, bg_start_span=bg_start_span, **kwargs
|
||||
)
|
||||
|
||||
async def defer_to_thread(
|
||||
|
@ -676,9 +676,16 @@ class Notifier:
|
||||
# is a new token.
|
||||
listener = user_stream.new_listener(prev_token)
|
||||
listener = timeout_deferred(
|
||||
listener,
|
||||
(end_time - now) / 1000.0,
|
||||
self.hs.get_reactor(),
|
||||
deferred=listener,
|
||||
timeout=(end_time - now) / 1000.0,
|
||||
# We don't track these calls since they are constantly being
|
||||
# overridden by new calls to /sync and they don't hold the
|
||||
# `HomeServer` in memory on shutdown. It is safe to let them
|
||||
# timeout of their own accord after shutting down since it
|
||||
# won't delay shutdown and there won't be any adverse
|
||||
# behaviour.
|
||||
cancel_on_shutdown=False,
|
||||
clock=self.hs.get_clock(),
|
||||
)
|
||||
|
||||
log_kv(
|
||||
|
@ -25,7 +25,6 @@ from typing import TYPE_CHECKING, Dict, List, Optional
|
||||
from twisted.internet.error import AlreadyCalled, AlreadyCancelled
|
||||
from twisted.internet.interfaces import IDelayedCall
|
||||
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.push import Pusher, PusherConfig, PusherConfigException, ThrottleParams
|
||||
from synapse.push.mailer import Mailer
|
||||
from synapse.push.push_types import EmailReason
|
||||
@ -118,7 +117,7 @@ class EmailPusher(Pusher):
|
||||
if self._is_processing:
|
||||
return
|
||||
|
||||
run_as_background_process("emailpush.process", self.server_name, self._process)
|
||||
self.hs.run_as_background_process("emailpush.process", self._process)
|
||||
|
||||
def _pause_processing(self) -> None:
|
||||
"""Used by tests to temporarily pause processing of events.
|
||||
@ -228,8 +227,10 @@ class EmailPusher(Pusher):
|
||||
self.timed_call = None
|
||||
|
||||
if soonest_due_at is not None:
|
||||
self.timed_call = self.hs.get_reactor().callLater(
|
||||
self.seconds_until(soonest_due_at), self.on_timer
|
||||
delay = self.seconds_until(soonest_due_at)
|
||||
self.timed_call = self.hs.get_clock().call_later(
|
||||
delay,
|
||||
self.on_timer,
|
||||
)
|
||||
|
||||
async def save_last_stream_ordering_and_success(
|
||||
|
@ -32,7 +32,6 @@ from synapse.api.constants import EventTypes
|
||||
from synapse.events import EventBase
|
||||
from synapse.logging import opentracing
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.push import Pusher, PusherConfig, PusherConfigException
|
||||
from synapse.storage.databases.main.event_push_actions import HttpPushAction
|
||||
from synapse.types import JsonDict, JsonMapping
|
||||
@ -182,8 +181,8 @@ class HttpPusher(Pusher):
|
||||
|
||||
# We could check the receipts are actually m.read receipts here,
|
||||
# but currently that's the only type of receipt anyway...
|
||||
run_as_background_process(
|
||||
"http_pusher.on_new_receipts", self.server_name, self._update_badge
|
||||
self.hs.run_as_background_process(
|
||||
"http_pusher.on_new_receipts", self._update_badge
|
||||
)
|
||||
|
||||
async def _update_badge(self) -> None:
|
||||
@ -219,7 +218,7 @@ class HttpPusher(Pusher):
|
||||
if self.failing_since and self.timed_call and self.timed_call.active():
|
||||
return
|
||||
|
||||
run_as_background_process("httppush.process", self.server_name, self._process)
|
||||
self.hs.run_as_background_process("httppush.process", self._process)
|
||||
|
||||
async def _process(self) -> None:
|
||||
# we should never get here if we are already processing
|
||||
@ -336,8 +335,9 @@ class HttpPusher(Pusher):
|
||||
)
|
||||
else:
|
||||
logger.info("Push failed: delaying for %ds", self.backoff_delay)
|
||||
self.timed_call = self.hs.get_reactor().callLater(
|
||||
self.backoff_delay, self.on_timer
|
||||
self.timed_call = self.hs.get_clock().call_later(
|
||||
self.backoff_delay,
|
||||
self.on_timer,
|
||||
)
|
||||
self.backoff_delay = min(
|
||||
self.backoff_delay * 2, self.MAX_BACKOFF_SEC
|
||||
|
@ -27,7 +27,6 @@ from prometheus_client import Gauge
|
||||
from synapse.api.errors import Codes, SynapseError
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
run_as_background_process,
|
||||
wrap_as_background_process,
|
||||
)
|
||||
from synapse.push import Pusher, PusherConfig, PusherConfigException
|
||||
@ -70,10 +69,8 @@ class PusherPool:
|
||||
"""
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.hs = hs
|
||||
self.server_name = (
|
||||
hs.hostname
|
||||
) # nb must be called this for @wrap_as_background_process
|
||||
self.hs = hs # nb must be called this for @wrap_as_background_process
|
||||
self.server_name = hs.hostname
|
||||
self.pusher_factory = PusherFactory(hs)
|
||||
self.store = self.hs.get_datastores().main
|
||||
self.clock = self.hs.get_clock()
|
||||
@ -112,9 +109,7 @@ class PusherPool:
|
||||
if not self._should_start_pushers:
|
||||
logger.info("Not starting pushers because they are disabled in the config")
|
||||
return
|
||||
run_as_background_process(
|
||||
"start_pushers", self.server_name, self._start_pushers
|
||||
)
|
||||
self.hs.run_as_background_process("start_pushers", self._start_pushers)
|
||||
|
||||
async def add_or_update_pusher(
|
||||
self,
|
||||
|
@ -185,46 +185,6 @@ class ReplicationMultiUserDevicesResyncRestServlet(ReplicationEndpoint):
|
||||
return 200, multi_user_devices
|
||||
|
||||
|
||||
# FIXME(2025-07-22): Remove this on the next release, this will only get used
|
||||
# during rollout to Synapse 1.135 and can be removed after that release.
|
||||
class ReplicationUploadKeysForUserRestServlet(ReplicationEndpoint):
|
||||
"""Unused endpoint, kept for backwards compatibility during rollout."""
|
||||
|
||||
NAME = "upload_keys_for_user"
|
||||
PATH_ARGS = ()
|
||||
CACHE = False
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
super().__init__(hs)
|
||||
|
||||
self.e2e_keys_handler = hs.get_e2e_keys_handler()
|
||||
self.store = hs.get_datastores().main
|
||||
self.clock = hs.get_clock()
|
||||
|
||||
@staticmethod
|
||||
async def _serialize_payload( # type: ignore[override]
|
||||
user_id: str, device_id: str, keys: JsonDict
|
||||
) -> JsonDict:
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"device_id": device_id,
|
||||
"keys": keys,
|
||||
}
|
||||
|
||||
async def _handle_request( # type: ignore[override]
|
||||
self, request: Request, content: JsonDict
|
||||
) -> Tuple[int, JsonDict]:
|
||||
user_id = content["user_id"]
|
||||
device_id = content["device_id"]
|
||||
keys = content["keys"]
|
||||
|
||||
results = await self.e2e_keys_handler.upload_keys_for_user(
|
||||
user_id, device_id, keys
|
||||
)
|
||||
|
||||
return 200, results
|
||||
|
||||
|
||||
class ReplicationHandleNewDeviceUpdateRestServlet(ReplicationEndpoint):
|
||||
"""Wake up a device writer to send local device list changes as federation outbound pokes.
|
||||
|
||||
@ -291,5 +251,4 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
|
||||
ReplicationNotifyUserSignatureUpdateRestServlet(hs).register(http_server)
|
||||
ReplicationMultiUserDevicesResyncRestServlet(hs).register(http_server)
|
||||
ReplicationHandleNewDeviceUpdateRestServlet(hs).register(http_server)
|
||||
ReplicationUploadKeysForUserRestServlet(hs).register(http_server)
|
||||
ReplicationDeviceHandleRoomUnPartialStated(hs).register(http_server)
|
||||
|
@ -32,7 +32,6 @@ from synapse.api.constants import EventTypes, Membership, ReceiptTypes
|
||||
from synapse.federation import send_queue
|
||||
from synapse.federation.sender import FederationSender
|
||||
from synapse.logging.context import PreserveLoggingContext, make_deferred_yieldable
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.replication.tcp.streams import (
|
||||
AccountDataStream,
|
||||
DeviceListsStream,
|
||||
@ -344,7 +343,9 @@ class ReplicationDataHandler:
|
||||
# to wedge here forever.
|
||||
deferred: "Deferred[None]" = Deferred()
|
||||
deferred = timeout_deferred(
|
||||
deferred, _WAIT_FOR_REPLICATION_TIMEOUT_SECONDS, self._reactor
|
||||
deferred=deferred,
|
||||
timeout=_WAIT_FOR_REPLICATION_TIMEOUT_SECONDS,
|
||||
clock=self._clock,
|
||||
)
|
||||
|
||||
waiting_list = self._streams_to_waiters.setdefault(
|
||||
@ -513,8 +514,8 @@ class FederationSenderHandler:
|
||||
# no need to queue up another task.
|
||||
return
|
||||
|
||||
run_as_background_process(
|
||||
"_save_and_send_ack", self.server_name, self._save_and_send_ack
|
||||
self._hs.run_as_background_process(
|
||||
"_save_and_send_ack", self._save_and_send_ack
|
||||
)
|
||||
|
||||
async def _save_and_send_ack(self) -> None:
|
||||
|
@ -41,7 +41,6 @@ from prometheus_client import Counter
|
||||
from twisted.internet.protocol import ReconnectingClientFactory
|
||||
|
||||
from synapse.metrics import SERVER_NAME_LABEL, LaterGauge
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.replication.tcp.commands import (
|
||||
ClearUserSyncsCommand,
|
||||
Command,
|
||||
@ -132,6 +131,7 @@ class ReplicationCommandHandler:
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.server_name = hs.hostname
|
||||
self.hs = hs
|
||||
self._replication_data_handler = hs.get_replication_data_handler()
|
||||
self._presence_handler = hs.get_presence_handler()
|
||||
self._store = hs.get_datastores().main
|
||||
@ -361,9 +361,8 @@ class ReplicationCommandHandler:
|
||||
return
|
||||
|
||||
# fire off a background process to start processing the queue.
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"process-replication-data",
|
||||
self.server_name,
|
||||
self._unsafe_process_queue,
|
||||
stream_name,
|
||||
)
|
||||
|
@ -42,7 +42,6 @@ from synapse.logging.context import PreserveLoggingContext
|
||||
from synapse.metrics import SERVER_NAME_LABEL, LaterGauge
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
BackgroundProcessLoggingContext,
|
||||
run_as_background_process,
|
||||
)
|
||||
from synapse.replication.tcp.commands import (
|
||||
VALID_CLIENT_COMMANDS,
|
||||
@ -140,9 +139,14 @@ class BaseReplicationStreamProtocol(LineOnlyReceiver):
|
||||
max_line_buffer = 10000
|
||||
|
||||
def __init__(
|
||||
self, server_name: str, clock: Clock, handler: "ReplicationCommandHandler"
|
||||
self,
|
||||
hs: "HomeServer",
|
||||
server_name: str,
|
||||
clock: Clock,
|
||||
handler: "ReplicationCommandHandler",
|
||||
):
|
||||
self.server_name = server_name
|
||||
self.hs = hs
|
||||
self.clock = clock
|
||||
self.command_handler = handler
|
||||
|
||||
@ -290,9 +294,8 @@ class BaseReplicationStreamProtocol(LineOnlyReceiver):
|
||||
# if so.
|
||||
|
||||
if isawaitable(res):
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"replication-" + cmd.get_logcontext_id(),
|
||||
self.server_name,
|
||||
lambda: res,
|
||||
)
|
||||
|
||||
@ -470,9 +473,13 @@ class ServerReplicationStreamProtocol(BaseReplicationStreamProtocol):
|
||||
VALID_OUTBOUND_COMMANDS = VALID_SERVER_COMMANDS
|
||||
|
||||
def __init__(
|
||||
self, server_name: str, clock: Clock, handler: "ReplicationCommandHandler"
|
||||
self,
|
||||
hs: "HomeServer",
|
||||
server_name: str,
|
||||
clock: Clock,
|
||||
handler: "ReplicationCommandHandler",
|
||||
):
|
||||
super().__init__(server_name, clock, handler)
|
||||
super().__init__(hs, server_name, clock, handler)
|
||||
|
||||
self.server_name = server_name
|
||||
|
||||
@ -497,7 +504,7 @@ class ClientReplicationStreamProtocol(BaseReplicationStreamProtocol):
|
||||
clock: Clock,
|
||||
command_handler: "ReplicationCommandHandler",
|
||||
):
|
||||
super().__init__(server_name, clock, command_handler)
|
||||
super().__init__(hs, server_name, clock, command_handler)
|
||||
|
||||
self.client_name = client_name
|
||||
self.server_name = server_name
|
||||
|
@ -40,7 +40,6 @@ from synapse.logging.context import PreserveLoggingContext, make_deferred_yielda
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
BackgroundProcessLoggingContext,
|
||||
run_as_background_process,
|
||||
wrap_as_background_process,
|
||||
)
|
||||
from synapse.replication.tcp.commands import (
|
||||
@ -109,6 +108,7 @@ class RedisSubscriber(SubscriberProtocol):
|
||||
"""
|
||||
|
||||
server_name: str
|
||||
hs: "HomeServer"
|
||||
synapse_handler: "ReplicationCommandHandler"
|
||||
synapse_stream_prefix: str
|
||||
synapse_channel_names: List[str]
|
||||
@ -146,9 +146,7 @@ class RedisSubscriber(SubscriberProtocol):
|
||||
def connectionMade(self) -> None:
|
||||
logger.info("Connected to redis")
|
||||
super().connectionMade()
|
||||
run_as_background_process(
|
||||
"subscribe-replication", self.server_name, self._send_subscribe
|
||||
)
|
||||
self.hs.run_as_background_process("subscribe-replication", self._send_subscribe)
|
||||
|
||||
async def _send_subscribe(self) -> None:
|
||||
# it's important to make sure that we only send the REPLICATE command once we
|
||||
@ -223,8 +221,8 @@ class RedisSubscriber(SubscriberProtocol):
|
||||
# if so.
|
||||
|
||||
if isawaitable(res):
|
||||
run_as_background_process(
|
||||
"replication-" + cmd.get_logcontext_id(), self.server_name, lambda: res
|
||||
self.hs.run_as_background_process(
|
||||
"replication-" + cmd.get_logcontext_id(), lambda: res
|
||||
)
|
||||
|
||||
def connectionLost(self, reason: Failure) -> None: # type: ignore[override]
|
||||
@ -245,9 +243,8 @@ class RedisSubscriber(SubscriberProtocol):
|
||||
Args:
|
||||
cmd: The command to send
|
||||
"""
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"send-cmd",
|
||||
self.server_name,
|
||||
self._async_send_command,
|
||||
cmd,
|
||||
# We originally started tracing background processes to avoid `There was no
|
||||
@ -317,9 +314,8 @@ class SynapseRedisFactory(RedisFactory):
|
||||
convertNumbers=convertNumbers,
|
||||
)
|
||||
|
||||
self.server_name = (
|
||||
hs.hostname
|
||||
) # nb must be called this for @wrap_as_background_process
|
||||
self.hs = hs # nb must be called this for @wrap_as_background_process
|
||||
self.server_name = hs.hostname
|
||||
|
||||
hs.get_clock().looping_call(self._send_ping, 30 * 1000)
|
||||
|
||||
@ -397,6 +393,7 @@ class RedisDirectTcpReplicationClientFactory(SynapseRedisFactory):
|
||||
)
|
||||
|
||||
self.server_name = hs.hostname
|
||||
self.hs = hs
|
||||
self.synapse_handler = hs.get_replication_command_handler()
|
||||
self.synapse_stream_prefix = hs.hostname
|
||||
self.synapse_channel_names = channel_names
|
||||
@ -412,6 +409,7 @@ class RedisDirectTcpReplicationClientFactory(SynapseRedisFactory):
|
||||
# the base method does some other things than just instantiating the
|
||||
# protocol.
|
||||
p.server_name = self.server_name
|
||||
p.hs = self.hs
|
||||
p.synapse_handler = self.synapse_handler
|
||||
p.synapse_outbound_redis_connection = self.synapse_outbound_redis_connection
|
||||
p.synapse_stream_prefix = self.synapse_stream_prefix
|
||||
|
@ -30,7 +30,6 @@ from twisted.internet.interfaces import IAddress
|
||||
from twisted.internet.protocol import ServerFactory
|
||||
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.replication.tcp.commands import PositionCommand
|
||||
from synapse.replication.tcp.protocol import ServerReplicationStreamProtocol
|
||||
from synapse.replication.tcp.streams import EventsStream
|
||||
@ -55,6 +54,7 @@ class ReplicationStreamProtocolFactory(ServerFactory):
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.command_handler = hs.get_replication_command_handler()
|
||||
self.clock = hs.get_clock()
|
||||
self.hs = hs
|
||||
self.server_name = hs.config.server.server_name
|
||||
|
||||
# If we've created a `ReplicationStreamProtocolFactory` then we're
|
||||
@ -69,7 +69,7 @@ class ReplicationStreamProtocolFactory(ServerFactory):
|
||||
|
||||
def buildProtocol(self, addr: IAddress) -> ServerReplicationStreamProtocol:
|
||||
return ServerReplicationStreamProtocol(
|
||||
self.server_name, self.clock, self.command_handler
|
||||
self.hs, self.server_name, self.clock, self.command_handler
|
||||
)
|
||||
|
||||
|
||||
@ -82,6 +82,7 @@ class ReplicationStreamer:
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.server_name = hs.hostname
|
||||
self.hs = hs
|
||||
self.store = hs.get_datastores().main
|
||||
self.clock = hs.get_clock()
|
||||
self.notifier = hs.get_notifier()
|
||||
@ -147,8 +148,8 @@ class ReplicationStreamer:
|
||||
logger.debug("Notifier poke loop already running")
|
||||
return
|
||||
|
||||
run_as_background_process(
|
||||
"replication_notifier", self.server_name, self._run_notifier_loop
|
||||
self.hs.run_as_background_process(
|
||||
"replication_notifier", self._run_notifier_loop
|
||||
)
|
||||
|
||||
async def _run_notifier_loop(self) -> None:
|
||||
|
@ -77,6 +77,7 @@ STREAMS_MAP = {
|
||||
__all__ = [
|
||||
"STREAMS_MAP",
|
||||
"Stream",
|
||||
"EventsStream",
|
||||
"BackfillStream",
|
||||
"PresenceStream",
|
||||
"PresenceFederationStream",
|
||||
@ -87,6 +88,7 @@ __all__ = [
|
||||
"CachesStream",
|
||||
"DeviceListsStream",
|
||||
"ToDeviceStream",
|
||||
"FederationStream",
|
||||
"AccountDataStream",
|
||||
"ThreadSubscriptionsStream",
|
||||
"UnPartialStatedRoomStream",
|
||||
|
@ -66,7 +66,6 @@ from synapse.http.site import SynapseRequest
|
||||
from synapse.logging.context import make_deferred_yieldable, run_in_background
|
||||
from synapse.logging.opentracing import set_tag
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.rest.client._base import client_patterns
|
||||
from synapse.rest.client.transactions import HttpTransactionCache
|
||||
from synapse.state import CREATE_KEY, POWER_KEY
|
||||
@ -1225,6 +1224,7 @@ class RoomRedactEventRestServlet(TransactionRestServlet):
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
super().__init__(hs)
|
||||
self.server_name = hs.hostname
|
||||
self.hs = hs
|
||||
self.event_creation_handler = hs.get_event_creation_handler()
|
||||
self.auth = hs.get_auth()
|
||||
self._store = hs.get_datastores().main
|
||||
@ -1307,9 +1307,8 @@ class RoomRedactEventRestServlet(TransactionRestServlet):
|
||||
)
|
||||
|
||||
if with_relations:
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"redact_related_events",
|
||||
self.server_name,
|
||||
self._relation_handler.redact_events_related_to,
|
||||
requester=requester,
|
||||
event_id=event_id,
|
||||
|
@ -126,6 +126,7 @@ class SyncRestServlet(RestServlet):
|
||||
|
||||
self._json_filter_cache: LruCache[str, bool] = LruCache(
|
||||
max_size=1000,
|
||||
clock=self.clock,
|
||||
cache_name="sync_valid_filter",
|
||||
server_name=self.server_name,
|
||||
)
|
||||
@ -363,9 +364,6 @@ class SyncRestServlet(RestServlet):
|
||||
|
||||
# https://github.com/matrix-org/matrix-doc/blob/54255851f642f84a4f1aaf7bc063eebe3d76752b/proposals/2732-olm-fallback-keys.md
|
||||
# states that this field should always be included, as long as the server supports the feature.
|
||||
response["org.matrix.msc2732.device_unused_fallback_key_types"] = (
|
||||
sync_result.device_unused_fallback_key_types
|
||||
)
|
||||
response["device_unused_fallback_key_types"] = (
|
||||
sync_result.device_unused_fallback_key_types
|
||||
)
|
||||
|
@ -56,7 +56,7 @@ class HttpTransactionCache:
|
||||
] = {}
|
||||
# Try to clean entries every 30 mins. This means entries will exist
|
||||
# for at *LEAST* 30 mins, and at *MOST* 60 mins.
|
||||
self.cleaner = self.clock.looping_call(self._cleanup, CLEANUP_PERIOD_MS)
|
||||
self.clock.looping_call(self._cleanup, CLEANUP_PERIOD_MS)
|
||||
|
||||
def _get_transaction_key(self, request: IRequest, requester: Requester) -> Hashable:
|
||||
"""A helper function which returns a transaction key that can be used
|
||||
|
@ -28,10 +28,27 @@
|
||||
import abc
|
||||
import functools
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type, TypeVar, cast
|
||||
from threading import Thread
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
cast,
|
||||
)
|
||||
from wsgiref.simple_server import WSGIServer
|
||||
|
||||
from attr import dataclass
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.base import _SystemEventID
|
||||
from twisted.internet.interfaces import IOpenSSLContextFactory
|
||||
from twisted.internet.tcp import Port
|
||||
from twisted.python.threadpool import ThreadPool
|
||||
@ -44,6 +61,7 @@ from synapse.api.auth.mas import MasDelegatedAuth
|
||||
from synapse.api.auth_blocking import AuthBlocking
|
||||
from synapse.api.filtering import Filtering
|
||||
from synapse.api.ratelimiting import Ratelimiter, RequestRatelimiter
|
||||
from synapse.app._base import unregister_sighups
|
||||
from synapse.appservice.api import ApplicationServiceApi
|
||||
from synapse.appservice.scheduler import ApplicationServiceScheduler
|
||||
from synapse.config.homeserver import HomeServerConfig
|
||||
@ -133,6 +151,7 @@ from synapse.metrics import (
|
||||
all_later_gauges_to_clean_up_on_shutdown,
|
||||
register_threadpool,
|
||||
)
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.metrics.common_usage_metrics import CommonUsageMetricsManager
|
||||
from synapse.module_api import ModuleApi
|
||||
from synapse.module_api.callbacks import ModuleApiCallbacks
|
||||
@ -156,6 +175,7 @@ from synapse.storage.controllers import StorageControllers
|
||||
from synapse.streams.events import EventSources
|
||||
from synapse.synapse_rust.rendezvous import RendezvousHandler
|
||||
from synapse.types import DomainSpecificString, ISynapseReactor
|
||||
from synapse.util.caches import CACHE_METRIC_REGISTRY
|
||||
from synapse.util.clock import Clock
|
||||
from synapse.util.distributor import Distributor
|
||||
from synapse.util.macaroons import MacaroonGenerator
|
||||
@ -166,7 +186,9 @@ from synapse.util.task_scheduler import TaskScheduler
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# Old Python versions don't have `LiteralString`
|
||||
from txredisapi import ConnectionHandler
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
from synapse.handlers.jwt import JwtHandler
|
||||
from synapse.handlers.oidc import OidcHandler
|
||||
@ -196,6 +218,7 @@ if TYPE_CHECKING:
|
||||
|
||||
T: TypeAlias = object
|
||||
F = TypeVar("F", bound=Callable[["HomeServer"], T])
|
||||
R = TypeVar("R")
|
||||
|
||||
|
||||
def cache_in_self(builder: F) -> F:
|
||||
@ -219,7 +242,8 @@ def cache_in_self(builder: F) -> F:
|
||||
@functools.wraps(builder)
|
||||
def _get(self: "HomeServer") -> T:
|
||||
try:
|
||||
return getattr(self, depname)
|
||||
dep = getattr(self, depname)
|
||||
return dep
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@ -239,6 +263,22 @@ def cache_in_self(builder: F) -> F:
|
||||
return cast(F, _get)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ShutdownInfo:
|
||||
"""Information for callable functions called at time of shutdown.
|
||||
|
||||
Attributes:
|
||||
func: the object to call before shutdown.
|
||||
trigger_id: an ID returned when registering this event trigger.
|
||||
args: the arguments to call the function with.
|
||||
kwargs: the keyword arguments to call the function with.
|
||||
"""
|
||||
|
||||
func: Callable[..., Any]
|
||||
trigger_id: _SystemEventID
|
||||
kwargs: Dict[str, object]
|
||||
|
||||
|
||||
class HomeServer(metaclass=abc.ABCMeta):
|
||||
"""A basic homeserver object without lazy component builders.
|
||||
|
||||
@ -289,6 +329,7 @@ class HomeServer(metaclass=abc.ABCMeta):
|
||||
hostname : The hostname for the server.
|
||||
config: The full config for the homeserver.
|
||||
"""
|
||||
|
||||
if not reactor:
|
||||
from twisted.internet import reactor as _reactor
|
||||
|
||||
@ -300,6 +341,7 @@ class HomeServer(metaclass=abc.ABCMeta):
|
||||
self.signing_key = config.key.signing_key[0]
|
||||
self.config = config
|
||||
self._listening_services: List[Port] = []
|
||||
self._metrics_listeners: List[Tuple[WSGIServer, Thread]] = []
|
||||
self.start_time: Optional[int] = None
|
||||
|
||||
self._instance_id = random_string(5)
|
||||
@ -315,6 +357,211 @@ class HomeServer(metaclass=abc.ABCMeta):
|
||||
# This attribute is set by the free function `refresh_certificate`.
|
||||
self.tls_server_context_factory: Optional[IOpenSSLContextFactory] = None
|
||||
|
||||
self._is_shutdown = False
|
||||
self._async_shutdown_handlers: List[ShutdownInfo] = []
|
||||
self._sync_shutdown_handlers: List[ShutdownInfo] = []
|
||||
self._background_processes: set[defer.Deferred[Optional[Any]]] = set()
|
||||
|
||||
def run_as_background_process(
|
||||
self,
|
||||
desc: "LiteralString",
|
||||
func: Callable[..., Awaitable[Optional[R]]],
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> "defer.Deferred[Optional[R]]":
|
||||
"""Run the given function in its own logcontext, with resource metrics
|
||||
|
||||
This should be used to wrap processes which are fired off to run in the
|
||||
background, instead of being associated with a particular request.
|
||||
|
||||
It returns a Deferred which completes when the function completes, but it doesn't
|
||||
follow the synapse logcontext rules, which makes it appropriate for passing to
|
||||
clock.looping_call and friends (or for firing-and-forgetting in the middle of a
|
||||
normal synapse async function).
|
||||
|
||||
Because the returned Deferred does not follow the synapse logcontext rules, awaiting
|
||||
the result of this function will result in the log context being cleared (bad). In
|
||||
order to properly await the result of this function and maintain the current log
|
||||
context, use `make_deferred_yieldable`.
|
||||
|
||||
Args:
|
||||
desc: a description for this background process type
|
||||
server_name: The homeserver name that this background process is being run for
|
||||
(this should be `hs.hostname`).
|
||||
func: a function, which may return a Deferred or a coroutine
|
||||
bg_start_span: Whether to start an opentracing span. Defaults to True.
|
||||
Should only be disabled for processes that will not log to or tag
|
||||
a span.
|
||||
args: positional args for func
|
||||
kwargs: keyword args for func
|
||||
|
||||
Returns:
|
||||
Deferred which returns the result of func, or `None` if func raises.
|
||||
Note that the returned Deferred does not follow the synapse logcontext
|
||||
rules.
|
||||
"""
|
||||
if self._is_shutdown:
|
||||
raise Exception(
|
||||
f"Cannot start background process. HomeServer has been shutdown {len(self._background_processes)} {len(self.get_clock()._looping_calls)} {len(self.get_clock()._call_id_to_delayed_call)}"
|
||||
)
|
||||
|
||||
# Ignore linter error as this is the one location this should be called.
|
||||
deferred = run_as_background_process(desc, self.hostname, func, *args, **kwargs) # type: ignore[untracked-background-process]
|
||||
self._background_processes.add(deferred)
|
||||
|
||||
def on_done(res: R) -> R:
|
||||
try:
|
||||
self._background_processes.remove(deferred)
|
||||
except KeyError:
|
||||
# If the background process isn't being tracked anymore we can just move on.
|
||||
pass
|
||||
return res
|
||||
|
||||
deferred.addBoth(on_done)
|
||||
return deferred
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""
|
||||
Cleanly stops all aspects of the HomeServer and removes any references that
|
||||
have been handed out in order to allow the HomeServer object to be garbage
|
||||
collected.
|
||||
|
||||
You must ensure the HomeServer object to not be frozen in the garbage collector
|
||||
in order for it to be cleaned up. By default, Synapse freezes the HomeServer
|
||||
object in the garbage collector.
|
||||
"""
|
||||
|
||||
self._is_shutdown = True
|
||||
|
||||
logger.info(
|
||||
"Received shutdown request for %s (%s).",
|
||||
self.hostname,
|
||||
self.get_instance_id(),
|
||||
)
|
||||
|
||||
# Unregister sighups first. If a shutdown was requested we shouldn't be responding
|
||||
# to things like config changes. So it would be best to stop listening to these first.
|
||||
unregister_sighups(self._instance_id)
|
||||
|
||||
# TODO: It would be desireable to be able to report an error if the HomeServer
|
||||
# object is frozen in the garbage collector as that would prevent it from being
|
||||
# collected after being shutdown.
|
||||
# In theory the following should work, but it doesn't seem to make a difference
|
||||
# when I test it locally.
|
||||
#
|
||||
# if gc.is_tracked(self):
|
||||
# logger.error("HomeServer object is tracked by garbage collection so cannot be fully cleaned up")
|
||||
|
||||
for listener in self._listening_services:
|
||||
# During unit tests, an incomplete `twisted.pair.testing._FakePort` is used
|
||||
# for listeners so check listener type here to ensure shutdown procedure is
|
||||
# only applied to actual `Port` instances.
|
||||
if type(listener) is Port:
|
||||
port_shutdown = listener.stopListening()
|
||||
if port_shutdown is not None:
|
||||
await port_shutdown
|
||||
self._listening_services.clear()
|
||||
|
||||
for server, thread in self._metrics_listeners:
|
||||
server.shutdown()
|
||||
thread.join()
|
||||
self._metrics_listeners.clear()
|
||||
|
||||
# TODO: Cleanup replication pieces
|
||||
|
||||
self.get_keyring().shutdown()
|
||||
|
||||
# Cleanup metrics associated with the homeserver
|
||||
for later_gauge in all_later_gauges_to_clean_up_on_shutdown.values():
|
||||
later_gauge.unregister_hooks_for_homeserver_instance_id(
|
||||
self.get_instance_id()
|
||||
)
|
||||
|
||||
CACHE_METRIC_REGISTRY.unregister_hooks_for_homeserver(
|
||||
self.config.server.server_name
|
||||
)
|
||||
|
||||
for db in self.get_datastores().databases:
|
||||
db.stop_background_updates()
|
||||
|
||||
if self.should_send_federation():
|
||||
try:
|
||||
self.get_federation_sender().shutdown()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for shutdown_handler in self._async_shutdown_handlers:
|
||||
try:
|
||||
self.get_reactor().removeSystemEventTrigger(shutdown_handler.trigger_id)
|
||||
defer.ensureDeferred(shutdown_handler.func(**shutdown_handler.kwargs))
|
||||
except Exception as e:
|
||||
logger.error("Error calling shutdown async handler: %s", e)
|
||||
self._async_shutdown_handlers.clear()
|
||||
|
||||
for shutdown_handler in self._sync_shutdown_handlers:
|
||||
try:
|
||||
self.get_reactor().removeSystemEventTrigger(shutdown_handler.trigger_id)
|
||||
shutdown_handler.func(**shutdown_handler.kwargs)
|
||||
except Exception as e:
|
||||
logger.error("Error calling shutdown sync handler: %s", e)
|
||||
self._sync_shutdown_handlers.clear()
|
||||
|
||||
self.get_clock().shutdown()
|
||||
|
||||
for background_process in list(self._background_processes):
|
||||
try:
|
||||
background_process.cancel()
|
||||
except Exception:
|
||||
pass
|
||||
self._background_processes.clear()
|
||||
|
||||
for db in self.get_datastores().databases:
|
||||
db._db_pool.close()
|
||||
|
||||
def register_async_shutdown_handler(
|
||||
self,
|
||||
*,
|
||||
phase: str,
|
||||
eventType: str,
|
||||
shutdown_func: Callable[..., Any],
|
||||
**kwargs: object,
|
||||
) -> None:
|
||||
"""
|
||||
Register a system event trigger with the HomeServer so it can be cleanly
|
||||
removed when the HomeServer is shutdown.
|
||||
"""
|
||||
id = self.get_clock().add_system_event_trigger(
|
||||
phase,
|
||||
eventType,
|
||||
shutdown_func,
|
||||
**kwargs,
|
||||
)
|
||||
self._async_shutdown_handlers.append(
|
||||
ShutdownInfo(func=shutdown_func, trigger_id=id, kwargs=kwargs)
|
||||
)
|
||||
|
||||
def register_sync_shutdown_handler(
|
||||
self,
|
||||
*,
|
||||
phase: str,
|
||||
eventType: str,
|
||||
shutdown_func: Callable[..., Any],
|
||||
**kwargs: object,
|
||||
) -> None:
|
||||
"""
|
||||
Register a system event trigger with the HomeServer so it can be cleanly
|
||||
removed when the HomeServer is shutdown.
|
||||
"""
|
||||
id = self.get_clock().add_system_event_trigger(
|
||||
phase,
|
||||
eventType,
|
||||
shutdown_func,
|
||||
**kwargs,
|
||||
)
|
||||
self._sync_shutdown_handlers.append(
|
||||
ShutdownInfo(func=shutdown_func, trigger_id=id, kwargs=kwargs)
|
||||
)
|
||||
|
||||
def register_module_web_resource(self, path: str, resource: Resource) -> None:
|
||||
"""Allows a module to register a web resource to be served at the given path.
|
||||
|
||||
@ -366,36 +613,25 @@ class HomeServer(metaclass=abc.ABCMeta):
|
||||
self.datastores = Databases(self.DATASTORE_CLASS, self)
|
||||
logger.info("Finished setting up.")
|
||||
|
||||
def __del__(self) -> None:
|
||||
"""
|
||||
Called when an the homeserver is garbage collected.
|
||||
# Register background tasks required by this server. This must be done
|
||||
# somewhat manually due to the background tasks not being registered
|
||||
# unless handlers are instantiated.
|
||||
if self.config.worker.run_background_tasks:
|
||||
self.start_background_tasks()
|
||||
|
||||
Make sure we actually do some clean-up, rather than leak data.
|
||||
"""
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self) -> None:
|
||||
"""
|
||||
WIP: Clean-up any references to the homeserver and stop any running related
|
||||
processes, timers, loops, replication stream, etc.
|
||||
|
||||
This should be called wherever you care about the HomeServer being completely
|
||||
garbage collected like in tests. It's not necessary to call if you plan to just
|
||||
shut down the whole Python process anyway.
|
||||
|
||||
Can be called multiple times.
|
||||
"""
|
||||
logger.info("Received cleanup request for %s.", self.hostname)
|
||||
|
||||
# TODO: Stop background processes, timers, loops, replication stream, etc.
|
||||
|
||||
# Cleanup metrics associated with the homeserver
|
||||
for later_gauge in all_later_gauges_to_clean_up_on_shutdown.values():
|
||||
later_gauge.unregister_hooks_for_homeserver_instance_id(
|
||||
self.get_instance_id()
|
||||
)
|
||||
|
||||
logger.info("Cleanup complete for %s.", self.hostname)
|
||||
# def __del__(self) -> None:
|
||||
# """
|
||||
# Called when an the homeserver is garbage collected.
|
||||
#
|
||||
# Make sure we actually do some clean-up, rather than leak data.
|
||||
# """
|
||||
#
|
||||
# # NOTE: This is a chicken and egg problem.
|
||||
# # __del__ will never be called since the HomeServer cannot be garbage collected
|
||||
# # until the shutdown function has been called. So it makes no sense to call
|
||||
# # shutdown inside of __del__, even though that is a logical place to assume it
|
||||
# # should be called.
|
||||
# self.shutdown()
|
||||
|
||||
def start_listening(self) -> None: # noqa: B027 (no-op by design)
|
||||
"""Start the HTTP, manhole, metrics, etc listeners
|
||||
@ -442,7 +678,8 @@ class HomeServer(metaclass=abc.ABCMeta):
|
||||
|
||||
@cache_in_self
|
||||
def get_clock(self) -> Clock:
|
||||
return Clock(self._reactor, server_name=self.hostname)
|
||||
# Ignore the linter error since this is the one place the `Clock` should be created.
|
||||
return Clock(self._reactor, server_name=self.hostname) # type: ignore[multiple-internal-clocks]
|
||||
|
||||
def get_datastores(self) -> Databases:
|
||||
if not self.datastores:
|
||||
@ -452,7 +689,7 @@ class HomeServer(metaclass=abc.ABCMeta):
|
||||
|
||||
@cache_in_self
|
||||
def get_distributor(self) -> Distributor:
|
||||
return Distributor(server_name=self.hostname)
|
||||
return Distributor(hs=self)
|
||||
|
||||
@cache_in_self
|
||||
def get_registration_ratelimiter(self) -> Ratelimiter:
|
||||
@ -1007,8 +1244,10 @@ class HomeServer(metaclass=abc.ABCMeta):
|
||||
)
|
||||
|
||||
media_threadpool.start()
|
||||
self.get_clock().add_system_event_trigger(
|
||||
"during", "shutdown", media_threadpool.stop
|
||||
self.register_sync_shutdown_handler(
|
||||
phase="during",
|
||||
eventType="shutdown",
|
||||
shutdown_func=media_threadpool.stop,
|
||||
)
|
||||
|
||||
# Register the threadpool with our metrics.
|
||||
|
@ -36,6 +36,7 @@ SERVER_NOTICE_ROOM_TAG = "m.server_notice"
|
||||
class ServerNoticesManager:
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self.server_name = hs.hostname # nb must be called this for @cached
|
||||
self.clock = hs.get_clock() # nb must be called this for @cached
|
||||
self._store = hs.get_datastores().main
|
||||
self._config = hs.config
|
||||
self._account_data_handler = hs.get_account_data_handler()
|
||||
|
@ -651,6 +651,7 @@ class StateResolutionHandler:
|
||||
ExpiringCache(
|
||||
cache_name="state_cache",
|
||||
server_name=self.server_name,
|
||||
hs=hs,
|
||||
clock=self.clock,
|
||||
max_len=100000,
|
||||
expiry_ms=EVICTION_TIMEOUT_SECONDS * 1000,
|
||||
|
@ -56,7 +56,7 @@ class SQLBaseStore(metaclass=ABCMeta):
|
||||
):
|
||||
self.hs = hs
|
||||
self.server_name = hs.hostname # nb must be called this for @cached
|
||||
self._clock = hs.get_clock()
|
||||
self.clock = hs.get_clock() # nb must be called this for @cached
|
||||
self.database_engine = database.engine
|
||||
self.db_pool = database
|
||||
|
||||
|
@ -41,7 +41,6 @@ from typing import (
|
||||
import attr
|
||||
|
||||
from synapse._pydantic_compat import BaseModel
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.storage.engines import PostgresEngine
|
||||
from synapse.storage.types import Connection, Cursor
|
||||
from synapse.types import JsonDict, StrCollection
|
||||
@ -285,6 +284,13 @@ class BackgroundUpdater:
|
||||
self.sleep_duration_ms = hs.config.background_updates.sleep_duration_ms
|
||||
self.sleep_enabled = hs.config.background_updates.sleep_enabled
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
Stop any further background updates from happening.
|
||||
"""
|
||||
self.enabled = False
|
||||
self._background_update_handlers.clear()
|
||||
|
||||
def get_status(self) -> UpdaterStatus:
|
||||
"""An integer summarising the updater status. Used as a metric."""
|
||||
if self._aborted:
|
||||
@ -396,9 +402,8 @@ class BackgroundUpdater:
|
||||
# if we start a new background update, not all updates are done.
|
||||
self._all_done = False
|
||||
sleep = self.sleep_enabled
|
||||
run_as_background_process(
|
||||
self.hs.run_as_background_process(
|
||||
"background_updates",
|
||||
self.server_name,
|
||||
self.run_background_updates,
|
||||
sleep,
|
||||
)
|
||||
|
@ -62,7 +62,6 @@ from synapse.logging.opentracing import (
|
||||
trace,
|
||||
)
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.storage.controllers.state import StateStorageController
|
||||
from synapse.storage.databases import Databases
|
||||
from synapse.storage.databases.main.events import DeltaState
|
||||
@ -195,6 +194,7 @@ class _EventPeristenceQueue(Generic[_PersistResult]):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hs: "HomeServer",
|
||||
server_name: str,
|
||||
per_item_callback: Callable[
|
||||
[str, _EventPersistQueueTask],
|
||||
@ -207,6 +207,7 @@ class _EventPeristenceQueue(Generic[_PersistResult]):
|
||||
and its result will be returned via the Deferreds returned from add_to_queue.
|
||||
"""
|
||||
self.server_name = server_name
|
||||
self.hs = hs
|
||||
self._event_persist_queues: Dict[str, Deque[_EventPersistQueueItem]] = {}
|
||||
self._currently_persisting_rooms: Set[str] = set()
|
||||
self._per_item_callback = per_item_callback
|
||||
@ -311,7 +312,7 @@ class _EventPeristenceQueue(Generic[_PersistResult]):
|
||||
self._currently_persisting_rooms.discard(room_id)
|
||||
|
||||
# set handle_queue_loop off in the background
|
||||
run_as_background_process("persist_events", self.server_name, handle_queue_loop)
|
||||
self.hs.run_as_background_process("persist_events", handle_queue_loop)
|
||||
|
||||
def _get_drainining_queue(
|
||||
self, room_id: str
|
||||
@ -354,7 +355,7 @@ class EventsPersistenceStorageController:
|
||||
self._instance_name = hs.get_instance_name()
|
||||
self.is_mine_id = hs.is_mine_id
|
||||
self._event_persist_queue = _EventPeristenceQueue(
|
||||
self.server_name, self._process_event_persist_queue_task
|
||||
hs, self.server_name, self._process_event_persist_queue_task
|
||||
)
|
||||
self._state_resolution_handler = hs.get_state_resolution_handler()
|
||||
self._state_controller = state_controller
|
||||
|
@ -46,9 +46,8 @@ class PurgeEventsStorageController:
|
||||
"""High level interface for purging rooms and event history."""
|
||||
|
||||
def __init__(self, hs: "HomeServer", stores: Databases):
|
||||
self.server_name = (
|
||||
hs.hostname
|
||||
) # nb must be called this for @wrap_as_background_process
|
||||
self.hs = hs # nb must be called this for @wrap_as_background_process
|
||||
self.server_name = hs.hostname
|
||||
self.stores = stores
|
||||
|
||||
if hs.config.worker.run_background_tasks:
|
||||
|
@ -69,8 +69,8 @@ class StateStorageController:
|
||||
|
||||
def __init__(self, hs: "HomeServer", stores: "Databases"):
|
||||
self.server_name = hs.hostname # nb must be called this for @cached
|
||||
self.clock = hs.get_clock()
|
||||
self._is_mine_id = hs.is_mine_id
|
||||
self._clock = hs.get_clock()
|
||||
self.stores = stores
|
||||
self._partial_state_events_tracker = PartialStateEventsTracker(stores.main)
|
||||
self._partial_state_room_tracker = PartialCurrentStateTracker(stores.main)
|
||||
@ -78,7 +78,7 @@ class StateStorageController:
|
||||
# Used by `_get_joined_hosts` to ensure only one thing mutates the cache
|
||||
# at a time. Keyed by room_id.
|
||||
self._joined_host_linearizer = Linearizer(
|
||||
name="_JoinedHostsCache", clock=self._clock
|
||||
name="_JoinedHostsCache", clock=self.clock
|
||||
)
|
||||
|
||||
def notify_event_un_partial_stated(self, event_id: str) -> None:
|
||||
@ -817,9 +817,7 @@ class StateStorageController:
|
||||
state_group = object()
|
||||
|
||||
assert state_group is not None
|
||||
with Measure(
|
||||
self._clock, name="get_joined_hosts", server_name=self.server_name
|
||||
):
|
||||
with Measure(self.clock, name="get_joined_hosts", server_name=self.server_name):
|
||||
return await self._get_joined_hosts(
|
||||
room_id, state_group, state_entry=state_entry
|
||||
)
|
||||
|
@ -62,7 +62,6 @@ from synapse.logging.context import (
|
||||
make_deferred_yieldable,
|
||||
)
|
||||
from synapse.metrics import SERVER_NAME_LABEL, register_threadpool
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.storage.background_updates import BackgroundUpdater
|
||||
from synapse.storage.engines import BaseDatabaseEngine, PostgresEngine, Sqlite3Engine
|
||||
from synapse.storage.types import Connection, Cursor, SQLQueryParameters
|
||||
@ -638,12 +637,17 @@ class DatabasePool:
|
||||
# background updates of tables that aren't safe to update.
|
||||
self._clock.call_later(
|
||||
0.0,
|
||||
run_as_background_process,
|
||||
self.hs.run_as_background_process,
|
||||
"upsert_safety_check",
|
||||
self.server_name,
|
||||
self._check_safe_to_upsert,
|
||||
)
|
||||
|
||||
def stop_background_updates(self) -> None:
|
||||
"""
|
||||
Stops the database from running any further background updates.
|
||||
"""
|
||||
self.updates.shutdown()
|
||||
|
||||
def name(self) -> str:
|
||||
"Return the name of this database"
|
||||
return self._database_config.name
|
||||
@ -681,9 +685,8 @@ class DatabasePool:
|
||||
if background_update_names:
|
||||
self._clock.call_later(
|
||||
15.0,
|
||||
run_as_background_process,
|
||||
self.hs.run_as_background_process,
|
||||
"upsert_safety_check",
|
||||
self.server_name,
|
||||
self._check_safe_to_upsert,
|
||||
)
|
||||
|
||||
|
@ -751,7 +751,7 @@ class CacheInvalidationWorkerStore(SQLBaseStore):
|
||||
"instance_name": self._instance_name,
|
||||
"cache_func": cache_name,
|
||||
"keys": keys,
|
||||
"invalidation_ts": self._clock.time_msec(),
|
||||
"invalidation_ts": self.clock.time_msec(),
|
||||
},
|
||||
)
|
||||
|
||||
@ -778,7 +778,7 @@ class CacheInvalidationWorkerStore(SQLBaseStore):
|
||||
assert self._cache_id_gen is not None
|
||||
|
||||
stream_ids = self._cache_id_gen.get_next_mult_txn(txn, len(key_tuples))
|
||||
ts = self._clock.time_msec()
|
||||
ts = self.clock.time_msec()
|
||||
txn.call_after(self.hs.get_notifier().on_new_replication_data)
|
||||
self.db_pool.simple_insert_many_txn(
|
||||
txn,
|
||||
@ -830,7 +830,8 @@ class CacheInvalidationWorkerStore(SQLBaseStore):
|
||||
next_interval = REGULAR_CLEANUP_INTERVAL_MS
|
||||
|
||||
self.hs.get_clock().call_later(
|
||||
next_interval / 1000, self._clean_up_cache_invalidation_wrapper
|
||||
next_interval / 1000,
|
||||
self._clean_up_cache_invalidation_wrapper,
|
||||
)
|
||||
|
||||
async def _clean_up_batch_of_old_cache_invalidations(
|
||||
|
@ -77,7 +77,7 @@ class CensorEventsStore(EventsWorkerStore, CacheInvalidationWorkerStore, SQLBase
|
||||
return
|
||||
|
||||
before_ts = (
|
||||
self._clock.time_msec() - self.hs.config.server.redaction_retention_period
|
||||
self.clock.time_msec() - self.hs.config.server.redaction_retention_period
|
||||
)
|
||||
|
||||
# We fetch all redactions that:
|
||||
|
@ -438,10 +438,11 @@ class ClientIpWorkerStore(ClientIpBackgroundUpdateStore, MonthlyActiveUsersWorke
|
||||
cache_name="client_ip_last_seen",
|
||||
server_name=self.server_name,
|
||||
max_size=50000,
|
||||
clock=hs.get_clock(),
|
||||
)
|
||||
|
||||
if hs.config.worker.run_background_tasks and self.user_ips_max_age:
|
||||
self._clock.looping_call(self._prune_old_user_ips, 5 * 1000)
|
||||
self.clock.looping_call(self._prune_old_user_ips, 5 * 1000)
|
||||
|
||||
if self._update_on_this_worker:
|
||||
# This is the designated worker that can write to the client IP
|
||||
@ -452,11 +453,11 @@ class ClientIpWorkerStore(ClientIpBackgroundUpdateStore, MonthlyActiveUsersWorke
|
||||
Tuple[str, str, str], Tuple[str, Optional[str], int]
|
||||
] = {}
|
||||
|
||||
self._client_ip_looper = self._clock.looping_call(
|
||||
self._update_client_ips_batch, 5 * 1000
|
||||
)
|
||||
self.hs.get_clock().add_system_event_trigger(
|
||||
"before", "shutdown", self._update_client_ips_batch
|
||||
self.clock.looping_call(self._update_client_ips_batch, 5 * 1000)
|
||||
hs.register_async_shutdown_handler(
|
||||
phase="before",
|
||||
eventType="shutdown",
|
||||
shutdown_func=self._update_client_ips_batch,
|
||||
)
|
||||
|
||||
@wrap_as_background_process("prune_old_user_ips")
|
||||
@ -492,7 +493,7 @@ class ClientIpWorkerStore(ClientIpBackgroundUpdateStore, MonthlyActiveUsersWorke
|
||||
)
|
||||
"""
|
||||
|
||||
timestamp = self._clock.time_msec() - self.user_ips_max_age
|
||||
timestamp = self.clock.time_msec() - self.user_ips_max_age
|
||||
|
||||
def _prune_old_user_ips_txn(txn: LoggingTransaction) -> None:
|
||||
txn.execute(sql, (timestamp,))
|
||||
@ -628,7 +629,7 @@ class ClientIpWorkerStore(ClientIpBackgroundUpdateStore, MonthlyActiveUsersWorke
|
||||
return
|
||||
|
||||
if not now:
|
||||
now = int(self._clock.time_msec())
|
||||
now = int(self.clock.time_msec())
|
||||
key = (user_id, access_token, ip)
|
||||
|
||||
try:
|
||||
|
@ -96,7 +96,8 @@ class DeviceInboxWorkerStore(SQLBaseStore):
|
||||
] = ExpiringCache(
|
||||
cache_name="last_device_delete_cache",
|
||||
server_name=self.server_name,
|
||||
clock=self._clock,
|
||||
hs=hs,
|
||||
clock=self.clock,
|
||||
max_len=10000,
|
||||
expiry_ms=30 * 60 * 1000,
|
||||
)
|
||||
@ -154,7 +155,7 @@ class DeviceInboxWorkerStore(SQLBaseStore):
|
||||
)
|
||||
|
||||
if hs.config.worker.run_background_tasks:
|
||||
self._clock.looping_call(
|
||||
self.clock.looping_call(
|
||||
run_as_background_process,
|
||||
DEVICE_FEDERATION_INBOX_CLEANUP_INTERVAL_MS,
|
||||
"_delete_old_federation_inbox_rows",
|
||||
@ -826,7 +827,7 @@ class DeviceInboxWorkerStore(SQLBaseStore):
|
||||
)
|
||||
|
||||
async with self._to_device_msg_id_gen.get_next() as stream_id:
|
||||
now_ms = self._clock.time_msec()
|
||||
now_ms = self.clock.time_msec()
|
||||
await self.db_pool.runInteraction(
|
||||
"add_messages_to_device_inbox", add_messages_txn, now_ms, stream_id
|
||||
)
|
||||
@ -881,7 +882,7 @@ class DeviceInboxWorkerStore(SQLBaseStore):
|
||||
)
|
||||
|
||||
async with self._to_device_msg_id_gen.get_next() as stream_id:
|
||||
now_ms = self._clock.time_msec()
|
||||
now_ms = self.clock.time_msec()
|
||||
await self.db_pool.runInteraction(
|
||||
"add_messages_from_remote_to_device_inbox",
|
||||
add_messages_txn,
|
||||
@ -1002,7 +1003,7 @@ class DeviceInboxWorkerStore(SQLBaseStore):
|
||||
# We delete at most 100 rows that are older than
|
||||
# DEVICE_FEDERATION_INBOX_CLEANUP_DELAY_MS
|
||||
delete_before_ts = (
|
||||
self._clock.time_msec() - DEVICE_FEDERATION_INBOX_CLEANUP_DELAY_MS
|
||||
self.clock.time_msec() - DEVICE_FEDERATION_INBOX_CLEANUP_DELAY_MS
|
||||
)
|
||||
sql = """
|
||||
WITH to_delete AS (
|
||||
@ -1032,7 +1033,7 @@ class DeviceInboxWorkerStore(SQLBaseStore):
|
||||
|
||||
# We sleep a bit so that we don't hammer the database in a tight
|
||||
# loop first time we run this.
|
||||
await self._clock.sleep(1)
|
||||
await self.clock.sleep(1)
|
||||
|
||||
async def get_devices_with_messages(
|
||||
self, user_id: str, device_ids: StrCollection
|
||||
|
@ -195,7 +195,7 @@ class DeviceWorkerStore(RoomMemberWorkerStore, EndToEndKeyWorkerStore):
|
||||
)
|
||||
|
||||
if hs.config.worker.run_background_tasks:
|
||||
self._clock.looping_call(
|
||||
self.clock.looping_call(
|
||||
self._prune_old_outbound_device_pokes, 60 * 60 * 1000
|
||||
)
|
||||
|
||||
@ -1390,7 +1390,7 @@ class DeviceWorkerStore(RoomMemberWorkerStore, EndToEndKeyWorkerStore):
|
||||
table="device_lists_remote_resync",
|
||||
keyvalues={"user_id": user_id},
|
||||
values={},
|
||||
insertion_values={"added_ts": self._clock.time_msec()},
|
||||
insertion_values={"added_ts": self.clock.time_msec()},
|
||||
)
|
||||
|
||||
await self.db_pool.runInteraction(
|
||||
@ -1601,7 +1601,7 @@ class DeviceWorkerStore(RoomMemberWorkerStore, EndToEndKeyWorkerStore):
|
||||
that user when the destination comes back. It doesn't matter which device
|
||||
we keep.
|
||||
"""
|
||||
yesterday = self._clock.time_msec() - prune_age
|
||||
yesterday = self.clock.time_msec() - prune_age
|
||||
|
||||
def _prune_txn(txn: LoggingTransaction) -> None:
|
||||
# look for (user, destination) pairs which have an update older than
|
||||
@ -2086,7 +2086,7 @@ class DeviceWorkerStore(RoomMemberWorkerStore, EndToEndKeyWorkerStore):
|
||||
stream_id,
|
||||
)
|
||||
|
||||
now = self._clock.time_msec()
|
||||
now = self.clock.time_msec()
|
||||
|
||||
encoded_context = json_encoder.encode(context)
|
||||
mark_sent = not self.hs.is_mine_id(user_id)
|
||||
|
@ -1564,7 +1564,7 @@ class EndToEndKeyWorkerStore(EndToEndKeyBackgroundStore, CacheInvalidationWorker
|
||||
DELETE FROM e2e_one_time_keys_json
|
||||
WHERE {clause} AND ts_added_ms < ? AND length(key_id) = 6
|
||||
"""
|
||||
args.append(self._clock.time_msec() - (7 * 24 * 3600 * 1000))
|
||||
args.append(self.clock.time_msec() - (7 * 24 * 3600 * 1000))
|
||||
txn.execute(sql, args)
|
||||
|
||||
return users, txn.rowcount
|
||||
@ -1585,7 +1585,7 @@ class EndToEndKeyWorkerStore(EndToEndKeyBackgroundStore, CacheInvalidationWorker
|
||||
None, if there is no such key.
|
||||
Otherwise, the timestamp before which replacement is allowed without UIA.
|
||||
"""
|
||||
timestamp = self._clock.time_msec() + duration_ms
|
||||
timestamp = self.clock.time_msec() + duration_ms
|
||||
|
||||
def impl(txn: LoggingTransaction) -> Optional[int]:
|
||||
txn.execute(
|
||||
|
@ -167,6 +167,7 @@ class EventFederationWorkerStore(
|
||||
# Cache of event ID to list of auth event IDs and their depths.
|
||||
self._event_auth_cache: LruCache[str, List[Tuple[str, int]]] = LruCache(
|
||||
max_size=500000,
|
||||
clock=self.hs.get_clock(),
|
||||
server_name=self.server_name,
|
||||
cache_name="_event_auth_cache",
|
||||
size_callback=len,
|
||||
@ -176,7 +177,7 @@ class EventFederationWorkerStore(
|
||||
# index.
|
||||
self.tests_allow_no_chain_cover_index = True
|
||||
|
||||
self._clock.looping_call(self._get_stats_for_federation_staging, 30 * 1000)
|
||||
self.clock.looping_call(self._get_stats_for_federation_staging, 30 * 1000)
|
||||
|
||||
if isinstance(self.database_engine, PostgresEngine):
|
||||
self.db_pool.updates.register_background_validate_constraint_and_delete_rows(
|
||||
@ -1328,7 +1329,7 @@ class EventFederationWorkerStore(
|
||||
(
|
||||
room_id,
|
||||
current_depth,
|
||||
self._clock.time_msec(),
|
||||
self.clock.time_msec(),
|
||||
BACKFILL_EVENT_EXPONENTIAL_BACKOFF_MAXIMUM_DOUBLING_STEPS,
|
||||
BACKFILL_EVENT_EXPONENTIAL_BACKOFF_STEP_MILLISECONDS,
|
||||
limit,
|
||||
@ -1841,7 +1842,7 @@ class EventFederationWorkerStore(
|
||||
last_cause=EXCLUDED.last_cause;
|
||||
"""
|
||||
|
||||
txn.execute(sql, (room_id, event_id, 1, self._clock.time_msec(), cause))
|
||||
txn.execute(sql, (room_id, event_id, 1, self.clock.time_msec(), cause))
|
||||
|
||||
@trace
|
||||
async def get_event_ids_with_failed_pull_attempts(
|
||||
@ -1905,7 +1906,7 @@ class EventFederationWorkerStore(
|
||||
),
|
||||
)
|
||||
|
||||
current_time = self._clock.time_msec()
|
||||
current_time = self.clock.time_msec()
|
||||
|
||||
event_ids_with_backoff = {}
|
||||
for event_id, last_attempt_ts, num_attempts in event_failed_pull_attempts:
|
||||
@ -2025,7 +2026,7 @@ class EventFederationWorkerStore(
|
||||
values={},
|
||||
insertion_values={
|
||||
"room_id": event.room_id,
|
||||
"received_ts": self._clock.time_msec(),
|
||||
"received_ts": self.clock.time_msec(),
|
||||
"event_json": json_encoder.encode(event.get_dict()),
|
||||
"internal_metadata": json_encoder.encode(
|
||||
event.internal_metadata.get_dict()
|
||||
@ -2299,7 +2300,7 @@ class EventFederationWorkerStore(
|
||||
# If there is nothing in the staging area default it to 0.
|
||||
age = 0
|
||||
if received_ts is not None:
|
||||
age = self._clock.time_msec() - received_ts
|
||||
age = self.clock.time_msec() - received_ts
|
||||
|
||||
return count, age
|
||||
|
||||
|
@ -95,6 +95,8 @@ from typing import (
|
||||
|
||||
import attr
|
||||
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
from synapse.api.constants import MAIN_TIMELINE, ReceiptTypes
|
||||
from synapse.metrics.background_process_metrics import wrap_as_background_process
|
||||
from synapse.storage._base import SQLBaseStore, db_to_json, make_in_list_sql_clause
|
||||
@ -254,6 +256,8 @@ def _deserialize_action(actions: str, is_highlight: bool) -> List[Union[dict, st
|
||||
|
||||
|
||||
class EventPushActionsWorkerStore(ReceiptsWorkerStore, StreamWorkerStore, SQLBaseStore):
|
||||
_background_tasks: List[LoopingCall] = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
database: DatabasePool,
|
||||
@ -263,7 +267,7 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, StreamWorkerStore, SQLBas
|
||||
super().__init__(database, db_conn, hs)
|
||||
|
||||
# Track when the process started.
|
||||
self._started_ts = self._clock.time_msec()
|
||||
self._started_ts = self.clock.time_msec()
|
||||
|
||||
# These get correctly set by _find_stream_orderings_for_times_txn
|
||||
self.stream_ordering_month_ago: Optional[int] = None
|
||||
@ -273,18 +277,14 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, StreamWorkerStore, SQLBas
|
||||
self._find_stream_orderings_for_times_txn(cur)
|
||||
cur.close()
|
||||
|
||||
self.find_stream_orderings_looping_call = self._clock.looping_call(
|
||||
self._find_stream_orderings_for_times, 10 * 60 * 1000
|
||||
)
|
||||
self.clock.looping_call(self._find_stream_orderings_for_times, 10 * 60 * 1000)
|
||||
|
||||
self._rotate_count = 10000
|
||||
self._doing_notif_rotation = False
|
||||
if hs.config.worker.run_background_tasks:
|
||||
self._rotate_notif_loop = self._clock.looping_call(
|
||||
self._rotate_notifs, 30 * 1000
|
||||
)
|
||||
self.clock.looping_call(self._rotate_notifs, 30 * 1000)
|
||||
|
||||
self._clear_old_staging_loop = self._clock.looping_call(
|
||||
self.clock.looping_call(
|
||||
self._clear_old_push_actions_staging, 30 * 60 * 1000
|
||||
)
|
||||
|
||||
@ -1190,7 +1190,7 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, StreamWorkerStore, SQLBas
|
||||
is_highlight, # highlight column
|
||||
int(count_as_unread), # unread column
|
||||
thread_id, # thread_id column
|
||||
self._clock.time_msec(), # inserted_ts column
|
||||
self.clock.time_msec(), # inserted_ts column
|
||||
)
|
||||
|
||||
await self.db_pool.simple_insert_many(
|
||||
@ -1241,14 +1241,14 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, StreamWorkerStore, SQLBas
|
||||
def _find_stream_orderings_for_times_txn(self, txn: LoggingTransaction) -> None:
|
||||
logger.info("Searching for stream ordering 1 month ago")
|
||||
self.stream_ordering_month_ago = self._find_first_stream_ordering_after_ts_txn(
|
||||
txn, self._clock.time_msec() - 30 * 24 * 60 * 60 * 1000
|
||||
txn, self.clock.time_msec() - 30 * 24 * 60 * 60 * 1000
|
||||
)
|
||||
logger.info(
|
||||
"Found stream ordering 1 month ago: it's %d", self.stream_ordering_month_ago
|
||||
)
|
||||
logger.info("Searching for stream ordering 1 day ago")
|
||||
self.stream_ordering_day_ago = self._find_first_stream_ordering_after_ts_txn(
|
||||
txn, self._clock.time_msec() - 24 * 60 * 60 * 1000
|
||||
txn, self.clock.time_msec() - 24 * 60 * 60 * 1000
|
||||
)
|
||||
logger.info(
|
||||
"Found stream ordering 1 day ago: it's %d", self.stream_ordering_day_ago
|
||||
@ -1787,7 +1787,7 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, StreamWorkerStore, SQLBas
|
||||
|
||||
# We delete anything more than an hour old, on the assumption that we'll
|
||||
# never take more than an hour to persist an event.
|
||||
delete_before_ts = self._clock.time_msec() - 60 * 60 * 1000
|
||||
delete_before_ts = self.clock.time_msec() - 60 * 60 * 1000
|
||||
|
||||
if self._started_ts > delete_before_ts:
|
||||
# We need to wait for at least an hour before we started deleting,
|
||||
@ -1824,7 +1824,7 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, StreamWorkerStore, SQLBas
|
||||
return
|
||||
|
||||
# We sleep to ensure that we don't overwhelm the DB.
|
||||
await self._clock.sleep(1.0)
|
||||
await self.clock.sleep(1.0)
|
||||
|
||||
async def get_push_actions_for_user(
|
||||
self,
|
||||
|
@ -730,7 +730,7 @@ class EventsBackgroundUpdatesStore(StreamWorkerStore, StateDeltasStore, SQLBaseS
|
||||
WHERE ? <= event_id AND event_id <= ?
|
||||
"""
|
||||
|
||||
txn.execute(sql, (self._clock.time_msec(), last_event_id, upper_event_id))
|
||||
txn.execute(sql, (self.clock.time_msec(), last_event_id, upper_event_id))
|
||||
|
||||
self.db_pool.updates._background_update_progress_txn(
|
||||
txn, "redactions_received_ts", {"last_event_id": upper_event_id}
|
||||
|
@ -70,7 +70,6 @@ from synapse.logging.opentracing import (
|
||||
)
|
||||
from synapse.metrics import SERVER_NAME_LABEL
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
run_as_background_process,
|
||||
wrap_as_background_process,
|
||||
)
|
||||
from synapse.replication.tcp.streams import BackfillStream, UnPartialStatedEventStream
|
||||
@ -282,13 +281,14 @@ class EventsWorkerStore(SQLBaseStore):
|
||||
|
||||
if hs.config.worker.run_background_tasks:
|
||||
# We periodically clean out old transaction ID mappings
|
||||
self._clock.looping_call(
|
||||
self.clock.looping_call(
|
||||
self._cleanup_old_transaction_ids,
|
||||
5 * 60 * 1000,
|
||||
)
|
||||
|
||||
self._get_event_cache: AsyncLruCache[Tuple[str], EventCacheEntry] = (
|
||||
AsyncLruCache(
|
||||
clock=hs.get_clock(),
|
||||
server_name=self.server_name,
|
||||
cache_name="*getEvent*",
|
||||
max_size=hs.config.caches.event_cache_size,
|
||||
@ -1154,9 +1154,7 @@ class EventsWorkerStore(SQLBaseStore):
|
||||
should_start = False
|
||||
|
||||
if should_start:
|
||||
run_as_background_process(
|
||||
"fetch_events", self.server_name, self._fetch_thread
|
||||
)
|
||||
self.hs.run_as_background_process("fetch_events", self._fetch_thread)
|
||||
|
||||
async def _fetch_thread(self) -> None:
|
||||
"""Services requests for events from `_event_fetch_list`."""
|
||||
@ -1276,7 +1274,7 @@ class EventsWorkerStore(SQLBaseStore):
|
||||
were not part of this request.
|
||||
"""
|
||||
with Measure(
|
||||
self._clock, name="_fetch_event_list", server_name=self.server_name
|
||||
self.clock, name="_fetch_event_list", server_name=self.server_name
|
||||
):
|
||||
try:
|
||||
events_to_fetch = {
|
||||
@ -2278,7 +2276,7 @@ class EventsWorkerStore(SQLBaseStore):
|
||||
"""Cleans out transaction id mappings older than 24hrs."""
|
||||
|
||||
def _cleanup_old_transaction_ids_txn(txn: LoggingTransaction) -> None:
|
||||
one_day_ago = self._clock.time_msec() - 24 * 60 * 60 * 1000
|
||||
one_day_ago = self.clock.time_msec() - 24 * 60 * 60 * 1000
|
||||
sql = """
|
||||
DELETE FROM event_txn_id_device_id
|
||||
WHERE inserted_ts < ?
|
||||
@ -2633,7 +2631,7 @@ class EventsWorkerStore(SQLBaseStore):
|
||||
keyvalues={"event_id": event_id},
|
||||
values={
|
||||
"reason": rejection_reason,
|
||||
"last_check": self._clock.time_msec(),
|
||||
"last_check": self.clock.time_msec(),
|
||||
},
|
||||
)
|
||||
self.db_pool.simple_update_txn(
|
||||
|
@ -28,7 +28,6 @@ from twisted.internet import defer
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
from synapse.metrics.background_process_metrics import (
|
||||
run_as_background_process,
|
||||
wrap_as_background_process,
|
||||
)
|
||||
from synapse.storage._base import SQLBaseStore
|
||||
@ -99,15 +98,15 @@ class LockStore(SQLBaseStore):
|
||||
# lead to a race, as we may drop the lock while we are still processing.
|
||||
# However, a) it should be a small window, b) the lock is best effort
|
||||
# anyway and c) we want to really avoid leaking locks when we restart.
|
||||
hs.get_clock().add_system_event_trigger(
|
||||
"before",
|
||||
"shutdown",
|
||||
self._on_shutdown,
|
||||
hs.register_async_shutdown_handler(
|
||||
phase="before",
|
||||
eventType="shutdown",
|
||||
shutdown_func=self._on_shutdown,
|
||||
)
|
||||
|
||||
self._acquiring_locks: Set[Tuple[str, str]] = set()
|
||||
|
||||
self._clock.looping_call(
|
||||
self.clock.looping_call(
|
||||
self._reap_stale_read_write_locks, _LOCK_TIMEOUT_MS / 10.0
|
||||
)
|
||||
|
||||
@ -153,7 +152,7 @@ class LockStore(SQLBaseStore):
|
||||
if lock and await lock.is_still_valid():
|
||||
return None
|
||||
|
||||
now = self._clock.time_msec()
|
||||
now = self.clock.time_msec()
|
||||
token = random_string(6)
|
||||
|
||||
def _try_acquire_lock_txn(txn: LoggingTransaction) -> bool:
|
||||
@ -202,7 +201,8 @@ class LockStore(SQLBaseStore):
|
||||
lock = Lock(
|
||||
self.server_name,
|
||||
self._reactor,
|
||||
self._clock,
|
||||
self.hs,
|
||||
self.clock,
|
||||
self,
|
||||
read_write=False,
|
||||
lock_name=lock_name,
|
||||
@ -251,7 +251,7 @@ class LockStore(SQLBaseStore):
|
||||
# constraints. If it doesn't then we have acquired the lock,
|
||||
# otherwise we haven't.
|
||||
|
||||
now = self._clock.time_msec()
|
||||
now = self.clock.time_msec()
|
||||
token = random_string(6)
|
||||
|
||||
self.db_pool.simple_insert_txn(
|
||||
@ -270,7 +270,8 @@ class LockStore(SQLBaseStore):
|
||||
lock = Lock(
|
||||
self.server_name,
|
||||
self._reactor,
|
||||
self._clock,
|
||||
self.hs,
|
||||
self.clock,
|
||||
self,
|
||||
read_write=True,
|
||||
lock_name=lock_name,
|
||||
@ -338,7 +339,7 @@ class LockStore(SQLBaseStore):
|
||||
"""
|
||||
|
||||
def reap_stale_read_write_locks_txn(txn: LoggingTransaction) -> None:
|
||||
txn.execute(delete_sql, (self._clock.time_msec() - _LOCK_TIMEOUT_MS,))
|
||||
txn.execute(delete_sql, (self.clock.time_msec() - _LOCK_TIMEOUT_MS,))
|
||||
if txn.rowcount:
|
||||
logger.info("Reaped %d stale locks", txn.rowcount)
|
||||
|
||||
@ -374,6 +375,7 @@ class Lock:
|
||||
self,
|
||||
server_name: str,
|
||||
reactor: ISynapseReactor,
|
||||
hs: "HomeServer",
|
||||
clock: Clock,
|
||||
store: LockStore,
|
||||
read_write: bool,
|
||||
@ -387,6 +389,7 @@ class Lock:
|
||||
"""
|
||||
self._server_name = server_name
|
||||
self._reactor = reactor
|
||||
self._hs = hs
|
||||
self._clock = clock
|
||||
self._store = store
|
||||
self._read_write = read_write
|
||||
@ -410,6 +413,7 @@ class Lock:
|
||||
_RENEWAL_INTERVAL_MS,
|
||||
self._server_name,
|
||||
self._store,
|
||||
self._hs,
|
||||
self._clock,
|
||||
self._read_write,
|
||||
self._lock_name,
|
||||
@ -421,6 +425,7 @@ class Lock:
|
||||
def _renew(
|
||||
server_name: str,
|
||||
store: LockStore,
|
||||
hs: "HomeServer",
|
||||
clock: Clock,
|
||||
read_write: bool,
|
||||
lock_name: str,
|
||||
@ -457,9 +462,8 @@ class Lock:
|
||||
desc="renew_lock",
|
||||
)
|
||||
|
||||
return run_as_background_process(
|
||||
return hs.run_as_background_process(
|
||||
"Lock._renew",
|
||||
server_name,
|
||||
_internal_renew,
|
||||
store,
|
||||
clock,
|
||||
|
@ -565,7 +565,7 @@ class MediaRepositoryStore(MediaRepositoryBackgroundUpdateStore):
|
||||
sql,
|
||||
(
|
||||
user_id.to_string(),
|
||||
self._clock.time_msec() - self.unused_expiration_time,
|
||||
self.clock.time_msec() - self.unused_expiration_time,
|
||||
),
|
||||
)
|
||||
row = txn.fetchone()
|
||||
@ -1059,7 +1059,7 @@ class MediaRepositoryStore(MediaRepositoryBackgroundUpdateStore):
|
||||
txn: LoggingTransaction,
|
||||
) -> int:
|
||||
# Calculate the timestamp for the start of the time period
|
||||
start_ts = self._clock.time_msec() - time_period_ms
|
||||
start_ts = self.clock.time_msec() - time_period_ms
|
||||
txn.execute(sql, (user_id, start_ts))
|
||||
row = txn.fetchone()
|
||||
if row is None:
|
||||
|
@ -78,7 +78,7 @@ class ServerMetricsStore(EventPushActionsWorkerStore, SQLBaseStore):
|
||||
|
||||
# Read the extrems every 60 minutes
|
||||
if hs.config.worker.run_background_tasks:
|
||||
self._clock.looping_call(self._read_forward_extremities, 60 * 60 * 1000)
|
||||
self.clock.looping_call(self._read_forward_extremities, 60 * 60 * 1000)
|
||||
|
||||
# Used in _generate_user_daily_visits to keep track of progress
|
||||
self._last_user_visit_update = self._get_start_of_day()
|
||||
@ -224,7 +224,7 @@ class ServerMetricsStore(EventPushActionsWorkerStore, SQLBaseStore):
|
||||
"""
|
||||
Counts the number of users who used this homeserver in the last 24 hours.
|
||||
"""
|
||||
yesterday = int(self._clock.time_msec()) - (1000 * 60 * 60 * 24)
|
||||
yesterday = int(self.clock.time_msec()) - (1000 * 60 * 60 * 24)
|
||||
return await self.db_pool.runInteraction(
|
||||
"count_daily_users", self._count_users, yesterday
|
||||
)
|
||||
@ -236,7 +236,7 @@ class ServerMetricsStore(EventPushActionsWorkerStore, SQLBaseStore):
|
||||
from the mau figure in synapse.storage.monthly_active_users which,
|
||||
amongst other things, includes a 3 day grace period before a user counts.
|
||||
"""
|
||||
thirty_days_ago = int(self._clock.time_msec()) - (1000 * 60 * 60 * 24 * 30)
|
||||
thirty_days_ago = int(self.clock.time_msec()) - (1000 * 60 * 60 * 24 * 30)
|
||||
return await self.db_pool.runInteraction(
|
||||
"count_monthly_users", self._count_users, thirty_days_ago
|
||||
)
|
||||
@ -281,7 +281,7 @@ class ServerMetricsStore(EventPushActionsWorkerStore, SQLBaseStore):
|
||||
|
||||
def _count_r30v2_users(txn: LoggingTransaction) -> Dict[str, int]:
|
||||
thirty_days_in_secs = 86400 * 30
|
||||
now = int(self._clock.time())
|
||||
now = int(self.clock.time())
|
||||
sixty_days_ago_in_secs = now - 2 * thirty_days_in_secs
|
||||
one_day_from_now_in_secs = now + 86400
|
||||
|
||||
@ -389,7 +389,7 @@ class ServerMetricsStore(EventPushActionsWorkerStore, SQLBaseStore):
|
||||
"""
|
||||
Returns millisecond unixtime for start of UTC day.
|
||||
"""
|
||||
now = time.gmtime(self._clock.time())
|
||||
now = time.gmtime(self.clock.time())
|
||||
today_start = calendar.timegm((now.tm_year, now.tm_mon, now.tm_mday, 0, 0, 0))
|
||||
return today_start * 1000
|
||||
|
||||
@ -403,7 +403,7 @@ class ServerMetricsStore(EventPushActionsWorkerStore, SQLBaseStore):
|
||||
logger.info("Calling _generate_user_daily_visits")
|
||||
today_start = self._get_start_of_day()
|
||||
a_day_in_milliseconds = 24 * 60 * 60 * 1000
|
||||
now = self._clock.time_msec()
|
||||
now = self.clock.time_msec()
|
||||
|
||||
# A note on user_agent. Technically a given device can have multiple
|
||||
# user agents, so we need to decide which one to pick. We could have
|
||||
|
@ -49,7 +49,6 @@ class MonthlyActiveUsersWorkerStore(RegistrationWorkerStore):
|
||||
hs: "HomeServer",
|
||||
):
|
||||
super().__init__(database, db_conn, hs)
|
||||
self._clock = hs.get_clock()
|
||||
self.hs = hs
|
||||
|
||||
if hs.config.redis.redis_enabled:
|
||||
@ -226,7 +225,7 @@ class MonthlyActiveUsersWorkerStore(RegistrationWorkerStore):
|
||||
reserved_users: reserved users to preserve
|
||||
"""
|
||||
|
||||
thirty_days_ago = int(self._clock.time_msec()) - (1000 * 60 * 60 * 24 * 30)
|
||||
thirty_days_ago = int(self.clock.time_msec()) - (1000 * 60 * 60 * 24 * 30)
|
||||
|
||||
in_clause, in_clause_args = make_in_list_sql_clause(
|
||||
self.database_engine, "user_id", reserved_users
|
||||
@ -328,7 +327,7 @@ class MonthlyActiveUsersWorkerStore(RegistrationWorkerStore):
|
||||
txn,
|
||||
table="monthly_active_users",
|
||||
keyvalues={"user_id": user_id},
|
||||
values={"timestamp": int(self._clock.time_msec())},
|
||||
values={"timestamp": int(self.clock.time_msec())},
|
||||
)
|
||||
else:
|
||||
logger.warning("mau limit reserved threepid %s not found in db", tp)
|
||||
@ -391,7 +390,7 @@ class MonthlyActiveUsersWorkerStore(RegistrationWorkerStore):
|
||||
txn,
|
||||
table="monthly_active_users",
|
||||
keyvalues={"user_id": user_id},
|
||||
values={"timestamp": int(self._clock.time_msec())},
|
||||
values={"timestamp": int(self.clock.time_msec())},
|
||||
)
|
||||
|
||||
self._invalidate_cache_and_stream(txn, self.get_monthly_active_count, ())
|
||||
|
@ -1073,7 +1073,7 @@ class ReceiptsWorkerStore(SQLBaseStore):
|
||||
if event_ts is None:
|
||||
return None
|
||||
|
||||
now = self._clock.time_msec()
|
||||
now = self.clock.time_msec()
|
||||
logger.debug(
|
||||
"Receipt %s for event %s in %s (%i ms old)",
|
||||
receipt_type,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user