Skip to content

Commit 606da39

Browse files
authored
Fix filtering room types on remote rooms (#17434)
We can only fetch room types for rooms the server is in, so we need to only filter rooms that we're joined to. Also includes a perf fix to bulk fetch room types.
1 parent 677142b commit 606da39

File tree

4 files changed

+130
-13
lines changed

4 files changed

+130
-13
lines changed

changelog.d/17434.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix bug in experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync `/sync` endpoint when using room type filters and the user has one or more remote invites.

synapse/handlers/sliding_sync.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,7 @@
2424
import attr
2525
from immutabledict import immutabledict
2626

27-
from synapse.api.constants import (
28-
AccountDataTypes,
29-
Direction,
30-
EventContentFields,
31-
EventTypes,
32-
Membership,
33-
)
27+
from synapse.api.constants import AccountDataTypes, Direction, EventTypes, Membership
3428
from synapse.events import EventBase
3529
from synapse.events.utils import strip_event
3630
from synapse.handlers.relations import BundledAggregations
@@ -959,11 +953,15 @@ async def filter_rooms(
959953
# provided in the list. `None` is a valid type for rooms which do not have a
960954
# room type.
961955
if filters.room_types is not None or filters.not_room_types is not None:
962-
# Make a copy so we don't run into an error: `Set changed size during
963-
# iteration`, when we filter out and remove items
964-
for room_id in filtered_room_id_set.copy():
965-
create_event = await self.store.get_create_event_for_room(room_id)
966-
room_type = create_event.content.get(EventContentFields.ROOM_TYPE)
956+
room_to_type = await self.store.bulk_get_room_type(
957+
{
958+
room_id
959+
for room_id in filtered_room_id_set
960+
# We only know the room types for joined rooms
961+
if sync_room_map[room_id].membership == Membership.JOIN
962+
}
963+
)
964+
for room_id, room_type in room_to_type.items():
967965
if (
968966
filters.room_types is not None
969967
and room_type not in filters.room_types

synapse/storage/databases/main/state.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
import attr
4343

44-
from synapse.api.constants import EventTypes, Membership
44+
from synapse.api.constants import EventContentFields, EventTypes, Membership
4545
from synapse.api.errors import NotFoundError, UnsupportedRoomVersionError
4646
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
4747
from synapse.events import EventBase
@@ -298,6 +298,56 @@ async def get_create_event_for_room(self, room_id: str) -> EventBase:
298298
create_event = await self.get_event(create_id)
299299
return create_event
300300

301+
@cached(max_entries=10000)
302+
async def get_room_type(self, room_id: str) -> Optional[str]:
303+
"""Get the room type for a given room. The server must be joined to the
304+
given room.
305+
"""
306+
307+
row = await self.db_pool.simple_select_one(
308+
table="room_stats_state",
309+
keyvalues={"room_id": room_id},
310+
retcols=("room_type",),
311+
allow_none=True,
312+
desc="get_room_type",
313+
)
314+
315+
if row is not None:
316+
return row[0]
317+
318+
# If we haven't updated `room_stats_state` with the room yet, query the
319+
# create event directly.
320+
create_event = await self.get_create_event_for_room(room_id)
321+
room_type = create_event.content.get(EventContentFields.ROOM_TYPE)
322+
return room_type
323+
324+
@cachedList(cached_method_name="get_room_type", list_name="room_ids")
325+
async def bulk_get_room_type(
326+
self, room_ids: Set[str]
327+
) -> Mapping[str, Optional[str]]:
328+
"""Bulk fetch room types for the given rooms, the server must be in all
329+
the rooms given.
330+
"""
331+
332+
rows = await self.db_pool.simple_select_many_batch(
333+
table="room_stats_state",
334+
column="room_id",
335+
iterable=room_ids,
336+
retcols=("room_id", "room_type"),
337+
desc="bulk_get_room_type",
338+
)
339+
340+
# If we haven't updated `room_stats_state` with the room yet, query the
341+
# create events directly. This should happen only rarely so we don't
342+
# mind if we do this in a loop.
343+
results = dict(rows)
344+
for room_id in room_ids - results.keys():
345+
create_event = await self.get_create_event_for_room(room_id)
346+
room_type = create_event.content.get(EventContentFields.ROOM_TYPE)
347+
results[room_id] = room_type
348+
349+
return results
350+
301351
@cached(max_entries=100000, iterable=True)
302352
async def get_partial_current_state_ids(self, room_id: str) -> StateMap[str]:
303353
"""Get the current state event ids for a room based on the

tests/handlers/test_sliding_sync.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
RoomTypes,
3636
)
3737
from synapse.api.room_versions import RoomVersions
38+
from synapse.events import make_event_from_dict
39+
from synapse.events.snapshot import EventContext
3840
from synapse.handlers.sliding_sync import RoomSyncConfig, StateValues
3941
from synapse.rest import admin
4042
from synapse.rest.client import knock, login, room
@@ -2791,6 +2793,72 @@ def test_filter_not_room_types(self) -> None:
27912793

27922794
self.assertEqual(filtered_room_map.keys(), {space_room_id})
27932795

2796+
def test_filter_room_types_with_invite_remote_room(self) -> None:
2797+
"""Test that we can apply a room type filter, even if we have an invite
2798+
for a remote room.
2799+
2800+
This is a regression test.
2801+
"""
2802+
2803+
user1_id = self.register_user("user1", "pass")
2804+
user1_tok = self.login(user1_id, "pass")
2805+
2806+
# Create a fake remote invite and persist it.
2807+
invite_room_id = "!some:room"
2808+
invite_event = make_event_from_dict(
2809+
{
2810+
"room_id": invite_room_id,
2811+
"sender": "@user:test.serv",
2812+
"state_key": user1_id,
2813+
"depth": 1,
2814+
"origin_server_ts": 1,
2815+
"type": EventTypes.Member,
2816+
"content": {"membership": Membership.INVITE},
2817+
"auth_events": [],
2818+
"prev_events": [],
2819+
},
2820+
room_version=RoomVersions.V10,
2821+
)
2822+
invite_event.internal_metadata.outlier = True
2823+
invite_event.internal_metadata.out_of_band_membership = True
2824+
2825+
self.get_success(
2826+
self.store.maybe_store_room_on_outlier_membership(
2827+
room_id=invite_room_id, room_version=invite_event.room_version
2828+
)
2829+
)
2830+
context = EventContext.for_outlier(self.hs.get_storage_controllers())
2831+
persist_controller = self.hs.get_storage_controllers().persistence
2832+
assert persist_controller is not None
2833+
self.get_success(persist_controller.persist_event(invite_event, context))
2834+
2835+
# Create a normal room (no room type)
2836+
room_id = self.helper.create_room_as(user1_id, tok=user1_tok)
2837+
2838+
after_rooms_token = self.event_sources.get_current_token()
2839+
2840+
# Get the rooms the user should be syncing with
2841+
sync_room_map = self.get_success(
2842+
self.sliding_sync_handler.get_sync_room_ids_for_user(
2843+
UserID.from_string(user1_id),
2844+
from_token=None,
2845+
to_token=after_rooms_token,
2846+
)
2847+
)
2848+
2849+
filtered_room_map = self.get_success(
2850+
self.sliding_sync_handler.filter_rooms(
2851+
UserID.from_string(user1_id),
2852+
sync_room_map,
2853+
SlidingSyncConfig.SlidingSyncList.Filters(
2854+
room_types=[None, RoomTypes.SPACE],
2855+
),
2856+
after_rooms_token,
2857+
)
2858+
)
2859+
2860+
self.assertEqual(filtered_room_map.keys(), {room_id, invite_room_id})
2861+
27942862

27952863
class SortRoomsTestCase(HomeserverTestCase):
27962864
"""

0 commit comments

Comments
 (0)