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.
 
 
 
 
 
 

967 lines
38 KiB

  1. #!/usr/bin/env python
  2. # Copyright 2016 OpenMarket Ltd
  3. # Copyright 2020 The Matrix.org Foundation C.I.C.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import contextlib
  17. import logging
  18. import sys
  19. from typing import Dict, Iterable, Optional, Set
  20. from typing_extensions import ContextManager
  21. from twisted.internet import address
  22. from twisted.web.resource import IResource
  23. from twisted.web.server import Request
  24. import synapse
  25. import synapse.events
  26. from synapse.api.errors import HttpResponseException, RequestSendFailed, SynapseError
  27. from synapse.api.urls import (
  28. CLIENT_API_PREFIX,
  29. FEDERATION_PREFIX,
  30. LEGACY_MEDIA_PREFIX,
  31. MEDIA_PREFIX,
  32. SERVER_KEY_V2_PREFIX,
  33. )
  34. from synapse.app import _base
  35. from synapse.app._base import register_start
  36. from synapse.config._base import ConfigError
  37. from synapse.config.homeserver import HomeServerConfig
  38. from synapse.config.logger import setup_logging
  39. from synapse.config.server import ListenerConfig
  40. from synapse.federation import send_queue
  41. from synapse.federation.transport.server import TransportLayerServer
  42. from synapse.handlers.presence import (
  43. BasePresenceHandler,
  44. PresenceState,
  45. get_interested_parties,
  46. )
  47. from synapse.http.server import JsonResource, OptionsResource
  48. from synapse.http.servlet import RestServlet, parse_json_object_from_request
  49. from synapse.http.site import SynapseSite
  50. from synapse.logging.context import LoggingContext
  51. from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy
  52. from synapse.metrics.background_process_metrics import run_as_background_process
  53. from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource
  54. from synapse.replication.http.presence import (
  55. ReplicationBumpPresenceActiveTime,
  56. ReplicationPresenceSetState,
  57. )
  58. from synapse.replication.slave.storage._base import BaseSlavedStore
  59. from synapse.replication.slave.storage.account_data import SlavedAccountDataStore
  60. from synapse.replication.slave.storage.appservice import SlavedApplicationServiceStore
  61. from synapse.replication.slave.storage.client_ips import SlavedClientIpStore
  62. from synapse.replication.slave.storage.deviceinbox import SlavedDeviceInboxStore
  63. from synapse.replication.slave.storage.devices import SlavedDeviceStore
  64. from synapse.replication.slave.storage.directory import DirectoryStore
  65. from synapse.replication.slave.storage.events import SlavedEventStore
  66. from synapse.replication.slave.storage.filtering import SlavedFilteringStore
  67. from synapse.replication.slave.storage.groups import SlavedGroupServerStore
  68. from synapse.replication.slave.storage.keys import SlavedKeyStore
  69. from synapse.replication.slave.storage.presence import SlavedPresenceStore
  70. from synapse.replication.slave.storage.profile import SlavedProfileStore
  71. from synapse.replication.slave.storage.push_rule import SlavedPushRuleStore
  72. from synapse.replication.slave.storage.pushers import SlavedPusherStore
  73. from synapse.replication.slave.storage.receipts import SlavedReceiptsStore
  74. from synapse.replication.slave.storage.registration import SlavedRegistrationStore
  75. from synapse.replication.slave.storage.room import RoomStore
  76. from synapse.replication.slave.storage.transactions import SlavedTransactionStore
  77. from synapse.replication.tcp.client import ReplicationDataHandler
  78. from synapse.replication.tcp.commands import ClearUserSyncsCommand
  79. from synapse.replication.tcp.streams import (
  80. AccountDataStream,
  81. DeviceListsStream,
  82. GroupServerStream,
  83. PresenceStream,
  84. PushersStream,
  85. PushRulesStream,
  86. ReceiptsStream,
  87. TagAccountDataStream,
  88. ToDeviceStream,
  89. )
  90. from synapse.rest.admin import register_servlets_for_media_repo
  91. from synapse.rest.client.v1 import events, login, room
  92. from synapse.rest.client.v1.initial_sync import InitialSyncRestServlet
  93. from synapse.rest.client.v1.profile import (
  94. ProfileAvatarURLRestServlet,
  95. ProfileDisplaynameRestServlet,
  96. ProfileRestServlet,
  97. )
  98. from synapse.rest.client.v1.push_rule import PushRuleRestServlet
  99. from synapse.rest.client.v1.voip import VoipRestServlet
  100. from synapse.rest.client.v2_alpha import (
  101. account_data,
  102. groups,
  103. read_marker,
  104. receipts,
  105. room_keys,
  106. sync,
  107. tags,
  108. user_directory,
  109. )
  110. from synapse.rest.client.v2_alpha._base import client_patterns
  111. from synapse.rest.client.v2_alpha.account import ThreepidRestServlet
  112. from synapse.rest.client.v2_alpha.account_data import (
  113. AccountDataServlet,
  114. RoomAccountDataServlet,
  115. )
  116. from synapse.rest.client.v2_alpha.devices import DevicesRestServlet
  117. from synapse.rest.client.v2_alpha.keys import (
  118. KeyChangesServlet,
  119. KeyQueryServlet,
  120. OneTimeKeyServlet,
  121. )
  122. from synapse.rest.client.v2_alpha.register import RegisterRestServlet
  123. from synapse.rest.client.v2_alpha.sendtodevice import SendToDeviceRestServlet
  124. from synapse.rest.client.versions import VersionsRestServlet
  125. from synapse.rest.health import HealthResource
  126. from synapse.rest.key.v2 import KeyApiV2Resource
  127. from synapse.rest.synapse.client import build_synapse_client_resource_tree
  128. from synapse.server import HomeServer, cache_in_self
  129. from synapse.storage.databases.main.censor_events import CensorEventsStore
  130. from synapse.storage.databases.main.client_ips import ClientIpWorkerStore
  131. from synapse.storage.databases.main.e2e_room_keys import EndToEndRoomKeyStore
  132. from synapse.storage.databases.main.media_repository import MediaRepositoryStore
  133. from synapse.storage.databases.main.metrics import ServerMetricsStore
  134. from synapse.storage.databases.main.monthly_active_users import (
  135. MonthlyActiveUsersWorkerStore,
  136. )
  137. from synapse.storage.databases.main.presence import UserPresenceState
  138. from synapse.storage.databases.main.search import SearchWorkerStore
  139. from synapse.storage.databases.main.stats import StatsStore
  140. from synapse.storage.databases.main.transactions import TransactionWorkerStore
  141. from synapse.storage.databases.main.ui_auth import UIAuthWorkerStore
  142. from synapse.storage.databases.main.user_directory import UserDirectoryStore
  143. from synapse.types import ReadReceipt
  144. from synapse.util.async_helpers import Linearizer
  145. from synapse.util.httpresourcetree import create_resource_tree
  146. from synapse.util.versionstring import get_version_string
  147. logger = logging.getLogger("synapse.app.generic_worker")
  148. class PresenceStatusStubServlet(RestServlet):
  149. """If presence is disabled this servlet can be used to stub out setting
  150. presence status.
  151. """
  152. PATTERNS = client_patterns("/presence/(?P<user_id>[^/]*)/status")
  153. def __init__(self, hs):
  154. super().__init__()
  155. self.auth = hs.get_auth()
  156. async def on_GET(self, request, user_id):
  157. await self.auth.get_user_by_req(request)
  158. return 200, {"presence": "offline"}
  159. async def on_PUT(self, request, user_id):
  160. await self.auth.get_user_by_req(request)
  161. return 200, {}
  162. class KeyUploadServlet(RestServlet):
  163. """An implementation of the `KeyUploadServlet` that responds to read only
  164. requests, but otherwise proxies through to the master instance.
  165. """
  166. PATTERNS = client_patterns("/keys/upload(/(?P<device_id>[^/]+))?$")
  167. def __init__(self, hs):
  168. """
  169. Args:
  170. hs (synapse.server.HomeServer): server
  171. """
  172. super().__init__()
  173. self.auth = hs.get_auth()
  174. self.store = hs.get_datastore()
  175. self.http_client = hs.get_simple_http_client()
  176. self.main_uri = hs.config.worker_main_http_uri
  177. async def on_POST(self, request: Request, device_id: Optional[str]):
  178. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  179. user_id = requester.user.to_string()
  180. body = parse_json_object_from_request(request)
  181. if device_id is not None:
  182. # passing the device_id here is deprecated; however, we allow it
  183. # for now for compatibility with older clients.
  184. if requester.device_id is not None and device_id != requester.device_id:
  185. logger.warning(
  186. "Client uploading keys for a different device "
  187. "(logged in as %s, uploading for %s)",
  188. requester.device_id,
  189. device_id,
  190. )
  191. else:
  192. device_id = requester.device_id
  193. if device_id is None:
  194. raise SynapseError(
  195. 400, "To upload keys, you must pass device_id when authenticating"
  196. )
  197. if body:
  198. # They're actually trying to upload something, proxy to main synapse.
  199. # Proxy headers from the original request, such as the auth headers
  200. # (in case the access token is there) and the original IP /
  201. # User-Agent of the request.
  202. headers = {
  203. header: request.requestHeaders.getRawHeaders(header, [])
  204. for header in (b"Authorization", b"User-Agent")
  205. }
  206. # Add the previous hop to the X-Forwarded-For header.
  207. x_forwarded_for = request.requestHeaders.getRawHeaders(
  208. b"X-Forwarded-For", []
  209. )
  210. # we use request.client here, since we want the previous hop, not the
  211. # original client (as returned by request.getClientAddress()).
  212. if isinstance(request.client, (address.IPv4Address, address.IPv6Address)):
  213. previous_host = request.client.host.encode("ascii")
  214. # If the header exists, add to the comma-separated list of the first
  215. # instance of the header. Otherwise, generate a new header.
  216. if x_forwarded_for:
  217. x_forwarded_for = [
  218. x_forwarded_for[0] + b", " + previous_host
  219. ] + x_forwarded_for[1:]
  220. else:
  221. x_forwarded_for = [previous_host]
  222. headers[b"X-Forwarded-For"] = x_forwarded_for
  223. # Replicate the original X-Forwarded-Proto header. Note that
  224. # XForwardedForRequest overrides isSecure() to give us the original protocol
  225. # used by the client, as opposed to the protocol used by our upstream proxy
  226. # - which is what we want here.
  227. headers[b"X-Forwarded-Proto"] = [
  228. b"https" if request.isSecure() else b"http"
  229. ]
  230. try:
  231. result = await self.http_client.post_json_get_json(
  232. self.main_uri + request.uri.decode("ascii"), body, headers=headers
  233. )
  234. except HttpResponseException as e:
  235. raise e.to_synapse_error() from e
  236. except RequestSendFailed as e:
  237. raise SynapseError(502, "Failed to talk to master") from e
  238. return 200, result
  239. else:
  240. # Just interested in counts.
  241. result = await self.store.count_e2e_one_time_keys(user_id, device_id)
  242. return 200, {"one_time_key_counts": result}
  243. class _NullContextManager(ContextManager[None]):
  244. """A context manager which does nothing."""
  245. def __exit__(self, exc_type, exc_val, exc_tb):
  246. pass
  247. UPDATE_SYNCING_USERS_MS = 10 * 1000
  248. class GenericWorkerPresence(BasePresenceHandler):
  249. def __init__(self, hs):
  250. super().__init__(hs)
  251. self.hs = hs
  252. self.is_mine_id = hs.is_mine_id
  253. self.presence_router = hs.get_presence_router()
  254. self._presence_enabled = hs.config.use_presence
  255. # The number of ongoing syncs on this process, by user id.
  256. # Empty if _presence_enabled is false.
  257. self._user_to_num_current_syncs = {} # type: Dict[str, int]
  258. self.notifier = hs.get_notifier()
  259. self.instance_id = hs.get_instance_id()
  260. # user_id -> last_sync_ms. Lists the users that have stopped syncing
  261. # but we haven't notified the master of that yet
  262. self.users_going_offline = {}
  263. self._bump_active_client = ReplicationBumpPresenceActiveTime.make_client(hs)
  264. self._set_state_client = ReplicationPresenceSetState.make_client(hs)
  265. self._send_stop_syncing_loop = self.clock.looping_call(
  266. self.send_stop_syncing, UPDATE_SYNCING_USERS_MS
  267. )
  268. self._busy_presence_enabled = hs.config.experimental.msc3026_enabled
  269. hs.get_reactor().addSystemEventTrigger(
  270. "before",
  271. "shutdown",
  272. run_as_background_process,
  273. "generic_presence.on_shutdown",
  274. self._on_shutdown,
  275. )
  276. def _on_shutdown(self):
  277. if self._presence_enabled:
  278. self.hs.get_tcp_replication().send_command(
  279. ClearUserSyncsCommand(self.instance_id)
  280. )
  281. def send_user_sync(self, user_id, is_syncing, last_sync_ms):
  282. if self._presence_enabled:
  283. self.hs.get_tcp_replication().send_user_sync(
  284. self.instance_id, user_id, is_syncing, last_sync_ms
  285. )
  286. def mark_as_coming_online(self, user_id):
  287. """A user has started syncing. Send a UserSync to the master, unless they
  288. had recently stopped syncing.
  289. Args:
  290. user_id (str)
  291. """
  292. going_offline = self.users_going_offline.pop(user_id, None)
  293. if not going_offline:
  294. # Safe to skip because we haven't yet told the master they were offline
  295. self.send_user_sync(user_id, True, self.clock.time_msec())
  296. def mark_as_going_offline(self, user_id):
  297. """A user has stopped syncing. We wait before notifying the master as
  298. its likely they'll come back soon. This allows us to avoid sending
  299. a stopped syncing immediately followed by a started syncing notification
  300. to the master
  301. Args:
  302. user_id (str)
  303. """
  304. self.users_going_offline[user_id] = self.clock.time_msec()
  305. def send_stop_syncing(self):
  306. """Check if there are any users who have stopped syncing a while ago
  307. and haven't come back yet. If there are poke the master about them.
  308. """
  309. now = self.clock.time_msec()
  310. for user_id, last_sync_ms in list(self.users_going_offline.items()):
  311. if now - last_sync_ms > UPDATE_SYNCING_USERS_MS:
  312. self.users_going_offline.pop(user_id, None)
  313. self.send_user_sync(user_id, False, last_sync_ms)
  314. async def user_syncing(
  315. self, user_id: str, affect_presence: bool
  316. ) -> ContextManager[None]:
  317. """Record that a user is syncing.
  318. Called by the sync and events servlets to record that a user has connected to
  319. this worker and is waiting for some events.
  320. """
  321. if not affect_presence or not self._presence_enabled:
  322. return _NullContextManager()
  323. curr_sync = self._user_to_num_current_syncs.get(user_id, 0)
  324. self._user_to_num_current_syncs[user_id] = curr_sync + 1
  325. # If we went from no in flight sync to some, notify replication
  326. if self._user_to_num_current_syncs[user_id] == 1:
  327. self.mark_as_coming_online(user_id)
  328. def _end():
  329. # We check that the user_id is in user_to_num_current_syncs because
  330. # user_to_num_current_syncs may have been cleared if we are
  331. # shutting down.
  332. if user_id in self._user_to_num_current_syncs:
  333. self._user_to_num_current_syncs[user_id] -= 1
  334. # If we went from one in flight sync to non, notify replication
  335. if self._user_to_num_current_syncs[user_id] == 0:
  336. self.mark_as_going_offline(user_id)
  337. @contextlib.contextmanager
  338. def _user_syncing():
  339. try:
  340. yield
  341. finally:
  342. _end()
  343. return _user_syncing()
  344. async def notify_from_replication(self, states, stream_id):
  345. parties = await get_interested_parties(self.store, self.presence_router, states)
  346. room_ids_to_states, users_to_states = parties
  347. self.notifier.on_new_event(
  348. "presence_key",
  349. stream_id,
  350. rooms=room_ids_to_states.keys(),
  351. users=users_to_states.keys(),
  352. )
  353. async def process_replication_rows(self, token, rows):
  354. states = [
  355. UserPresenceState(
  356. row.user_id,
  357. row.state,
  358. row.last_active_ts,
  359. row.last_federation_update_ts,
  360. row.last_user_sync_ts,
  361. row.status_msg,
  362. row.currently_active,
  363. )
  364. for row in rows
  365. ]
  366. for state in states:
  367. self.user_to_current_state[state.user_id] = state
  368. stream_id = token
  369. await self.notify_from_replication(states, stream_id)
  370. def get_currently_syncing_users_for_replication(self) -> Iterable[str]:
  371. return [
  372. user_id
  373. for user_id, count in self._user_to_num_current_syncs.items()
  374. if count > 0
  375. ]
  376. async def set_state(self, target_user, state, ignore_status_msg=False):
  377. """Set the presence state of the user."""
  378. presence = state["presence"]
  379. valid_presence = (
  380. PresenceState.ONLINE,
  381. PresenceState.UNAVAILABLE,
  382. PresenceState.OFFLINE,
  383. PresenceState.BUSY,
  384. )
  385. if presence not in valid_presence or (
  386. presence == PresenceState.BUSY and not self._busy_presence_enabled
  387. ):
  388. raise SynapseError(400, "Invalid presence state")
  389. user_id = target_user.to_string()
  390. # If presence is disabled, no-op
  391. if not self.hs.config.use_presence:
  392. return
  393. # Proxy request to master
  394. await self._set_state_client(
  395. user_id=user_id, state=state, ignore_status_msg=ignore_status_msg
  396. )
  397. async def bump_presence_active_time(self, user):
  398. """We've seen the user do something that indicates they're interacting
  399. with the app.
  400. """
  401. # If presence is disabled, no-op
  402. if not self.hs.config.use_presence:
  403. return
  404. # Proxy request to master
  405. user_id = user.to_string()
  406. await self._bump_active_client(user_id=user_id)
  407. class GenericWorkerSlavedStore(
  408. # FIXME(#3714): We need to add UserDirectoryStore as we write directly
  409. # rather than going via the correct worker.
  410. UserDirectoryStore,
  411. StatsStore,
  412. UIAuthWorkerStore,
  413. EndToEndRoomKeyStore,
  414. SlavedDeviceInboxStore,
  415. SlavedDeviceStore,
  416. SlavedReceiptsStore,
  417. SlavedPushRuleStore,
  418. SlavedGroupServerStore,
  419. SlavedAccountDataStore,
  420. SlavedPusherStore,
  421. CensorEventsStore,
  422. ClientIpWorkerStore,
  423. SlavedEventStore,
  424. SlavedKeyStore,
  425. RoomStore,
  426. DirectoryStore,
  427. SlavedApplicationServiceStore,
  428. SlavedRegistrationStore,
  429. SlavedTransactionStore,
  430. SlavedProfileStore,
  431. SlavedClientIpStore,
  432. SlavedPresenceStore,
  433. SlavedFilteringStore,
  434. MonthlyActiveUsersWorkerStore,
  435. MediaRepositoryStore,
  436. ServerMetricsStore,
  437. SearchWorkerStore,
  438. TransactionWorkerStore,
  439. BaseSlavedStore,
  440. ):
  441. pass
  442. class GenericWorkerServer(HomeServer):
  443. DATASTORE_CLASS = GenericWorkerSlavedStore
  444. def _listen_http(self, listener_config: ListenerConfig):
  445. port = listener_config.port
  446. bind_addresses = listener_config.bind_addresses
  447. assert listener_config.http_options is not None
  448. site_tag = listener_config.http_options.tag
  449. if site_tag is None:
  450. site_tag = port
  451. # We always include a health resource.
  452. resources = {"/health": HealthResource()} # type: Dict[str, IResource]
  453. for res in listener_config.http_options.resources:
  454. for name in res.names:
  455. if name == "metrics":
  456. resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
  457. elif name == "client":
  458. resource = JsonResource(self, canonical_json=False)
  459. RegisterRestServlet(self).register(resource)
  460. login.register_servlets(self, resource)
  461. ThreepidRestServlet(self).register(resource)
  462. DevicesRestServlet(self).register(resource)
  463. KeyQueryServlet(self).register(resource)
  464. OneTimeKeyServlet(self).register(resource)
  465. KeyChangesServlet(self).register(resource)
  466. VoipRestServlet(self).register(resource)
  467. PushRuleRestServlet(self).register(resource)
  468. VersionsRestServlet(self).register(resource)
  469. ProfileAvatarURLRestServlet(self).register(resource)
  470. ProfileDisplaynameRestServlet(self).register(resource)
  471. ProfileRestServlet(self).register(resource)
  472. KeyUploadServlet(self).register(resource)
  473. AccountDataServlet(self).register(resource)
  474. RoomAccountDataServlet(self).register(resource)
  475. sync.register_servlets(self, resource)
  476. events.register_servlets(self, resource)
  477. room.register_servlets(self, resource, True)
  478. room.register_deprecated_servlets(self, resource)
  479. InitialSyncRestServlet(self).register(resource)
  480. room_keys.register_servlets(self, resource)
  481. tags.register_servlets(self, resource)
  482. account_data.register_servlets(self, resource)
  483. receipts.register_servlets(self, resource)
  484. read_marker.register_servlets(self, resource)
  485. SendToDeviceRestServlet(self).register(resource)
  486. user_directory.register_servlets(self, resource)
  487. # If presence is disabled, use the stub servlet that does
  488. # not allow sending presence
  489. if not self.config.use_presence:
  490. PresenceStatusStubServlet(self).register(resource)
  491. groups.register_servlets(self, resource)
  492. resources.update({CLIENT_API_PREFIX: resource})
  493. resources.update(build_synapse_client_resource_tree(self))
  494. elif name == "federation":
  495. resources.update({FEDERATION_PREFIX: TransportLayerServer(self)})
  496. elif name == "media":
  497. if self.config.can_load_media_repo:
  498. media_repo = self.get_media_repository_resource()
  499. # We need to serve the admin servlets for media on the
  500. # worker.
  501. admin_resource = JsonResource(self, canonical_json=False)
  502. register_servlets_for_media_repo(self, admin_resource)
  503. resources.update(
  504. {
  505. MEDIA_PREFIX: media_repo,
  506. LEGACY_MEDIA_PREFIX: media_repo,
  507. "/_synapse/admin": admin_resource,
  508. }
  509. )
  510. else:
  511. logger.warning(
  512. "A 'media' listener is configured but the media"
  513. " repository is disabled. Ignoring."
  514. )
  515. if name == "openid" and "federation" not in res.names:
  516. # Only load the openid resource separately if federation resource
  517. # is not specified since federation resource includes openid
  518. # resource.
  519. resources.update(
  520. {
  521. FEDERATION_PREFIX: TransportLayerServer(
  522. self, servlet_groups=["openid"]
  523. )
  524. }
  525. )
  526. if name in ["keys", "federation"]:
  527. resources[SERVER_KEY_V2_PREFIX] = KeyApiV2Resource(self)
  528. if name == "replication":
  529. resources[REPLICATION_PREFIX] = ReplicationRestResource(self)
  530. root_resource = create_resource_tree(resources, OptionsResource())
  531. _base.listen_tcp(
  532. bind_addresses,
  533. port,
  534. SynapseSite(
  535. "synapse.access.http.%s" % (site_tag,),
  536. site_tag,
  537. listener_config,
  538. root_resource,
  539. self.version_string,
  540. ),
  541. reactor=self.get_reactor(),
  542. )
  543. logger.info("Synapse worker now listening on port %d", port)
  544. def start_listening(self, listeners: Iterable[ListenerConfig]):
  545. for listener in listeners:
  546. if listener.type == "http":
  547. self._listen_http(listener)
  548. elif listener.type == "manhole":
  549. _base.listen_manhole(
  550. listener.bind_addresses, listener.port, manhole_globals={"hs": self}
  551. )
  552. elif listener.type == "metrics":
  553. if not self.get_config().enable_metrics:
  554. logger.warning(
  555. (
  556. "Metrics listener configured, but "
  557. "enable_metrics is not True!"
  558. )
  559. )
  560. else:
  561. _base.listen_metrics(listener.bind_addresses, listener.port)
  562. else:
  563. logger.warning("Unsupported listener type: %s", listener.type)
  564. self.get_tcp_replication().start_replication(self)
  565. @cache_in_self
  566. def get_replication_data_handler(self):
  567. return GenericWorkerReplicationHandler(self)
  568. @cache_in_self
  569. def get_presence_handler(self):
  570. return GenericWorkerPresence(self)
  571. class GenericWorkerReplicationHandler(ReplicationDataHandler):
  572. def __init__(self, hs):
  573. super().__init__(hs)
  574. self.store = hs.get_datastore()
  575. self.presence_handler = hs.get_presence_handler() # type: GenericWorkerPresence
  576. self.notifier = hs.get_notifier()
  577. self.notify_pushers = hs.config.start_pushers
  578. self.pusher_pool = hs.get_pusherpool()
  579. self.send_handler = None # type: Optional[FederationSenderHandler]
  580. if hs.config.send_federation:
  581. self.send_handler = FederationSenderHandler(hs)
  582. async def on_rdata(self, stream_name, instance_name, token, rows):
  583. await super().on_rdata(stream_name, instance_name, token, rows)
  584. await self._process_and_notify(stream_name, instance_name, token, rows)
  585. async def _process_and_notify(self, stream_name, instance_name, token, rows):
  586. try:
  587. if self.send_handler:
  588. await self.send_handler.process_replication_rows(
  589. stream_name, token, rows
  590. )
  591. if stream_name == PushRulesStream.NAME:
  592. self.notifier.on_new_event(
  593. "push_rules_key", token, users=[row.user_id for row in rows]
  594. )
  595. elif stream_name in (AccountDataStream.NAME, TagAccountDataStream.NAME):
  596. self.notifier.on_new_event(
  597. "account_data_key", token, users=[row.user_id for row in rows]
  598. )
  599. elif stream_name == ReceiptsStream.NAME:
  600. self.notifier.on_new_event(
  601. "receipt_key", token, rooms=[row.room_id for row in rows]
  602. )
  603. await self.pusher_pool.on_new_receipts(
  604. token, token, {row.room_id for row in rows}
  605. )
  606. elif stream_name == ToDeviceStream.NAME:
  607. entities = [row.entity for row in rows if row.entity.startswith("@")]
  608. if entities:
  609. self.notifier.on_new_event("to_device_key", token, users=entities)
  610. elif stream_name == DeviceListsStream.NAME:
  611. all_room_ids = set() # type: Set[str]
  612. for row in rows:
  613. if row.entity.startswith("@"):
  614. room_ids = await self.store.get_rooms_for_user(row.entity)
  615. all_room_ids.update(room_ids)
  616. self.notifier.on_new_event("device_list_key", token, rooms=all_room_ids)
  617. elif stream_name == PresenceStream.NAME:
  618. await self.presence_handler.process_replication_rows(token, rows)
  619. elif stream_name == GroupServerStream.NAME:
  620. self.notifier.on_new_event(
  621. "groups_key", token, users=[row.user_id for row in rows]
  622. )
  623. elif stream_name == PushersStream.NAME:
  624. for row in rows:
  625. if row.deleted:
  626. self.stop_pusher(row.user_id, row.app_id, row.pushkey)
  627. else:
  628. await self.start_pusher(row.user_id, row.app_id, row.pushkey)
  629. except Exception:
  630. logger.exception("Error processing replication")
  631. async def on_position(self, stream_name: str, instance_name: str, token: int):
  632. await super().on_position(stream_name, instance_name, token)
  633. # Also call on_rdata to ensure that stream positions are properly reset.
  634. await self.on_rdata(stream_name, instance_name, token, [])
  635. def stop_pusher(self, user_id, app_id, pushkey):
  636. if not self.notify_pushers:
  637. return
  638. key = "%s:%s" % (app_id, pushkey)
  639. pushers_for_user = self.pusher_pool.pushers.get(user_id, {})
  640. pusher = pushers_for_user.pop(key, None)
  641. if pusher is None:
  642. return
  643. logger.info("Stopping pusher %r / %r", user_id, key)
  644. pusher.on_stop()
  645. async def start_pusher(self, user_id, app_id, pushkey):
  646. if not self.notify_pushers:
  647. return
  648. key = "%s:%s" % (app_id, pushkey)
  649. logger.info("Starting pusher %r / %r", user_id, key)
  650. return await self.pusher_pool.start_pusher_by_id(app_id, pushkey, user_id)
  651. def on_remote_server_up(self, server: str):
  652. """Called when get a new REMOTE_SERVER_UP command."""
  653. # Let's wake up the transaction queue for the server in case we have
  654. # pending stuff to send to it.
  655. if self.send_handler:
  656. self.send_handler.wake_destination(server)
  657. class FederationSenderHandler:
  658. """Processes the fedration replication stream
  659. This class is only instantiate on the worker responsible for sending outbound
  660. federation transactions. It receives rows from the replication stream and forwards
  661. the appropriate entries to the FederationSender class.
  662. """
  663. def __init__(self, hs: GenericWorkerServer):
  664. self.store = hs.get_datastore()
  665. self._is_mine_id = hs.is_mine_id
  666. self.federation_sender = hs.get_federation_sender()
  667. self._hs = hs
  668. # Stores the latest position in the federation stream we've gotten up
  669. # to. This is always set before we use it.
  670. self.federation_position = None
  671. self._fed_position_linearizer = Linearizer(name="_fed_position_linearizer")
  672. def wake_destination(self, server: str):
  673. self.federation_sender.wake_destination(server)
  674. async def process_replication_rows(self, stream_name, token, rows):
  675. # The federation stream contains things that we want to send out, e.g.
  676. # presence, typing, etc.
  677. if stream_name == "federation":
  678. send_queue.process_rows_for_federation(self.federation_sender, rows)
  679. await self.update_token(token)
  680. # ... and when new receipts happen
  681. elif stream_name == ReceiptsStream.NAME:
  682. await self._on_new_receipts(rows)
  683. # ... as well as device updates and messages
  684. elif stream_name == DeviceListsStream.NAME:
  685. # The entities are either user IDs (starting with '@') whose devices
  686. # have changed, or remote servers that we need to tell about
  687. # changes.
  688. hosts = {row.entity for row in rows if not row.entity.startswith("@")}
  689. for host in hosts:
  690. self.federation_sender.send_device_messages(host)
  691. elif stream_name == ToDeviceStream.NAME:
  692. # The to_device stream includes stuff to be pushed to both local
  693. # clients and remote servers, so we ignore entities that start with
  694. # '@' (since they'll be local users rather than destinations).
  695. hosts = {row.entity for row in rows if not row.entity.startswith("@")}
  696. for host in hosts:
  697. self.federation_sender.send_device_messages(host)
  698. async def _on_new_receipts(self, rows):
  699. """
  700. Args:
  701. rows (Iterable[synapse.replication.tcp.streams.ReceiptsStream.ReceiptsStreamRow]):
  702. new receipts to be processed
  703. """
  704. for receipt in rows:
  705. # we only want to send on receipts for our own users
  706. if not self._is_mine_id(receipt.user_id):
  707. continue
  708. receipt_info = ReadReceipt(
  709. receipt.room_id,
  710. receipt.receipt_type,
  711. receipt.user_id,
  712. [receipt.event_id],
  713. receipt.data,
  714. )
  715. await self.federation_sender.send_read_receipt(receipt_info)
  716. async def update_token(self, token):
  717. """Update the record of where we have processed to in the federation stream.
  718. Called after we have processed a an update received over replication. Sends
  719. a FEDERATION_ACK back to the master, and stores the token that we have processed
  720. in `federation_stream_position` so that we can restart where we left off.
  721. """
  722. self.federation_position = token
  723. # We save and send the ACK to master asynchronously, so we don't block
  724. # processing on persistence. We don't need to do this operation for
  725. # every single RDATA we receive, we just need to do it periodically.
  726. if self._fed_position_linearizer.is_queued(None):
  727. # There is already a task queued up to save and send the token, so
  728. # no need to queue up another task.
  729. return
  730. run_as_background_process("_save_and_send_ack", self._save_and_send_ack)
  731. async def _save_and_send_ack(self):
  732. """Save the current federation position in the database and send an ACK
  733. to master with where we're up to.
  734. """
  735. try:
  736. # We linearize here to ensure we don't have races updating the token
  737. #
  738. # XXX this appears to be redundant, since the ReplicationCommandHandler
  739. # has a linearizer which ensures that we only process one line of
  740. # replication data at a time. Should we remove it, or is it doing useful
  741. # service for robustness? Or could we replace it with an assertion that
  742. # we're not being re-entered?
  743. with (await self._fed_position_linearizer.queue(None)):
  744. # We persist and ack the same position, so we take a copy of it
  745. # here as otherwise it can get modified from underneath us.
  746. current_position = self.federation_position
  747. await self.store.update_federation_out_pos(
  748. "federation", current_position
  749. )
  750. # We ACK this token over replication so that the master can drop
  751. # its in memory queues
  752. self._hs.get_tcp_replication().send_federation_ack(current_position)
  753. except Exception:
  754. logger.exception("Error updating federation stream position")
  755. def start(config_options):
  756. try:
  757. config = HomeServerConfig.load_config("Synapse worker", config_options)
  758. except ConfigError as e:
  759. sys.stderr.write("\n" + str(e) + "\n")
  760. sys.exit(1)
  761. # For backwards compatibility let any of the old app names.
  762. assert config.worker_app in (
  763. "synapse.app.appservice",
  764. "synapse.app.client_reader",
  765. "synapse.app.event_creator",
  766. "synapse.app.federation_reader",
  767. "synapse.app.federation_sender",
  768. "synapse.app.frontend_proxy",
  769. "synapse.app.generic_worker",
  770. "synapse.app.media_repository",
  771. "synapse.app.pusher",
  772. "synapse.app.synchrotron",
  773. "synapse.app.user_dir",
  774. )
  775. if config.worker_app == "synapse.app.appservice":
  776. if config.appservice.notify_appservices:
  777. sys.stderr.write(
  778. "\nThe appservices must be disabled in the main synapse process"
  779. "\nbefore they can be run in a separate worker."
  780. "\nPlease add ``notify_appservices: false`` to the main config"
  781. "\n"
  782. )
  783. sys.exit(1)
  784. # Force the appservice to start since they will be disabled in the main config
  785. config.appservice.notify_appservices = True
  786. else:
  787. # For other worker types we force this to off.
  788. config.appservice.notify_appservices = False
  789. if config.worker_app == "synapse.app.user_dir":
  790. if config.server.update_user_directory:
  791. sys.stderr.write(
  792. "\nThe update_user_directory must be disabled in the main synapse process"
  793. "\nbefore they can be run in a separate worker."
  794. "\nPlease add ``update_user_directory: false`` to the main config"
  795. "\n"
  796. )
  797. sys.exit(1)
  798. # Force the pushers to start since they will be disabled in the main config
  799. config.server.update_user_directory = True
  800. else:
  801. # For other worker types we force this to off.
  802. config.server.update_user_directory = False
  803. synapse.events.USE_FROZEN_DICTS = config.use_frozen_dicts
  804. hs = GenericWorkerServer(
  805. config.server_name,
  806. config=config,
  807. version_string="Synapse/" + get_version_string(synapse),
  808. )
  809. setup_logging(hs, config, use_worker_options=True)
  810. hs.setup()
  811. # Ensure the replication streamer is always started in case we write to any
  812. # streams. Will no-op if no streams can be written to by this worker.
  813. hs.get_replication_streamer()
  814. register_start(_base.start, hs, config.worker_listeners)
  815. _base.start_worker_reactor("synapse-generic-worker", config)
  816. if __name__ == "__main__":
  817. with LoggingContext("main"):
  818. start(sys.argv[1:])