Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 8bcfc2e

Browse files
Be smarter about which hosts to send presence to when processing room joins (#9402)
This PR attempts to eliminate unnecessary presence sending work when your local server joins a room, or when a remote server joins a room your server is participating in by processing state deltas in chunks rather than individually. --- When your server joins a room for the first time, it requests the historical state as well. This chunk of new state is passed to the presence handler which, after filtering that state down to only membership joins, will send presence updates to homeservers for each join processed. It turns out that we were being a bit naive and processing each event individually, and sending out presence updates for every one of those joins. Even if many different joins were users on the same server (hello IRC bridges), we'd send presence to that same homeserver for every remote user join we saw. This PR attempts to deduplicate all of that by processing the entire batch of state deltas at once, instead of only doing each join individually. We process the joins and note down which servers need which presence: * If it was a local user join, send that user's latest presence to all servers in the room * If it was a remote user join, send the presence for all local users in the room to that homeserver We deduplicate by inserting all of those pending updates into a dictionary of the form: ``` { server_name1: {presence_update1, ...}, server_name2: {presence_update1, presence_update2, ...} } ``` Only after building this dict do we then start sending out presence updates.
1 parent 13e9029 commit 8bcfc2e

File tree

4 files changed

+54
-19
lines changed

4 files changed

+54
-19
lines changed

changelog.d/9402.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a bug where a lot of unnecessary presence updates were sent when joining a room.

synapse/federation/sender/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ async def send_presence(self, states: List[UserPresenceState]):
474474
self._processing_pending_presence = False
475475

476476
def send_presence_to_destinations(
477-
self, states: List[UserPresenceState], destinations: List[str]
477+
self, states: Iterable[UserPresenceState], destinations: Iterable[str]
478478
) -> None:
479479
"""Send the given presence states to the given destinations.
480480
destinations (list[str])

synapse/handlers/presence.py

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,9 @@ async def _handle_state_delta(self, deltas):
849849
"""Process current state deltas to find new joins that need to be
850850
handled.
851851
"""
852+
# A map of destination to a set of user state that they should receive
853+
presence_destinations = {} # type: Dict[str, Set[UserPresenceState]]
854+
852855
for delta in deltas:
853856
typ = delta["type"]
854857
state_key = delta["state_key"]
@@ -858,6 +861,7 @@ async def _handle_state_delta(self, deltas):
858861

859862
logger.debug("Handling: %r %r, %s", typ, state_key, event_id)
860863

864+
# Drop any event that isn't a membership join
861865
if typ != EventTypes.Member:
862866
continue
863867

@@ -880,29 +884,54 @@ async def _handle_state_delta(self, deltas):
880884
# Ignore changes to join events.
881885
continue
882886

883-
await self._on_user_joined_room(room_id, state_key)
887+
# Retrieve any user presence state updates that need to be sent as a result,
888+
# and the destinations that need to receive it
889+
destinations, user_presence_states = await self._on_user_joined_room(
890+
room_id, state_key
891+
)
892+
893+
# Insert the destinations and respective updates into our destinations dict
894+
for destination in destinations:
895+
presence_destinations.setdefault(destination, set()).update(
896+
user_presence_states
897+
)
898+
899+
# Send out user presence updates for each destination
900+
for destination, user_state_set in presence_destinations.items():
901+
self.federation.send_presence_to_destinations(
902+
destinations=[destination], states=user_state_set
903+
)
884904

885-
async def _on_user_joined_room(self, room_id: str, user_id: str) -> None:
905+
async def _on_user_joined_room(
906+
self, room_id: str, user_id: str
907+
) -> Tuple[List[str], List[UserPresenceState]]:
886908
"""Called when we detect a user joining the room via the current state
887-
delta stream.
888-
"""
909+
delta stream. Returns the destinations that need to be updated and the
910+
presence updates to send to them.
911+
912+
Args:
913+
room_id: The ID of the room that the user has joined.
914+
user_id: The ID of the user that has joined the room.
889915
916+
Returns:
917+
A tuple of destinations and presence updates to send to them.
918+
"""
890919
if self.is_mine_id(user_id):
891920
# If this is a local user then we need to send their presence
892921
# out to hosts in the room (who don't already have it)
893922

894923
# TODO: We should be able to filter the hosts down to those that
895924
# haven't previously seen the user
896925

897-
state = await self.current_state_for_user(user_id)
898-
hosts = await self.state.get_current_hosts_in_room(room_id)
926+
remote_hosts = await self.state.get_current_hosts_in_room(room_id)
899927

900928
# Filter out ourselves.
901-
hosts = {host for host in hosts if host != self.server_name}
929+
filtered_remote_hosts = [
930+
host for host in remote_hosts if host != self.server_name
931+
]
902932

903-
self.federation.send_presence_to_destinations(
904-
states=[state], destinations=hosts
905-
)
933+
state = await self.current_state_for_user(user_id)
934+
return filtered_remote_hosts, [state]
906935
else:
907936
# A remote user has joined the room, so we need to:
908937
# 1. Check if this is a new server in the room
@@ -915,6 +944,8 @@ async def _on_user_joined_room(self, room_id: str, user_id: str) -> None:
915944
# TODO: Check that this is actually a new server joining the
916945
# room.
917946

947+
remote_host = get_domain_from_id(user_id)
948+
918949
users = await self.state.get_current_users_in_room(room_id)
919950
user_ids = list(filter(self.is_mine_id, users))
920951

@@ -934,10 +965,7 @@ async def _on_user_joined_room(self, room_id: str, user_id: str) -> None:
934965
or state.status_msg is not None
935966
]
936967

937-
if states:
938-
self.federation.send_presence_to_destinations(
939-
states=states, destinations=[get_domain_from_id(user_id)]
940-
)
968+
return [remote_host], states
941969

942970

943971
def should_notify(old_state, new_state):

tests/handlers/test_presence.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ def test_remote_joins(self):
521521
)
522522
self.assertEqual(expected_state.state, PresenceState.ONLINE)
523523
self.federation_sender.send_presence_to_destinations.assert_called_once_with(
524-
destinations=["server2"], states=[expected_state]
524+
destinations=["server2"], states={expected_state}
525525
)
526526

527527
#
@@ -533,7 +533,7 @@ def test_remote_joins(self):
533533

534534
self.federation_sender.send_presence.assert_not_called()
535535
self.federation_sender.send_presence_to_destinations.assert_called_once_with(
536-
destinations=["server3"], states=[expected_state]
536+
destinations=["server3"], states={expected_state}
537537
)
538538

539539
def test_remote_gets_presence_when_local_user_joins(self):
@@ -584,8 +584,14 @@ def test_remote_gets_presence_when_local_user_joins(self):
584584
self.presence_handler.current_state_for_user("@test2:server")
585585
)
586586
self.assertEqual(expected_state.state, PresenceState.ONLINE)
587-
self.federation_sender.send_presence_to_destinations.assert_called_once_with(
588-
destinations={"server2", "server3"}, states=[expected_state]
587+
self.assertEqual(
588+
self.federation_sender.send_presence_to_destinations.call_count, 2
589+
)
590+
self.federation_sender.send_presence_to_destinations.assert_any_call(
591+
destinations=["server3"], states={expected_state}
592+
)
593+
self.federation_sender.send_presence_to_destinations.assert_any_call(
594+
destinations=["server2"], states={expected_state}
589595
)
590596

591597
def _add_new_user(self, room_id, user_id):

0 commit comments

Comments
 (0)