matrix-synapse 1.139.0rc2__cp39-abi3-musllinux_1_2_aarch64.whl → 1.140.0rc1__cp39-abi3-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of matrix-synapse might be problematic. Click here for more details.

Files changed (159) hide show
  1. {matrix_synapse-1.139.0rc2.dist-info → matrix_synapse-1.140.0rc1.dist-info}/METADATA +5 -3
  2. {matrix_synapse-1.139.0rc2.dist-info → matrix_synapse-1.140.0rc1.dist-info}/RECORD +158 -155
  3. synapse/_scripts/generate_workers_map.py +6 -1
  4. synapse/_scripts/synapse_port_db.py +0 -2
  5. synapse/_scripts/update_synapse_database.py +1 -6
  6. synapse/api/auth/base.py +1 -3
  7. synapse/api/auth/mas.py +6 -8
  8. synapse/api/auth/msc3861_delegated.py +6 -8
  9. synapse/api/errors.py +3 -0
  10. synapse/app/_base.py +101 -39
  11. synapse/app/admin_cmd.py +2 -4
  12. synapse/app/appservice.py +1 -1
  13. synapse/app/client_reader.py +1 -1
  14. synapse/app/event_creator.py +1 -1
  15. synapse/app/federation_reader.py +1 -1
  16. synapse/app/federation_sender.py +1 -1
  17. synapse/app/frontend_proxy.py +1 -1
  18. synapse/app/generic_worker.py +17 -11
  19. synapse/app/homeserver.py +85 -47
  20. synapse/app/media_repository.py +1 -1
  21. synapse/app/phone_stats_home.py +16 -14
  22. synapse/app/pusher.py +1 -1
  23. synapse/app/synchrotron.py +1 -1
  24. synapse/app/user_dir.py +1 -1
  25. synapse/appservice/__init__.py +29 -2
  26. synapse/appservice/scheduler.py +8 -8
  27. synapse/config/_base.py +32 -14
  28. synapse/config/_base.pyi +5 -3
  29. synapse/config/experimental.py +3 -0
  30. synapse/config/homeserver.py +27 -1
  31. synapse/config/logger.py +3 -4
  32. synapse/config/matrixrtc.py +67 -0
  33. synapse/crypto/keyring.py +18 -4
  34. synapse/events/auto_accept_invites.py +0 -1
  35. synapse/federation/federation_client.py +39 -0
  36. synapse/federation/federation_server.py +1 -1
  37. synapse/federation/send_queue.py +3 -0
  38. synapse/federation/sender/__init__.py +24 -8
  39. synapse/federation/sender/per_destination_queue.py +31 -8
  40. synapse/federation/sender/transaction_manager.py +12 -0
  41. synapse/federation/transport/client.py +29 -0
  42. synapse/handlers/account_validity.py +2 -4
  43. synapse/handlers/appservice.py +5 -7
  44. synapse/handlers/deactivate_account.py +2 -3
  45. synapse/handlers/delayed_events.py +10 -13
  46. synapse/handlers/device.py +14 -14
  47. synapse/handlers/e2e_keys.py +16 -11
  48. synapse/handlers/federation.py +7 -11
  49. synapse/handlers/federation_event.py +5 -6
  50. synapse/handlers/message.py +16 -10
  51. synapse/handlers/pagination.py +3 -7
  52. synapse/handlers/presence.py +21 -25
  53. synapse/handlers/profile.py +1 -1
  54. synapse/handlers/read_marker.py +3 -1
  55. synapse/handlers/register.py +8 -1
  56. synapse/handlers/room.py +13 -4
  57. synapse/handlers/room_member.py +11 -7
  58. synapse/handlers/room_policy.py +96 -2
  59. synapse/handlers/sso.py +1 -1
  60. synapse/handlers/stats.py +5 -3
  61. synapse/handlers/sync.py +20 -13
  62. synapse/handlers/typing.py +5 -10
  63. synapse/handlers/user_directory.py +12 -11
  64. synapse/handlers/worker_lock.py +19 -15
  65. synapse/http/client.py +18 -13
  66. synapse/http/federation/matrix_federation_agent.py +6 -1
  67. synapse/http/federation/well_known_resolver.py +3 -1
  68. synapse/http/matrixfederationclient.py +50 -11
  69. synapse/http/proxy.py +2 -2
  70. synapse/http/server.py +36 -2
  71. synapse/http/site.py +109 -17
  72. synapse/logging/context.py +201 -110
  73. synapse/logging/opentracing.py +30 -6
  74. synapse/logging/scopecontextmanager.py +161 -0
  75. synapse/media/_base.py +2 -1
  76. synapse/media/media_repository.py +20 -6
  77. synapse/media/url_previewer.py +5 -6
  78. synapse/metrics/_gc.py +3 -1
  79. synapse/metrics/background_process_metrics.py +128 -24
  80. synapse/metrics/common_usage_metrics.py +3 -5
  81. synapse/module_api/__init__.py +42 -5
  82. synapse/notifier.py +10 -3
  83. synapse/push/emailpusher.py +5 -4
  84. synapse/push/httppusher.py +6 -6
  85. synapse/push/pusherpool.py +3 -8
  86. synapse/replication/http/devices.py +0 -41
  87. synapse/replication/tcp/client.py +8 -5
  88. synapse/replication/tcp/handler.py +2 -3
  89. synapse/replication/tcp/protocol.py +14 -7
  90. synapse/replication/tcp/redis.py +16 -11
  91. synapse/replication/tcp/resource.py +5 -4
  92. synapse/replication/tcp/streams/__init__.py +2 -0
  93. synapse/res/providers.json +6 -5
  94. synapse/rest/__init__.py +2 -0
  95. synapse/rest/admin/__init__.py +4 -0
  96. synapse/rest/admin/events.py +69 -0
  97. synapse/rest/admin/media.py +70 -2
  98. synapse/rest/client/keys.py +147 -3
  99. synapse/rest/client/matrixrtc.py +52 -0
  100. synapse/rest/client/push_rule.py +1 -1
  101. synapse/rest/client/room.py +2 -3
  102. synapse/rest/client/sync.py +1 -3
  103. synapse/rest/client/transactions.py +1 -1
  104. synapse/server.py +271 -38
  105. synapse/server_notices/server_notices_manager.py +1 -0
  106. synapse/state/__init__.py +4 -1
  107. synapse/storage/_base.py +1 -1
  108. synapse/storage/background_updates.py +8 -3
  109. synapse/storage/controllers/persist_events.py +4 -3
  110. synapse/storage/controllers/purge_events.py +2 -3
  111. synapse/storage/controllers/state.py +5 -5
  112. synapse/storage/database.py +12 -7
  113. synapse/storage/databases/main/__init__.py +7 -2
  114. synapse/storage/databases/main/cache.py +4 -3
  115. synapse/storage/databases/main/censor_events.py +1 -1
  116. synapse/storage/databases/main/client_ips.py +9 -8
  117. synapse/storage/databases/main/deviceinbox.py +7 -6
  118. synapse/storage/databases/main/devices.py +4 -4
  119. synapse/storage/databases/main/end_to_end_keys.py +6 -3
  120. synapse/storage/databases/main/event_federation.py +7 -6
  121. synapse/storage/databases/main/event_push_actions.py +13 -13
  122. synapse/storage/databases/main/events_bg_updates.py +1 -1
  123. synapse/storage/databases/main/events_worker.py +6 -8
  124. synapse/storage/databases/main/lock.py +17 -13
  125. synapse/storage/databases/main/media_repository.py +2 -2
  126. synapse/storage/databases/main/metrics.py +6 -6
  127. synapse/storage/databases/main/monthly_active_users.py +3 -4
  128. synapse/storage/databases/main/receipts.py +1 -1
  129. synapse/storage/databases/main/registration.py +18 -19
  130. synapse/storage/databases/main/roommember.py +1 -1
  131. synapse/storage/databases/main/session.py +3 -3
  132. synapse/storage/databases/main/sliding_sync.py +2 -2
  133. synapse/storage/databases/main/transactions.py +3 -3
  134. synapse/storage/databases/state/store.py +2 -0
  135. synapse/synapse_rust/http_client.pyi +4 -0
  136. synapse/synapse_rust.abi3.so +0 -0
  137. synapse/util/async_helpers.py +36 -24
  138. synapse/util/batching_queue.py +16 -6
  139. synapse/util/caches/__init__.py +1 -1
  140. synapse/util/caches/deferred_cache.py +4 -0
  141. synapse/util/caches/descriptors.py +14 -2
  142. synapse/util/caches/dictionary_cache.py +6 -1
  143. synapse/util/caches/expiringcache.py +16 -5
  144. synapse/util/caches/lrucache.py +14 -26
  145. synapse/util/caches/response_cache.py +11 -1
  146. synapse/util/clock.py +215 -39
  147. synapse/util/constants.py +2 -0
  148. synapse/util/daemonize.py +5 -1
  149. synapse/util/distributor.py +9 -5
  150. synapse/util/metrics.py +35 -6
  151. synapse/util/ratelimitutils.py +4 -1
  152. synapse/util/retryutils.py +7 -4
  153. synapse/util/task_scheduler.py +11 -14
  154. synapse/logging/filter.py +0 -38
  155. {matrix_synapse-1.139.0rc2.dist-info → matrix_synapse-1.140.0rc1.dist-info}/AUTHORS.rst +0 -0
  156. {matrix_synapse-1.139.0rc2.dist-info → matrix_synapse-1.140.0rc1.dist-info}/LICENSE-AGPL-3.0 +0 -0
  157. {matrix_synapse-1.139.0rc2.dist-info → matrix_synapse-1.140.0rc1.dist-info}/LICENSE-COMMERCIAL +0 -0
  158. {matrix_synapse-1.139.0rc2.dist-info → matrix_synapse-1.140.0rc1.dist-info}/WHEEL +0 -0
  159. {matrix_synapse-1.139.0rc2.dist-info → matrix_synapse-1.140.0rc1.dist-info}/entry_points.txt +0 -0
@@ -45,14 +45,10 @@ from typing import (
45
45
  overload,
46
46
  )
47
47
 
48
- from twisted.internet import defer, reactor
48
+ from twisted.internet import defer
49
49
 
50
50
  from synapse.config import cache as cache_config
51
- from synapse.metrics.background_process_metrics import (
52
- run_as_background_process,
53
- )
54
51
  from synapse.metrics.jemalloc import get_jemalloc_stats
55
- from synapse.types import ISynapseThreadlessReactor
56
52
  from synapse.util import caches
57
53
  from synapse.util.caches import CacheMetric, EvictionReason, register_cache
58
54
  from synapse.util.caches.treecache import (
@@ -123,6 +119,7 @@ GLOBAL_ROOT = ListNode["_Node"].create_root_node()
123
119
 
124
120
  def _expire_old_entries(
125
121
  server_name: str,
122
+ hs: "HomeServer",
126
123
  clock: Clock,
127
124
  expiry_seconds: float,
128
125
  autotune_config: Optional[dict],
@@ -228,9 +225,8 @@ def _expire_old_entries(
228
225
 
229
226
  logger.info("Dropped %d items from caches", i)
230
227
 
231
- return run_as_background_process(
228
+ return hs.run_as_background_process(
232
229
  "LruCache._expire_old_entries",
233
- server_name,
234
230
  _internal_expire_old_entries,
235
231
  clock,
236
232
  expiry_seconds,
@@ -261,6 +257,7 @@ def setup_expire_lru_cache_entries(hs: "HomeServer") -> None:
261
257
  _expire_old_entries,
262
258
  30 * 1000,
263
259
  server_name,
260
+ hs,
264
261
  clock,
265
262
  expiry_time,
266
263
  hs.config.caches.cache_autotuning,
@@ -404,13 +401,13 @@ class LruCache(Generic[KT, VT]):
404
401
  self,
405
402
  *,
406
403
  max_size: int,
404
+ clock: Clock,
407
405
  server_name: str,
408
406
  cache_name: str,
409
407
  cache_type: Type[Union[dict, TreeCache]] = dict,
410
408
  size_callback: Optional[Callable[[VT], int]] = None,
411
409
  metrics_collection_callback: Optional[Callable[[], None]] = None,
412
410
  apply_cache_factor_from_config: bool = True,
413
- clock: Optional[Clock] = None,
414
411
  prune_unread_entries: bool = True,
415
412
  extra_index_cb: Optional[Callable[[KT, VT], KT]] = None,
416
413
  ): ...
@@ -420,13 +417,13 @@ class LruCache(Generic[KT, VT]):
420
417
  self,
421
418
  *,
422
419
  max_size: int,
423
- server_name: Literal[None] = None,
420
+ clock: Clock,
421
+ server_name: str,
424
422
  cache_name: Literal[None] = None,
425
423
  cache_type: Type[Union[dict, TreeCache]] = dict,
426
424
  size_callback: Optional[Callable[[VT], int]] = None,
427
425
  metrics_collection_callback: Optional[Callable[[], None]] = None,
428
426
  apply_cache_factor_from_config: bool = True,
429
- clock: Optional[Clock] = None,
430
427
  prune_unread_entries: bool = True,
431
428
  extra_index_cb: Optional[Callable[[KT, VT], KT]] = None,
432
429
  ): ...
@@ -435,13 +432,13 @@ class LruCache(Generic[KT, VT]):
435
432
  self,
436
433
  *,
437
434
  max_size: int,
438
- server_name: Optional[str] = None,
435
+ clock: Clock,
436
+ server_name: str,
439
437
  cache_name: Optional[str] = None,
440
438
  cache_type: Type[Union[dict, TreeCache]] = dict,
441
439
  size_callback: Optional[Callable[[VT], int]] = None,
442
440
  metrics_collection_callback: Optional[Callable[[], None]] = None,
443
441
  apply_cache_factor_from_config: bool = True,
444
- clock: Optional[Clock] = None,
445
442
  prune_unread_entries: bool = True,
446
443
  extra_index_cb: Optional[Callable[[KT, VT], KT]] = None,
447
444
  ):
@@ -450,12 +447,10 @@ class LruCache(Generic[KT, VT]):
450
447
  max_size: The maximum amount of entries the cache can hold
451
448
 
452
449
  server_name: The homeserver name that this cache is associated with
453
- (used to label the metric) (`hs.hostname`). Must be set if `cache_name` is
454
- set. If unset, no metrics will be reported on this cache.
450
+ (used to label the metric) (`hs.hostname`).
455
451
 
456
- cache_name: The name of this cache, for the prometheus metrics. Must be set
457
- if `server_name` is set. If unset, no metrics will be reported on this
458
- cache.
452
+ cache_name: The name of this cache, for the prometheus metrics. If unset, no
453
+ metrics will be reported on this cache.
459
454
 
460
455
  cache_type:
461
456
  type of underlying cache to be used. Typically one of dict
@@ -494,13 +489,6 @@ class LruCache(Generic[KT, VT]):
494
489
 
495
490
  Note: The new key does not have to be unique.
496
491
  """
497
- # Default `clock` to something sensible. Note that we rename it to
498
- # `real_clock` so that mypy doesn't think its still `Optional`.
499
- if clock is None:
500
- real_clock = Clock(cast(ISynapseThreadlessReactor, reactor))
501
- else:
502
- real_clock = clock
503
-
504
492
  cache: Union[Dict[KT, _Node[KT, VT]], TreeCache] = cache_type()
505
493
  self.cache = cache # Used for introspection.
506
494
  self.apply_cache_factor_from_config = apply_cache_factor_from_config
@@ -592,7 +580,7 @@ class LruCache(Generic[KT, VT]):
592
580
  key,
593
581
  value,
594
582
  weak_ref_to_self,
595
- real_clock,
583
+ clock,
596
584
  callbacks,
597
585
  prune_unread_entries,
598
586
  )
@@ -610,7 +598,7 @@ class LruCache(Generic[KT, VT]):
610
598
  metrics.inc_memory_usage(node.memory)
611
599
 
612
600
  def move_node_to_front(node: _Node[KT, VT]) -> None:
613
- node.move_to_front(real_clock, list_root)
601
+ node.move_to_front(clock, list_root)
614
602
 
615
603
  def delete_node(node: _Node[KT, VT]) -> int:
616
604
  node.drop_from_lists()
@@ -198,7 +198,17 @@ class ResponseCache(Generic[KV]):
198
198
  # the should_cache bit, we leave it in the cache for now and schedule
199
199
  # its removal later.
200
200
  if self.timeout_sec and context.should_cache:
201
- self.clock.call_later(self.timeout_sec, self._entry_timeout, key)
201
+ self.clock.call_later(
202
+ self.timeout_sec,
203
+ self._entry_timeout,
204
+ key,
205
+ # We don't need to track these calls since they don't hold any strong
206
+ # references which would keep the `HomeServer` in memory after shutdown.
207
+ # We don't want to track these because they can get cancelled really
208
+ # quickly and thrash the tracking mechanism, ie. during repeated calls
209
+ # to /sync.
210
+ call_later_cancel_on_shutdown=False,
211
+ )
202
212
  else:
203
213
  # otherwise, remove the result immediately.
204
214
  self.unset(key)
synapse/util/clock.py CHANGED
@@ -17,10 +17,12 @@
17
17
  from typing import (
18
18
  Any,
19
19
  Callable,
20
+ Dict,
21
+ List,
20
22
  )
21
23
 
22
- import attr
23
24
  from typing_extensions import ParamSpec
25
+ from zope.interface import implementer
24
26
 
25
27
  from twisted.internet import defer, task
26
28
  from twisted.internet.defer import Deferred
@@ -34,23 +36,54 @@ from synapse.util import log_failure
34
36
  P = ParamSpec("P")
35
37
 
36
38
 
37
- @attr.s(slots=True)
38
39
  class Clock:
39
40
  """
40
41
  A Clock wraps a Twisted reactor and provides utilities on top of it.
41
42
 
43
+ This clock should be used in place of calls to the base reactor wherever `LoopingCall`
44
+ or `DelayedCall` are made (such as when calling `reactor.callLater`. This is to
45
+ ensure the calls made by this `HomeServer` instance are tracked and can be cleaned
46
+ up during `HomeServer.shutdown()`.
47
+
48
+ We enforce usage of this clock instead of using the reactor directly via lints in
49
+ `scripts-dev/mypy_synapse_plugin.py`.
50
+
51
+
42
52
  Args:
43
53
  reactor: The Twisted reactor to use.
44
54
  """
45
55
 
46
- _reactor: ISynapseThreadlessReactor = attr.ib()
56
+ _reactor: ISynapseThreadlessReactor
57
+
58
+ def __init__(self, reactor: ISynapseThreadlessReactor, server_name: str) -> None:
59
+ self._reactor = reactor
60
+ self._server_name = server_name
61
+
62
+ self._delayed_call_id: int = 0
63
+ """Unique ID used to track delayed calls"""
64
+
65
+ self._looping_calls: List[LoopingCall] = []
66
+ """List of active looping calls"""
67
+
68
+ self._call_id_to_delayed_call: Dict[int, IDelayedCall] = {}
69
+ """Mapping from unique call ID to delayed call"""
70
+
71
+ self._is_shutdown = False
72
+ """Whether shutdown has been requested by the HomeServer"""
73
+
74
+ def shutdown(self) -> None:
75
+ self._is_shutdown = True
76
+ self.cancel_all_looping_calls()
77
+ self.cancel_all_delayed_calls()
47
78
 
48
79
  async def sleep(self, seconds: float) -> None:
49
80
  d: defer.Deferred[float] = defer.Deferred()
50
81
  # Start task in the `sentinel` logcontext, to avoid leaking the current context
51
82
  # into the reactor once it finishes.
52
83
  with context.PreserveLoggingContext():
53
- self._reactor.callLater(seconds, d.callback, seconds)
84
+ # We can ignore the lint here since this class is the one location callLater should
85
+ # be called.
86
+ self._reactor.callLater(seconds, d.callback, seconds) # type: ignore[call-later-not-tracked]
54
87
  await d
55
88
 
56
89
  def time(self) -> float:
@@ -123,6 +156,9 @@ class Clock:
123
156
  ) -> LoopingCall:
124
157
  """Common functionality for `looping_call` and `looping_call_now`"""
125
158
 
159
+ if self._is_shutdown:
160
+ raise Exception("Cannot start looping call. Clock has been shutdown")
161
+
126
162
  def wrapped_f(*args: P.args, **kwargs: P.kwargs) -> Deferred:
127
163
  assert context.current_context() is context.SENTINEL_CONTEXT, (
128
164
  "Expected `looping_call` callback from the reactor to start with the sentinel logcontext "
@@ -144,13 +180,19 @@ class Clock:
144
180
  # this function and yield control back to the reactor to avoid leaking the
145
181
  # current logcontext to the reactor (which would then get picked up and
146
182
  # associated with the next thing the reactor does)
147
- with context.PreserveLoggingContext(context.LoggingContext("looping_call")):
183
+ with context.PreserveLoggingContext(
184
+ context.LoggingContext(
185
+ name="looping_call", server_name=self._server_name
186
+ )
187
+ ):
148
188
  # We use `run_in_background` to reset the logcontext after `f` (or the
149
189
  # awaitable returned by `f`) completes to avoid leaking the current
150
190
  # logcontext to the reactor
151
191
  return context.run_in_background(f, *args, **kwargs)
152
192
 
153
- call = task.LoopingCall(wrapped_f, *args, **kwargs)
193
+ # We can ignore the lint here since this is the one location LoopingCall's
194
+ # should be created.
195
+ call = task.LoopingCall(wrapped_f, *args, **kwargs) # type: ignore[prefer-synapse-clock-looping-call]
154
196
  call.clock = self._reactor
155
197
  # If `now=true`, the function will be called here immediately so we need to be
156
198
  # in the sentinel context now.
@@ -160,10 +202,32 @@ class Clock:
160
202
  with context.PreserveLoggingContext():
161
203
  d = call.start(msec / 1000.0, now=now)
162
204
  d.addErrback(log_failure, "Looping call died", consumeErrors=False)
205
+ self._looping_calls.append(call)
163
206
  return call
164
207
 
208
+ def cancel_all_looping_calls(self, consumeErrors: bool = True) -> None:
209
+ """
210
+ Stop all running looping calls.
211
+
212
+ Args:
213
+ consumeErrors: Whether to re-raise errors encountered when cancelling the
214
+ scheduled call.
215
+ """
216
+ for call in self._looping_calls:
217
+ try:
218
+ call.stop()
219
+ except Exception:
220
+ if not consumeErrors:
221
+ raise
222
+ self._looping_calls.clear()
223
+
165
224
  def call_later(
166
- self, delay: float, callback: Callable, *args: Any, **kwargs: Any
225
+ self,
226
+ delay: float,
227
+ callback: Callable,
228
+ *args: Any,
229
+ call_later_cancel_on_shutdown: bool = True,
230
+ **kwargs: Any,
167
231
  ) -> IDelayedCall:
168
232
  """Call something later
169
233
 
@@ -175,37 +239,78 @@ class Clock:
175
239
  delay: How long to wait in seconds.
176
240
  callback: Function to call
177
241
  *args: Postional arguments to pass to function.
242
+ call_later_cancel_on_shutdown: Whether this call should be tracked for cleanup during
243
+ shutdown. In general, all calls should be tracked. There may be a use case
244
+ not to track calls with a `timeout` of 0 (or similarly short) since tracking
245
+ them may result in rapid insertions and removals of tracked calls
246
+ unnecessarily. But unless a specific instance of tracking proves to be an
247
+ issue, we can just track all delayed calls.
178
248
  **kwargs: Key arguments to pass to function.
179
249
  """
180
250
 
181
- def wrapped_callback(*args: Any, **kwargs: Any) -> None:
182
- assert context.current_context() is context.SENTINEL_CONTEXT, (
183
- "Expected `call_later` callback from the reactor to start with the sentinel logcontext "
184
- f"but saw {context.current_context()}. In other words, another task shouldn't have "
185
- "leaked their logcontext to us."
186
- )
187
-
188
- # Because this is a callback from the reactor, we will be using the
189
- # `sentinel` log context at this point. We want the function to log with
190
- # some logcontext as we want to know which server the logs came from.
191
- #
192
- # We use `PreserveLoggingContext` to prevent our new `call_later`
193
- # logcontext from finishing as soon as we exit this function, in case `f`
194
- # returns an awaitable/deferred which would continue running and may try to
195
- # restore the `loop_call` context when it's done (because it's trying to
196
- # adhere to the Synapse logcontext rules.)
197
- #
198
- # This also ensures that we return to the `sentinel` context when we exit
199
- # this function and yield control back to the reactor to avoid leaking the
200
- # current logcontext to the reactor (which would then get picked up and
201
- # associated with the next thing the reactor does)
202
- with context.PreserveLoggingContext(context.LoggingContext("call_later")):
203
- # We use `run_in_background` to reset the logcontext after `f` (or the
204
- # awaitable returned by `f`) completes to avoid leaking the current
205
- # logcontext to the reactor
206
- context.run_in_background(callback, *args, **kwargs)
207
-
208
- return self._reactor.callLater(delay, wrapped_callback, *args, **kwargs)
251
+ if self._is_shutdown:
252
+ raise Exception("Cannot start delayed call. Clock has been shutdown")
253
+
254
+ def create_wrapped_callback(
255
+ track_for_shutdown_cancellation: bool,
256
+ ) -> Callable[P, None]:
257
+ def wrapped_callback(*args: Any, **kwargs: Any) -> None:
258
+ assert context.current_context() is context.SENTINEL_CONTEXT, (
259
+ "Expected `call_later` callback from the reactor to start with the sentinel logcontext "
260
+ f"but saw {context.current_context()}. In other words, another task shouldn't have "
261
+ "leaked their logcontext to us."
262
+ )
263
+
264
+ # Because this is a callback from the reactor, we will be using the
265
+ # `sentinel` log context at this point. We want the function to log with
266
+ # some logcontext as we want to know which server the logs came from.
267
+ #
268
+ # We use `PreserveLoggingContext` to prevent our new `call_later`
269
+ # logcontext from finishing as soon as we exit this function, in case `f`
270
+ # returns an awaitable/deferred which would continue running and may try to
271
+ # restore the `loop_call` context when it's done (because it's trying to
272
+ # adhere to the Synapse logcontext rules.)
273
+ #
274
+ # This also ensures that we return to the `sentinel` context when we exit
275
+ # this function and yield control back to the reactor to avoid leaking the
276
+ # current logcontext to the reactor (which would then get picked up and
277
+ # associated with the next thing the reactor does)
278
+ try:
279
+ with context.PreserveLoggingContext(
280
+ context.LoggingContext(
281
+ name="call_later", server_name=self._server_name
282
+ )
283
+ ):
284
+ # We use `run_in_background` to reset the logcontext after `f` (or the
285
+ # awaitable returned by `f`) completes to avoid leaking the current
286
+ # logcontext to the reactor
287
+ context.run_in_background(callback, *args, **kwargs)
288
+ finally:
289
+ if track_for_shutdown_cancellation:
290
+ # We still want to remove the call from the tracking map. Even if
291
+ # the callback raises an exception.
292
+ self._call_id_to_delayed_call.pop(call_id)
293
+
294
+ return wrapped_callback
295
+
296
+ if call_later_cancel_on_shutdown:
297
+ call_id = self._delayed_call_id
298
+ self._delayed_call_id = self._delayed_call_id + 1
299
+
300
+ # We can ignore the lint here since this class is the one location callLater
301
+ # should be called.
302
+ call = self._reactor.callLater(
303
+ delay, create_wrapped_callback(True), *args, **kwargs
304
+ ) # type: ignore[call-later-not-tracked]
305
+ call = DelayedCallWrapper(call, call_id, self)
306
+ self._call_id_to_delayed_call[call_id] = call
307
+ return call
308
+ else:
309
+ # We can ignore the lint here since this class is the one location callLater should
310
+ # be called.
311
+ return self._reactor.callLater(
312
+ delay, create_wrapped_callback(False), *args, **kwargs
313
+ ) # type: ignore[call-later-not-tracked]
209
314
 
210
315
  def cancel_call_later(self, timer: IDelayedCall, ignore_errs: bool = False) -> None:
211
316
  try:
@@ -214,6 +319,24 @@ class Clock:
214
319
  if not ignore_errs:
215
320
  raise
216
321
 
322
+ def cancel_all_delayed_calls(self, ignore_errs: bool = True) -> None:
323
+ """
324
+ Stop all scheduled calls that were marked with `cancel_on_shutdown` when they were created.
325
+
326
+ Args:
327
+ ignore_errs: Whether to re-raise errors encountered when cancelling the
328
+ scheduled call.
329
+ """
330
+ # We make a copy here since calling `cancel()` on a delayed_call
331
+ # will result in the call removing itself from the map mid-iteration.
332
+ for call in list(self._call_id_to_delayed_call.values()):
333
+ try:
334
+ call.cancel()
335
+ except Exception:
336
+ if not ignore_errs:
337
+ raise
338
+ self._call_id_to_delayed_call.clear()
339
+
217
340
  def call_when_running(
218
341
  self,
219
342
  callback: Callable[P, object],
@@ -258,7 +381,9 @@ class Clock:
258
381
  # current logcontext to the reactor (which would then get picked up and
259
382
  # associated with the next thing the reactor does)
260
383
  with context.PreserveLoggingContext(
261
- context.LoggingContext("call_when_running")
384
+ context.LoggingContext(
385
+ name="call_when_running", server_name=self._server_name
386
+ )
262
387
  ):
263
388
  # We use `run_in_background` to reset the logcontext after `f` (or the
264
389
  # awaitable returned by `f`) completes to avoid leaking the current
@@ -276,7 +401,7 @@ class Clock:
276
401
  callback: Callable[P, object],
277
402
  *args: P.args,
278
403
  **kwargs: P.kwargs,
279
- ) -> None:
404
+ ) -> Any:
280
405
  """
281
406
  Add a function to be called when a system event occurs.
282
407
 
@@ -290,6 +415,9 @@ class Clock:
290
415
  callback: Function to call
291
416
  *args: Postional arguments to pass to function.
292
417
  **kwargs: Key arguments to pass to function.
418
+
419
+ Returns:
420
+ an ID that can be used to remove this call with `reactor.removeSystemEventTrigger`.
293
421
  """
294
422
 
295
423
  def wrapped_callback(*args: Any, **kwargs: Any) -> None:
@@ -313,7 +441,11 @@ class Clock:
313
441
  # this function and yield control back to the reactor to avoid leaking the
314
442
  # current logcontext to the reactor (which would then get picked up and
315
443
  # associated with the next thing the reactor does)
316
- with context.PreserveLoggingContext(context.LoggingContext("system_event")):
444
+ with context.PreserveLoggingContext(
445
+ context.LoggingContext(
446
+ name="system_event", server_name=self._server_name
447
+ )
448
+ ):
317
449
  # We use `run_in_background` to reset the logcontext after `f` (or the
318
450
  # awaitable returned by `f`) completes to avoid leaking the current
319
451
  # logcontext to the reactor
@@ -321,6 +453,50 @@ class Clock:
321
453
 
322
454
  # We can ignore the lint here since this class is the one location
323
455
  # `addSystemEventTrigger` should be called.
324
- self._reactor.addSystemEventTrigger(
456
+ return self._reactor.addSystemEventTrigger(
325
457
  phase, event_type, wrapped_callback, *args, **kwargs
326
458
  ) # type: ignore[prefer-synapse-clock-add-system-event-trigger]
459
+
460
+
461
+ @implementer(IDelayedCall)
462
+ class DelayedCallWrapper:
463
+ """Wraps an `IDelayedCall` so that we can intercept the call to `cancel()` and
464
+ properly cleanup the delayed call from the tracking map of the `Clock`.
465
+
466
+ args:
467
+ delayed_call: The actual `IDelayedCall`
468
+ call_id: Unique identifier for this delayed call
469
+ clock: The clock instance tracking this call
470
+ """
471
+
472
+ def __init__(self, delayed_call: IDelayedCall, call_id: int, clock: Clock):
473
+ self.delayed_call = delayed_call
474
+ self.call_id = call_id
475
+ self.clock = clock
476
+
477
+ def cancel(self) -> None:
478
+ """Remove the call from the tracking map and propagate the call to the
479
+ underlying delayed_call.
480
+ """
481
+ self.delayed_call.cancel()
482
+ try:
483
+ self.clock._call_id_to_delayed_call.pop(self.call_id)
484
+ except KeyError:
485
+ # If the delayed call isn't being tracked anymore we can just move on.
486
+ pass
487
+
488
+ def getTime(self) -> float:
489
+ """Propagate the call to the underlying delayed_call."""
490
+ return self.delayed_call.getTime()
491
+
492
+ def delay(self, secondsLater: float) -> None:
493
+ """Propagate the call to the underlying delayed_call."""
494
+ self.delayed_call.delay(secondsLater)
495
+
496
+ def reset(self, secondsFromNow: float) -> None:
497
+ """Propagate the call to the underlying delayed_call."""
498
+ self.delayed_call.reset(secondsFromNow)
499
+
500
+ def active(self) -> bool:
501
+ """Propagate the call to the underlying delayed_call."""
502
+ return self.delayed_call.active()
synapse/util/constants.py CHANGED
@@ -18,3 +18,5 @@
18
18
  # readability and catching bugs.
19
19
  ONE_MINUTE_SECONDS = 60
20
20
  ONE_HOUR_SECONDS = 60 * ONE_MINUTE_SECONDS
21
+
22
+ MILLISECONDS_PER_SECOND = 1000
synapse/util/daemonize.py CHANGED
@@ -32,6 +32,7 @@ from typing import NoReturn, Optional, Type
32
32
  from synapse.logging.context import (
33
33
  LoggingContext,
34
34
  PreserveLoggingContext,
35
+ current_context,
35
36
  )
36
37
 
37
38
 
@@ -149,9 +150,12 @@ def daemonize_process(pid_file: str, logger: logging.Logger, chdir: str = "/") -
149
150
 
150
151
  signal.signal(signal.SIGTERM, sigterm)
151
152
 
153
+ # Copy the `server_name` from the current logcontext
154
+ server_name = current_context().server_name
155
+
152
156
  # Cleanup pid file at exit.
153
157
  def exit() -> None:
154
- with LoggingContext("atexit"):
158
+ with LoggingContext(name="atexit", server_name=server_name):
155
159
  logger.warning("Stopping daemon.")
156
160
  os.remove(pid_file)
157
161
  sys.exit(0)
@@ -20,6 +20,7 @@
20
20
  #
21
21
  import logging
22
22
  from typing import (
23
+ TYPE_CHECKING,
23
24
  Any,
24
25
  Awaitable,
25
26
  Callable,
@@ -36,10 +37,13 @@ from typing_extensions import ParamSpec
36
37
  from twisted.internet import defer
37
38
 
38
39
  from synapse.logging.context import make_deferred_yieldable, run_in_background
39
- from synapse.metrics.background_process_metrics import run_as_background_process
40
40
  from synapse.types import UserID
41
41
  from synapse.util.async_helpers import maybe_awaitable
42
42
 
43
+ if TYPE_CHECKING:
44
+ from synapse.server import HomeServer
45
+
46
+
43
47
  logger = logging.getLogger(__name__)
44
48
 
45
49
 
@@ -58,13 +62,13 @@ class Distributor:
58
62
  model will do for today.
59
63
  """
60
64
 
61
- def __init__(self, server_name: str) -> None:
65
+ def __init__(self, hs: "HomeServer") -> None:
62
66
  """
63
67
  Args:
64
68
  server_name: The homeserver name of the server (used to label metrics)
65
69
  (this should be `hs.hostname`).
66
70
  """
67
- self.server_name = server_name
71
+ self.hs = hs
68
72
  self.signals: Dict[str, Signal] = {}
69
73
  self.pre_registration: Dict[str, List[Callable]] = {}
70
74
 
@@ -97,8 +101,8 @@ class Distributor:
97
101
  if name not in self.signals:
98
102
  raise KeyError("%r does not have a signal named %s" % (self, name))
99
103
 
100
- run_as_background_process(
101
- name, self.server_name, self.signals[name].fire, *args, **kwargs
104
+ self.hs.run_as_background_process(
105
+ name, self.signals[name].fire, *args, **kwargs
102
106
  )
103
107
 
104
108
 
synapse/util/metrics.py CHANGED
@@ -217,7 +217,11 @@ class Measure:
217
217
  else:
218
218
  assert isinstance(curr_context, LoggingContext)
219
219
  parent_context = curr_context
220
- self._logging_context = LoggingContext(str(curr_context), parent_context)
220
+ self._logging_context = LoggingContext(
221
+ name=str(curr_context),
222
+ server_name=self.server_name,
223
+ parent_context=parent_context,
224
+ )
221
225
  self.start: Optional[float] = None
222
226
 
223
227
  def __enter__(self) -> "Measure":
@@ -289,21 +293,46 @@ class DynamicCollectorRegistry(CollectorRegistry):
289
293
 
290
294
  def __init__(self) -> None:
291
295
  super().__init__()
292
- self._pre_update_hooks: Dict[str, Callable[[], None]] = {}
296
+ self._server_name_to_pre_update_hooks: Dict[
297
+ str, Dict[str, Callable[[], None]]
298
+ ] = {}
299
+ """
300
+ Mapping of server name to a mapping of metric name to metric pre-update
301
+ hook
302
+ """
293
303
 
294
304
  def collect(self) -> Generator[Metric, None, None]:
295
305
  """
296
306
  Collects metrics, calling pre-update hooks first.
297
307
  """
298
308
 
299
- for pre_update_hook in self._pre_update_hooks.values():
300
- pre_update_hook()
309
+ for pre_update_hooks in self._server_name_to_pre_update_hooks.values():
310
+ for pre_update_hook in pre_update_hooks.values():
311
+ pre_update_hook()
301
312
 
302
313
  yield from super().collect()
303
314
 
304
- def register_hook(self, metric_name: str, hook: Callable[[], None]) -> None:
315
+ def register_hook(
316
+ self, server_name: str, metric_name: str, hook: Callable[[], None]
317
+ ) -> None:
305
318
  """
306
319
  Registers a hook that is called before metric collection.
307
320
  """
308
321
 
309
- self._pre_update_hooks[metric_name] = hook
322
+ server_hooks = self._server_name_to_pre_update_hooks.setdefault(server_name, {})
323
+ if server_hooks.get(metric_name) is not None:
324
+ # TODO: This should be an `assert` since registering the same metric name
325
+ # multiple times will clobber the old metric.
326
+ # We currently rely on this behaviour as we instantiate multiple
327
+ # `SyncRestServlet`, one per listener, and in the `__init__` we setup a new
328
+ # LruCache.
329
+ # Once the above behaviour is changed, this should be changed to an `assert`.
330
+ logger.error(
331
+ "Metric named %s already registered for server %s",
332
+ metric_name,
333
+ server_name,
334
+ )
335
+ server_hooks[metric_name] = hook
336
+
337
+ def unregister_hooks_for_homeserver(self, server_name: str) -> None:
338
+ self._server_name_to_pre_update_hooks.pop(server_name, None)
@@ -419,4 +419,7 @@ class _PerHostRatelimiter:
419
419
  except KeyError:
420
420
  pass
421
421
 
422
- self.clock.call_later(0.0, start_next_request)
422
+ self.clock.call_later(
423
+ 0.0,
424
+ start_next_request,
425
+ )