Skip to content

Commit 5a9ca1e

Browse files
Introduce Clock.call_when_running(...) to include logcontext by default (#18944)
Introduce `Clock.call_when_running(...)` to wrap startup code in a logcontext, ensuring we can identify which server generated the logs. Background: > Ideally, nothing from the Synapse homeserver would be logged against the `sentinel` > logcontext as we want to know which server the logs came from. In practice, this is not > always the case yet especially outside of request handling. > > Global things outside of Synapse (e.g. Twisted reactor code) should run in the > `sentinel` logcontext. It's only when it calls into application code that a logcontext > gets activated. This means the reactor should be started in the `sentinel` logcontext, > and any time an awaitable yields control back to the reactor, it should reset the > logcontext to be the `sentinel` logcontext. This is important to avoid leaking the > current logcontext to the reactor (which would then get picked up and associated with > the next thing the reactor does). > > *-- `docs/log_contexts.md` Also adds a lint to prefer `Clock.call_when_running(...)` over `reactor.callWhenRunning(...)` Part of #18905
1 parent 83aca3f commit 5a9ca1e

File tree

292 files changed

+693
-527
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

292 files changed

+693
-527
lines changed

changelog.d/18944.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Introduce `Clock.call_when_running(...)` to wrap startup code in a logcontext, ensuring we can identify which server generated the logs.

scripts-dev/mypy_synapse_plugin.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@
6868
category="per-homeserver-tenant-metrics",
6969
)
7070

71+
PREFER_SYNAPSE_CLOCK_CALL_WHEN_RUNNING = ErrorCode(
72+
"prefer-synapse-clock-call-when-running",
73+
"`synapse.util.Clock.call_when_running` should be used instead of `reactor.callWhenRunning`",
74+
category="synapse-reactor-clock",
75+
)
76+
7177

7278
class Sentinel(enum.Enum):
7379
# defining a sentinel in this way allows mypy to correctly handle the
@@ -229,9 +235,43 @@ def get_method_signature_hook(
229235
):
230236
return check_is_cacheable_wrapper
231237

238+
if fullname in (
239+
"twisted.internet.interfaces.IReactorCore.callWhenRunning",
240+
"synapse.types.ISynapseThreadlessReactor.callWhenRunning",
241+
"synapse.types.ISynapseReactor.callWhenRunning",
242+
):
243+
return check_call_when_running
244+
232245
return None
233246

234247

248+
def check_call_when_running(ctx: MethodSigContext) -> CallableType:
249+
"""
250+
Ensure that the `reactor.callWhenRunning` callsites aren't used.
251+
252+
`synapse.util.Clock.call_when_running` should always be used instead of
253+
`reactor.callWhenRunning`.
254+
255+
Since `reactor.callWhenRunning` is a reactor callback, the callback will start out
256+
with the sentinel logcontext. `synapse.util.Clock` starts a default logcontext as we
257+
want to know which server the logs came from.
258+
259+
Args:
260+
ctx: The `FunctionSigContext` from mypy.
261+
"""
262+
signature: CallableType = ctx.default_signature
263+
ctx.api.fail(
264+
(
265+
"Expected all `reactor.callWhenRunning` calls to use `synapse.util.Clock.call_when_running` instead. "
266+
"This is so all Synapse code runs with a logcontext as we want to know which server the logs came from."
267+
),
268+
ctx.context,
269+
code=PREFER_SYNAPSE_CLOCK_CALL_WHEN_RUNNING,
270+
)
271+
272+
return signature
273+
274+
235275
def analyze_prometheus_metric_classes(ctx: ClassDefContext) -> None:
236276
"""
237277
Cross-check the list of Prometheus metric classes against the

scripts-dev/sign_json.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
3232
from synapse.crypto.event_signing import add_hashes_and_signatures
33-
from synapse.util import json_encoder
33+
from synapse.util.json import json_encoder
3434

3535

3636
def main() -> None:

synapse/_scripts/synapse_port_db.py

Lines changed: 24 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@
5454
from synapse.config.database import DatabaseConnectionConfig
5555
from synapse.config.homeserver import HomeServerConfig
5656
from synapse.logging.context import (
57-
LoggingContext,
5857
make_deferred_yieldable,
5958
run_in_background,
6059
)
61-
from synapse.notifier import ReplicationNotifier
60+
from synapse.server import HomeServer
61+
from synapse.storage import DataStore
6262
from synapse.storage.database import DatabasePool, LoggingTransaction, make_conn
6363
from synapse.storage.databases.main import FilteringWorkerStore
6464
from synapse.storage.databases.main.account_data import AccountDataWorkerStore
@@ -98,8 +98,7 @@
9898
from synapse.storage.engines import create_engine
9999
from synapse.storage.prepare_database import prepare_database
100100
from synapse.types import ISynapseReactor
101-
from synapse.util import SYNAPSE_VERSION, Clock
102-
from synapse.util.stringutils import random_string
101+
from synapse.util import SYNAPSE_VERSION
103102

104103
# Cast safety: Twisted does some naughty magic which replaces the
105104
# twisted.internet.reactor module with a Reactor instance at runtime.
@@ -318,31 +317,16 @@ def set_room_is_public(self, room_id: str, is_public: bool) -> NoReturn:
318317
)
319318

320319

321-
class MockHomeserver:
322-
def __init__(self, config: HomeServerConfig):
323-
self.clock = Clock(reactor)
324-
self.config = config
325-
self.hostname = config.server.server_name
326-
self.version_string = SYNAPSE_VERSION
327-
self.instance_id = random_string(5)
328-
329-
def get_clock(self) -> Clock:
330-
return self.clock
331-
332-
def get_reactor(self) -> ISynapseReactor:
333-
return reactor
334-
335-
def get_instance_id(self) -> str:
336-
return self.instance_id
337-
338-
def get_instance_name(self) -> str:
339-
return "master"
320+
class MockHomeserver(HomeServer):
321+
DATASTORE_CLASS = DataStore
340322

341-
def should_send_federation(self) -> bool:
342-
return False
343-
344-
def get_replication_notifier(self) -> ReplicationNotifier:
345-
return ReplicationNotifier()
323+
def __init__(self, config: HomeServerConfig):
324+
super().__init__(
325+
hostname=config.server.server_name,
326+
config=config,
327+
reactor=reactor,
328+
version_string=f"Synapse/{SYNAPSE_VERSION}",
329+
)
346330

347331

348332
class Porter:
@@ -351,12 +335,12 @@ def __init__(
351335
sqlite_config: Dict[str, Any],
352336
progress: "Progress",
353337
batch_size: int,
354-
hs_config: HomeServerConfig,
338+
hs: HomeServer,
355339
):
356340
self.sqlite_config = sqlite_config
357341
self.progress = progress
358342
self.batch_size = batch_size
359-
self.hs_config = hs_config
343+
self.hs = hs
360344

361345
async def setup_table(self, table: str) -> Tuple[str, int, int, int, int]:
362346
if table in APPEND_ONLY_TABLES:
@@ -676,8 +660,7 @@ def build_db_store(
676660

677661
engine = create_engine(db_config.config)
678662

679-
hs = MockHomeserver(self.hs_config)
680-
server_name = hs.hostname
663+
server_name = self.hs.hostname
681664

682665
with make_conn(
683666
db_config=db_config,
@@ -688,16 +671,16 @@ def build_db_store(
688671
engine.check_database(
689672
db_conn, allow_outdated_version=allow_outdated_version
690673
)
691-
prepare_database(db_conn, engine, config=self.hs_config)
674+
prepare_database(db_conn, engine, config=self.hs.config)
692675
# Type safety: ignore that we're using Mock homeservers here.
693676
store = Store(
694677
DatabasePool(
695-
hs, # type: ignore[arg-type]
678+
self.hs,
696679
db_config,
697680
engine,
698681
),
699682
db_conn,
700-
hs, # type: ignore[arg-type]
683+
self.hs,
701684
)
702685
db_conn.commit()
703686

@@ -795,7 +778,7 @@ async def run(self) -> None:
795778
return
796779

797780
self.postgres_store = self.build_db_store(
798-
self.hs_config.database.get_single_database()
781+
self.hs.config.database.get_single_database()
799782
)
800783

801784
await self.remove_ignored_background_updates_from_database()
@@ -1584,6 +1567,8 @@ def main() -> None:
15841567
config = HomeServerConfig()
15851568
config.parse_config_dict(hs_config, "", "")
15861569

1570+
hs = MockHomeserver(config)
1571+
15871572
def start(stdscr: Optional["curses.window"] = None) -> None:
15881573
progress: Progress
15891574
if stdscr:
@@ -1595,15 +1580,14 @@ def start(stdscr: Optional["curses.window"] = None) -> None:
15951580
sqlite_config=sqlite_config,
15961581
progress=progress,
15971582
batch_size=args.batch_size,
1598-
hs_config=config,
1583+
hs=hs,
15991584
)
16001585

16011586
@defer.inlineCallbacks
16021587
def run() -> Generator["defer.Deferred[Any]", Any, None]:
1603-
with LoggingContext("synapse_port_db_run"):
1604-
yield defer.ensureDeferred(porter.run())
1588+
yield defer.ensureDeferred(porter.run())
16051589

1606-
reactor.callWhenRunning(run)
1590+
hs.get_clock().call_when_running(run)
16071591

16081592
reactor.run()
16091593

synapse/_scripts/update_synapse_database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def run() -> None:
7474
)
7575
)
7676

77-
reactor.callWhenRunning(run)
77+
hs.get_clock().call_when_running(run)
7878

7979
reactor.run()
8080

synapse/api/auth/mas.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@
4343
from synapse.metrics import SERVER_NAME_LABEL
4444
from synapse.synapse_rust.http_client import HttpClient
4545
from synapse.types import JsonDict, Requester, UserID, create_requester
46-
from synapse.util import json_decoder
4746
from synapse.util.caches.cached_call import RetryOnExceptionCachedCall
4847
from synapse.util.caches.response_cache import ResponseCache, ResponseCacheContext
48+
from synapse.util.json import json_decoder
4949

5050
from . import introspection_response_timer
5151

synapse/api/auth/msc3861_delegated.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@
4848
from synapse.metrics import SERVER_NAME_LABEL
4949
from synapse.synapse_rust.http_client import HttpClient
5050
from synapse.types import Requester, UserID, create_requester
51-
from synapse.util import json_decoder
5251
from synapse.util.caches.cached_call import RetryOnExceptionCachedCall
5352
from synapse.util.caches.response_cache import ResponseCache, ResponseCacheContext
53+
from synapse.util.json import json_decoder
5454

5555
from . import introspection_response_timer
5656

synapse/api/errors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
from twisted.web import http
3232

33-
from synapse.util import json_decoder
33+
from synapse.util.json import json_decoder
3434

3535
if typing.TYPE_CHECKING:
3636
from synapse.config.homeserver import HomeServerConfig

synapse/api/ratelimiting.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from synapse.config.ratelimiting import RatelimitSettings
2727
from synapse.storage.databases.main import DataStore
2828
from synapse.types import Requester
29-
from synapse.util import Clock
29+
from synapse.util.clock import Clock
3030

3131
if TYPE_CHECKING:
3232
# To avoid circular imports:

synapse/app/_base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ def redirect_stdio_to_logs() -> None:
241241

242242

243243
def register_start(
244-
cb: Callable[P, Awaitable], *args: P.args, **kwargs: P.kwargs
244+
hs: "HomeServer", cb: Callable[P, Awaitable], *args: P.args, **kwargs: P.kwargs
245245
) -> None:
246246
"""Register a callback with the reactor, to be called once it is running
247247
@@ -278,7 +278,8 @@ async def wrapper() -> None:
278278
# on as normal.
279279
os._exit(1)
280280

281-
reactor.callWhenRunning(lambda: defer.ensureDeferred(wrapper()))
281+
clock = hs.get_clock()
282+
clock.call_when_running(lambda: defer.ensureDeferred(wrapper()))
282283

283284

284285
def listen_metrics(bind_addresses: StrCollection, port: int) -> None:

0 commit comments

Comments
 (0)