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

Include heroes in partial join responses' state #14442

Merged
merged 6 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/14442.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Faster joins: include heroes' membership events in the partial join response, for rooms without a name or canonical alias.
23 changes: 19 additions & 4 deletions synapse/federation/federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
)
from synapse.storage.databases.main.events import PartialStateConflictError
from synapse.storage.databases.main.lock import Lock
from synapse.storage.databases.main.roommember import extract_heroes_from_room_summary
from synapse.storage.roommember import MemberSummary
from synapse.types import JsonDict, StateMap, get_domain_from_id
from synapse.util import json_decoder, unwrapFirstError
from synapse.util.async_helpers import Linearizer, concurrently_execute, gather_results
Expand Down Expand Up @@ -691,8 +693,9 @@ async def on_send_join_request(
state_event_ids: Collection[str]
servers_in_room: Optional[Collection[str]]
if caller_supports_partial_state:
summary = await self.store.get_room_summary(room_id)
state_event_ids = _get_event_ids_for_partial_state_join(
event, prev_state_ids
event, prev_state_ids, summary
)
servers_in_room = await self.state.get_hosts_in_room_at_events(
room_id, event_ids=event.prev_event_ids()
Expand Down Expand Up @@ -1495,6 +1498,7 @@ async def on_query(self, query_type: str, args: dict) -> JsonDict:
def _get_event_ids_for_partial_state_join(
join_event: EventBase,
prev_state_ids: StateMap[str],
summary: Dict[str, MemberSummary],
) -> Collection[str]:
"""Calculate state to be retuned in a partial_state send_join

Expand All @@ -1521,8 +1525,19 @@ def _get_event_ids_for_partial_state_join(
if current_membership_event_id is not None:
state_event_ids.add(current_membership_event_id)

# TODO: return a few more members:
# - those with invites
# - those that are kicked? / banned
name_id = prev_state_ids.get((EventTypes.Name, ""))
canonical_alias_id = prev_state_ids.get((EventTypes.CanonicalAlias, ""))
if not name_id and not canonical_alias_id:
# Also include the hero members of the room (for DM rooms without a title).
# To do this properly, we should select the correct subset of membership events
# from `prev_state_ids`. Instead, we are lazier and use the (cached)
# `get_room_summary` function, which is based on the current state of the room.
# This introduces races; we choose to ignore them because a) they should be rare
# and b) even if it's wrong, joining servers will get the full state eventually.
heroes = extract_heroes_from_room_summary(summary, join_event.state_key)
for hero in heroes:
membership_event_id = prev_state_ids.get((EventTypes.Member, hero))
if membership_event_id:
state_event_ids.add(membership_event_id)

return state_event_ids
20 changes: 3 additions & 17 deletions synapse/handlers/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from synapse.logging.opentracing import SynapseTags, log_kv, set_tag, start_active_span
from synapse.push.clientformat import format_push_rules_for_user
from synapse.storage.databases.main.event_push_actions import RoomNotifCounts
from synapse.storage.databases.main.roommember import extract_heroes_from_room_summary
from synapse.storage.roommember import MemberSummary
from synapse.storage.state import StateFilter
from synapse.types import (
Expand Down Expand Up @@ -805,18 +806,6 @@ async def compute_summary(
if canonical_alias and canonical_alias.content.get("alias"):
return summary

me = sync_config.user.to_string()

joined_user_ids = [
r[0] for r in details.get(Membership.JOIN, empty_ms).members if r[0] != me
]
invited_user_ids = [
r[0] for r in details.get(Membership.INVITE, empty_ms).members if r[0] != me
]
gone_user_ids = [
r[0] for r in details.get(Membership.LEAVE, empty_ms).members if r[0] != me
] + [r[0] for r in details.get(Membership.BAN, empty_ms).members if r[0] != me]

# FIXME: only build up a member_ids list for our heroes
member_ids = {}
for membership in (
Expand All @@ -828,11 +817,8 @@ async def compute_summary(
for user_id, event_id in details.get(membership, empty_ms).members:
member_ids[user_id] = event_id

# FIXME: order by stream ordering rather than as returned by SQL
if joined_user_ids or invited_user_ids:
summary["m.heroes"] = sorted(joined_user_ids + invited_user_ids)[0:5]
else:
summary["m.heroes"] = sorted(gone_user_ids)[0:5]
me = sync_config.user.to_string()
summary["m.heroes"] = extract_heroes_from_room_summary(details, me)

if not sync_config.filter_collection.lazy_load_members():
return summary
Expand Down
30 changes: 30 additions & 0 deletions synapse/storage/databases/main/roommember.py
Original file line number Diff line number Diff line change
Expand Up @@ -1517,6 +1517,36 @@ def f(txn: LoggingTransaction) -> None:
await self.db_pool.runInteraction("forget_membership", f)


def extract_heroes_from_room_summary(
details: Mapping[str, MemberSummary], me: str
) -> List[str]:
"""Determine the users that represent a room, from the perspective of the `me` user.

The rules which say which users we select are specified in the "Room Summary"
section of
https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv3sync

Returns a list (possibly empty) of heroes' mxids.
"""
empty_ms = MemberSummary([], 0)

joined_user_ids = [
r[0] for r in details.get(Membership.JOIN, empty_ms).members if r[0] != me
]
invited_user_ids = [
r[0] for r in details.get(Membership.INVITE, empty_ms).members if r[0] != me
]
gone_user_ids = [
r[0] for r in details.get(Membership.LEAVE, empty_ms).members if r[0] != me
] + [r[0] for r in details.get(Membership.BAN, empty_ms).members if r[0] != me]

# FIXME: order by stream ordering rather than as returned by SQL
DMRobertson marked this conversation as resolved.
Show resolved Hide resolved
if joined_user_ids or invited_user_ids:
return sorted(joined_user_ids + invited_user_ids)[0:5]
else:
return sorted(gone_user_ids)[0:5]


@attr.s(slots=True, auto_attribs=True)
class _JoinedHostsCache:
"""The cached data used by the `_get_joined_hosts_cache`."""
Expand Down
11 changes: 7 additions & 4 deletions tests/federation/test_federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def test_send_join(self):
self.assertEqual(r[("m.room.member", joining_user)].membership, "join")

@override_config({"experimental_features": {"msc3706_enabled": True}})
def test_send_join_partial_state(self):
def test_send_join_partial_state(self) -> None:
"""When MSC3706 support is enabled, /send_join should return partial state"""
joining_user = "@misspiggy:" + self.OTHER_SERVER_NAME
join_result = self._make_join(joining_user)
Expand Down Expand Up @@ -240,6 +240,9 @@ def test_send_join_partial_state(self):
("m.room.power_levels", ""),
("m.room.join_rules", ""),
("m.room.history_visibility", ""),
# Users included here because they're heroes.
("m.room.member", "@kermit:test"),
("m.room.member", "@fozzie:test"),
],
)

Expand All @@ -249,9 +252,9 @@ def test_send_join_partial_state(self):
]
self.assertCountEqual(
returned_auth_chain_events,
[
("m.room.member", "@kermit:test"),
],
# TODO: change the test so that we get at least one event in the auth chain
# here.
[],
)

# the room should show that the new user is a member
Expand Down