You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

509 rivejä
21 KiB

  1. # Copyright 2017 Vector Creations Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """A replication client for use by synapse workers.
  15. """
  16. import logging
  17. from typing import TYPE_CHECKING, Dict, Iterable, Optional, Set, Tuple
  18. from sortedcontainers import SortedList
  19. from twisted.internet import defer
  20. from twisted.internet.defer import Deferred
  21. from synapse.api.constants import EventTypes, Membership, ReceiptTypes
  22. from synapse.federation import send_queue
  23. from synapse.federation.sender import FederationSender
  24. from synapse.logging.context import PreserveLoggingContext, make_deferred_yieldable
  25. from synapse.metrics.background_process_metrics import run_as_background_process
  26. from synapse.replication.tcp.streams import (
  27. AccountDataStream,
  28. DeviceListsStream,
  29. PushersStream,
  30. PushRulesStream,
  31. ReceiptsStream,
  32. ToDeviceStream,
  33. TypingStream,
  34. UnPartialStatedEventStream,
  35. UnPartialStatedRoomStream,
  36. )
  37. from synapse.replication.tcp.streams.events import (
  38. EventsStream,
  39. EventsStreamEventRow,
  40. EventsStreamRow,
  41. )
  42. from synapse.replication.tcp.streams.partial_state import (
  43. UnPartialStatedEventStreamRow,
  44. UnPartialStatedRoomStreamRow,
  45. )
  46. from synapse.types import PersistedEventPosition, ReadReceipt, StreamKeyType, UserID
  47. from synapse.util.async_helpers import Linearizer, timeout_deferred
  48. from synapse.util.metrics import Measure
  49. if TYPE_CHECKING:
  50. from synapse.server import HomeServer
  51. logger = logging.getLogger(__name__)
  52. # How long we allow callers to wait for replication updates before timing out.
  53. _WAIT_FOR_REPLICATION_TIMEOUT_SECONDS = 5
  54. class ReplicationDataHandler:
  55. """Handles incoming stream updates from replication.
  56. This instance notifies the data store about updates. Can be subclassed
  57. to handle updates in additional ways.
  58. """
  59. def __init__(self, hs: "HomeServer"):
  60. self.store = hs.get_datastores().main
  61. self.notifier = hs.get_notifier()
  62. self._reactor = hs.get_reactor()
  63. self._clock = hs.get_clock()
  64. self._streams = hs.get_replication_streams()
  65. self._instance_name = hs.get_instance_name()
  66. self._typing_handler = hs.get_typing_handler()
  67. self._state_storage_controller = hs.get_storage_controllers().state
  68. self._notify_pushers = hs.config.worker.start_pushers
  69. self._pusher_pool = hs.get_pusherpool()
  70. self._presence_handler = hs.get_presence_handler()
  71. self.send_handler: Optional[FederationSenderHandler] = None
  72. if hs.should_send_federation():
  73. self.send_handler = FederationSenderHandler(hs)
  74. # Map from stream and instance to list of deferreds waiting for the stream to
  75. # arrive at a particular position. The lists are sorted by stream position.
  76. self._streams_to_waiters: Dict[
  77. Tuple[str, str], SortedList[Tuple[int, Deferred]]
  78. ] = {}
  79. async def on_rdata(
  80. self, stream_name: str, instance_name: str, token: int, rows: list
  81. ) -> None:
  82. """Called to handle a batch of replication data with a given stream token.
  83. By default, this just pokes the data store. Can be overridden in subclasses to
  84. handle more.
  85. Args:
  86. stream_name: name of the replication stream for this batch of rows
  87. instance_name: the instance that wrote the rows.
  88. token: stream token for this batch of rows
  89. rows: a list of Stream.ROW_TYPE objects as returned by Stream.parse_row.
  90. """
  91. self.store.process_replication_rows(stream_name, instance_name, token, rows)
  92. # NOTE: this must be called after process_replication_rows to ensure any
  93. # cache invalidations are first handled before any stream ID advances.
  94. self.store.process_replication_position(stream_name, instance_name, token)
  95. if self.send_handler:
  96. await self.send_handler.process_replication_rows(stream_name, token, rows)
  97. if stream_name == TypingStream.NAME:
  98. self._typing_handler.process_replication_rows(token, rows)
  99. self.notifier.on_new_event(
  100. StreamKeyType.TYPING, token, rooms=[row.room_id for row in rows]
  101. )
  102. elif stream_name == PushRulesStream.NAME:
  103. self.notifier.on_new_event(
  104. StreamKeyType.PUSH_RULES, token, users=[row.user_id for row in rows]
  105. )
  106. elif stream_name in AccountDataStream.NAME:
  107. self.notifier.on_new_event(
  108. StreamKeyType.ACCOUNT_DATA, token, users=[row.user_id for row in rows]
  109. )
  110. elif stream_name == ReceiptsStream.NAME:
  111. new_token = self.store.get_max_receipt_stream_id()
  112. self.notifier.on_new_event(
  113. StreamKeyType.RECEIPT, new_token, rooms=[row.room_id for row in rows]
  114. )
  115. await self._pusher_pool.on_new_receipts({row.user_id for row in rows})
  116. elif stream_name == ToDeviceStream.NAME:
  117. entities = [row.entity for row in rows if row.entity.startswith("@")]
  118. if entities:
  119. self.notifier.on_new_event(
  120. StreamKeyType.TO_DEVICE, token, users=entities
  121. )
  122. elif stream_name == DeviceListsStream.NAME:
  123. all_room_ids: Set[str] = set()
  124. for row in rows:
  125. if row.entity.startswith("@") and not row.is_signature:
  126. room_ids = await self.store.get_rooms_for_user(row.entity)
  127. all_room_ids.update(room_ids)
  128. self.notifier.on_new_event(
  129. StreamKeyType.DEVICE_LIST, token, rooms=all_room_ids
  130. )
  131. elif stream_name == PushersStream.NAME:
  132. for row in rows:
  133. if row.deleted:
  134. self.stop_pusher(row.user_id, row.app_id, row.pushkey)
  135. else:
  136. await self.process_pusher_change(
  137. row.user_id, row.app_id, row.pushkey
  138. )
  139. elif stream_name == EventsStream.NAME:
  140. # We shouldn't get multiple rows per token for events stream, so
  141. # we don't need to optimise this for multiple rows.
  142. for row in rows:
  143. if row.type != EventsStreamEventRow.TypeId:
  144. # The row's data is an `EventsStreamCurrentStateRow`.
  145. # When we recompute the current state of a room based on forward
  146. # extremities (see `update_current_state`), no new events are
  147. # persisted, so we must poke the replication callbacks ourselves.
  148. # This functionality is used when finishing up a partial state join.
  149. self.notifier.notify_replication()
  150. continue
  151. assert isinstance(row, EventsStreamRow)
  152. assert isinstance(row.data, EventsStreamEventRow)
  153. if row.data.rejected:
  154. continue
  155. extra_users: Tuple[UserID, ...] = ()
  156. if row.data.type == EventTypes.Member and row.data.state_key:
  157. extra_users = (UserID.from_string(row.data.state_key),)
  158. max_token = self.store.get_room_max_token()
  159. event_pos = PersistedEventPosition(instance_name, token)
  160. event_entry = self.notifier.create_pending_room_event_entry(
  161. event_pos,
  162. extra_users,
  163. row.data.room_id,
  164. row.data.type,
  165. row.data.state_key,
  166. row.data.membership,
  167. )
  168. await self.notifier.notify_new_room_events(
  169. [(event_entry, row.data.event_id)], max_token
  170. )
  171. # If this event is a join, make a note of it so we have an accurate
  172. # cross-worker room rate limit.
  173. # TODO: Erik said we should exclude rows that came from ex_outliers
  174. # here, but I don't see how we can determine that. I guess we could
  175. # add a flag to row.data?
  176. if (
  177. row.data.type == EventTypes.Member
  178. and row.data.membership == Membership.JOIN
  179. and not row.data.outlier
  180. ):
  181. # TODO retrieve the previous state, and exclude join -> join transitions
  182. self.notifier.notify_user_joined_room(
  183. row.data.event_id, row.data.room_id
  184. )
  185. # If this is a server ACL event, clear the cache in the storage controller.
  186. if row.data.type == EventTypes.ServerACL:
  187. self._state_storage_controller.get_server_acl_for_room.invalidate(
  188. (row.data.room_id,)
  189. )
  190. elif stream_name == UnPartialStatedRoomStream.NAME:
  191. for row in rows:
  192. assert isinstance(row, UnPartialStatedRoomStreamRow)
  193. # Wake up any tasks waiting for the room to be un-partial-stated.
  194. self._state_storage_controller.notify_room_un_partial_stated(
  195. row.room_id
  196. )
  197. await self.notifier.on_un_partial_stated_room(row.room_id, token)
  198. elif stream_name == UnPartialStatedEventStream.NAME:
  199. for row in rows:
  200. assert isinstance(row, UnPartialStatedEventStreamRow)
  201. # Wake up any tasks waiting for the event to be un-partial-stated.
  202. self._state_storage_controller.notify_event_un_partial_stated(
  203. row.event_id
  204. )
  205. await self._presence_handler.process_replication_rows(
  206. stream_name, instance_name, token, rows
  207. )
  208. # Notify any waiting deferreds. The list is ordered by position so we
  209. # just iterate through the list until we reach a position that is
  210. # greater than the received row position.
  211. waiting_list = self._streams_to_waiters.get((stream_name, instance_name))
  212. if not waiting_list:
  213. return
  214. # Index of first item with a position after the current token, i.e we
  215. # have called all deferreds before this index. If not overwritten by
  216. # loop below means either a) no items in list so no-op or b) all items
  217. # in list were called and so the list should be cleared. Setting it to
  218. # `len(list)` works for both cases.
  219. index_of_first_deferred_not_called = len(waiting_list)
  220. # We don't fire the deferreds until after we finish iterating over the
  221. # list, to avoid the list changing when we fire the deferreds.
  222. deferreds_to_callback = []
  223. for idx, (position, deferred) in enumerate(waiting_list):
  224. if position <= token:
  225. deferreds_to_callback.append(deferred)
  226. else:
  227. # The list is sorted by position so we don't need to continue
  228. # checking any further entries in the list.
  229. index_of_first_deferred_not_called = idx
  230. break
  231. # Drop all entries in the waiting list that were called in the above
  232. # loop. (This maintains the order so no need to resort)
  233. del waiting_list[:index_of_first_deferred_not_called]
  234. for deferred in deferreds_to_callback:
  235. try:
  236. with PreserveLoggingContext():
  237. deferred.callback(None)
  238. except Exception:
  239. # The deferred has been cancelled or timed out.
  240. pass
  241. async def on_position(
  242. self, stream_name: str, instance_name: str, token: int
  243. ) -> None:
  244. await self.on_rdata(stream_name, instance_name, token, [])
  245. # We poke the generic "replication" notifier to wake anything up that
  246. # may be streaming.
  247. self.notifier.notify_replication()
  248. async def wait_for_stream_position(
  249. self,
  250. instance_name: str,
  251. stream_name: str,
  252. position: int,
  253. ) -> None:
  254. """Wait until this instance has received updates up to and including
  255. the given stream position.
  256. Args:
  257. instance_name
  258. stream_name
  259. position
  260. """
  261. if instance_name == self._instance_name:
  262. # We don't get told about updates written by this process, and
  263. # anyway in that case we don't need to wait.
  264. return
  265. current_position = self._streams[stream_name].current_token(instance_name)
  266. if position <= current_position:
  267. # We're already past the position
  268. return
  269. # Create a new deferred that times out after N seconds, as we don't want
  270. # to wedge here forever.
  271. deferred: "Deferred[None]" = Deferred()
  272. deferred = timeout_deferred(
  273. deferred, _WAIT_FOR_REPLICATION_TIMEOUT_SECONDS, self._reactor
  274. )
  275. waiting_list = self._streams_to_waiters.setdefault(
  276. (stream_name, instance_name), SortedList(key=lambda t: t[0])
  277. )
  278. waiting_list.add((position, deferred))
  279. # We measure here to get in flight counts and average waiting time.
  280. with Measure(self._clock, "repl.wait_for_stream_position"):
  281. logger.info(
  282. "Waiting for repl stream %r to reach %s (%s); currently at: %s",
  283. stream_name,
  284. position,
  285. instance_name,
  286. current_position,
  287. )
  288. try:
  289. await make_deferred_yieldable(deferred)
  290. except defer.TimeoutError:
  291. logger.warning(
  292. "Timed out waiting for repl stream %r to reach %s (%s)"
  293. "; currently at: %s",
  294. stream_name,
  295. position,
  296. instance_name,
  297. self._streams[stream_name].current_token(instance_name),
  298. )
  299. return
  300. logger.info(
  301. "Finished waiting for repl stream %r to reach %s (%s)",
  302. stream_name,
  303. position,
  304. instance_name,
  305. )
  306. def stop_pusher(self, user_id: str, app_id: str, pushkey: str) -> None:
  307. if not self._notify_pushers:
  308. return
  309. key = "%s:%s" % (app_id, pushkey)
  310. pushers_for_user = self._pusher_pool.pushers.get(user_id, {})
  311. pusher = pushers_for_user.pop(key, None)
  312. if pusher is None:
  313. return
  314. logger.info("Stopping pusher %r / %r", user_id, key)
  315. pusher.on_stop()
  316. async def process_pusher_change(
  317. self, user_id: str, app_id: str, pushkey: str
  318. ) -> None:
  319. if not self._notify_pushers:
  320. return
  321. key = "%s:%s" % (app_id, pushkey)
  322. logger.info("Starting pusher %r / %r", user_id, key)
  323. await self._pusher_pool.process_pusher_change_by_id(app_id, pushkey, user_id)
  324. class FederationSenderHandler:
  325. """Processes the fedration replication stream
  326. This class is only instantiate on the worker responsible for sending outbound
  327. federation transactions. It receives rows from the replication stream and forwards
  328. the appropriate entries to the FederationSender class.
  329. """
  330. def __init__(self, hs: "HomeServer"):
  331. assert hs.should_send_federation()
  332. self.store = hs.get_datastores().main
  333. self._is_mine_id = hs.is_mine_id
  334. self._hs = hs
  335. # We need to make a temporary value to ensure that mypy picks up the
  336. # right type. We know we should have a federation sender instance since
  337. # `should_send_federation` is True.
  338. sender = hs.get_federation_sender()
  339. assert isinstance(sender, FederationSender)
  340. self.federation_sender = sender
  341. # Stores the latest position in the federation stream we've gotten up
  342. # to. This is always set before we use it.
  343. self.federation_position: Optional[int] = None
  344. self._fed_position_linearizer = Linearizer(name="_fed_position_linearizer")
  345. async def process_replication_rows(
  346. self, stream_name: str, token: int, rows: list
  347. ) -> None:
  348. # The federation stream contains things that we want to send out, e.g.
  349. # presence, typing, etc.
  350. if stream_name == "federation":
  351. await send_queue.process_rows_for_federation(self.federation_sender, rows)
  352. await self.update_token(token)
  353. # ... and when new receipts happen
  354. elif stream_name == ReceiptsStream.NAME:
  355. await self._on_new_receipts(rows)
  356. # ... as well as device updates and messages
  357. elif stream_name == DeviceListsStream.NAME:
  358. # The entities are either user IDs (starting with '@') whose devices
  359. # have changed, or remote servers that we need to tell about
  360. # changes.
  361. hosts = {
  362. row.entity
  363. for row in rows
  364. if not row.entity.startswith("@") and not row.is_signature
  365. }
  366. await self.federation_sender.send_device_messages(hosts, immediate=False)
  367. elif stream_name == ToDeviceStream.NAME:
  368. # The to_device stream includes stuff to be pushed to both local
  369. # clients and remote servers, so we ignore entities that start with
  370. # '@' (since they'll be local users rather than destinations).
  371. hosts = {row.entity for row in rows if not row.entity.startswith("@")}
  372. await self.federation_sender.send_device_messages(hosts)
  373. async def _on_new_receipts(
  374. self, rows: Iterable[ReceiptsStream.ReceiptsStreamRow]
  375. ) -> None:
  376. """
  377. Args:
  378. rows: new receipts to be processed
  379. """
  380. for receipt in rows:
  381. # we only want to send on receipts for our own users
  382. if not self._is_mine_id(receipt.user_id):
  383. continue
  384. # Private read receipts never get sent over federation.
  385. if receipt.receipt_type == ReceiptTypes.READ_PRIVATE:
  386. continue
  387. receipt_info = ReadReceipt(
  388. receipt.room_id,
  389. receipt.receipt_type,
  390. receipt.user_id,
  391. [receipt.event_id],
  392. thread_id=receipt.thread_id,
  393. data=receipt.data,
  394. )
  395. await self.federation_sender.send_read_receipt(receipt_info)
  396. async def update_token(self, token: int) -> None:
  397. """Update the record of where we have processed to in the federation stream.
  398. Called after we have processed a an update received over replication. Sends
  399. a FEDERATION_ACK back to the master, and stores the token that we have processed
  400. in `federation_stream_position` so that we can restart where we left off.
  401. """
  402. self.federation_position = token
  403. # We save and send the ACK to master asynchronously, so we don't block
  404. # processing on persistence. We don't need to do this operation for
  405. # every single RDATA we receive, we just need to do it periodically.
  406. if self._fed_position_linearizer.is_queued(None):
  407. # There is already a task queued up to save and send the token, so
  408. # no need to queue up another task.
  409. return
  410. run_as_background_process("_save_and_send_ack", self._save_and_send_ack)
  411. async def _save_and_send_ack(self) -> None:
  412. """Save the current federation position in the database and send an ACK
  413. to master with where we're up to.
  414. """
  415. # We should only be calling this once we've got a token.
  416. assert self.federation_position is not None
  417. try:
  418. # We linearize here to ensure we don't have races updating the token
  419. #
  420. # XXX this appears to be redundant, since the ReplicationCommandHandler
  421. # has a linearizer which ensures that we only process one line of
  422. # replication data at a time. Should we remove it, or is it doing useful
  423. # service for robustness? Or could we replace it with an assertion that
  424. # we're not being re-entered?
  425. async with self._fed_position_linearizer.queue(None):
  426. # We persist and ack the same position, so we take a copy of it
  427. # here as otherwise it can get modified from underneath us.
  428. current_position = self.federation_position
  429. await self.store.update_federation_out_pos(
  430. "federation", current_position
  431. )
  432. # We ACK this token over replication so that the master can drop
  433. # its in memory queues
  434. self._hs.get_replication_command_handler().send_federation_ack(
  435. current_position
  436. )
  437. except Exception:
  438. logger.exception("Error updating federation stream position")