Compare commits

...

94 Commits

Author SHA1 Message Date
Eric Eastwood
2175edf334 Merge branch 'develop' into madlittlemods/sliding-sync-required-state
Conflicts:
	synapse/handlers/sliding_sync.py
	tests/rest/client/test_sync.py
2024-07-02 11:20:42 -05:00
Eric Eastwood
cae489b277 Merge branch 'madlittlemods/sliding-sync-room-data' into madlittlemods/sliding-sync-required-state 2024-07-02 11:00:21 -05:00
Erik Johnston
126ce1e7ac Merge branch 'develop' into madlittlemods/sliding-sync-room-data 2024-07-02 16:07:50 +01:00
Eric Eastwood
939b9ceffd room_state can be None 2024-07-01 23:17:58 -05:00
Eric Eastwood
8aa2297644 Merge branch 'madlittlemods/sliding-sync-room-data' into madlittlemods/sliding-sync-required-state 2024-07-01 22:41:25 -05:00
Eric Eastwood
8b73185094 Trigger CI again 2024-07-01 22:41:08 -05:00
Eric Eastwood
78df54af2d Get state at leave/ban 2024-07-01 22:40:18 -05:00
Eric Eastwood
dfb43247c7 Merge branch 'madlittlemods/sliding-sync-room-data' into madlittlemods/sliding-sync-required-state 2024-07-01 21:50:01 -05:00
Eric Eastwood
7c9513ccb1 Add missing test description 2024-07-01 21:49:41 -05:00
Eric Eastwood
b8687e771c Select to_key if to_key else from_key
See https://github.com/element-hq/synapse/pull/17320#discussion_r1646591886
2024-07-01 21:45:58 -05:00
Eric Eastwood
2bd6cba76d Select to_key if to_key else from_key
See https://github.com/element-hq/synapse/pull/17320#discussion_r1646591886
2024-07-01 21:42:11 -05:00
Eric Eastwood
e4195875b9 Protect from no timeline_events 2024-07-01 21:21:38 -05:00
Eric Eastwood
73f2903bc5 Merge branch 'madlittlemods/sliding-sync-room-data' into madlittlemods/sliding-sync-required-state
Conflicts:
	synapse/handlers/sliding_sync.py
2024-07-01 21:20:16 -05:00
Eric Eastwood
0061561988 Merge branch 'develop' into madlittlemods/sliding-sync-room-data 2024-07-01 19:10:30 -05:00
Eric Eastwood
10d78d66b7 Protect for non-existent prev events 2024-07-01 19:04:46 -05:00
Eric Eastwood
a4263bf925 Update stream tests with prev event info 2024-07-01 18:56:45 -05:00
Eric Eastwood
9e53336a71 Avoid fetching full events for prev_event_ids
See https://github.com/element-hq/synapse/pull/17320#discussion_r1658832755
2024-07-01 13:44:00 -05:00
Eric Eastwood
6045e118d6 Merge branch 'develop' into madlittlemods/sliding-sync-room-data 2024-07-01 11:34:46 -05:00
Eric Eastwood
32b8b68df6 Add TODO to handle state resets
See https://github.com/element-hq/synapse/pull/17320#discussion_r1656548733
2024-06-27 18:13:34 -05:00
Eric Eastwood
1158058aa5 Opt for tackling more batch scenarios in future PRs 2024-06-27 17:58:31 -05:00
Eric Eastwood
63c7b5017a (doesn't work) Add test for batch persisting multiple member events for the same user 2024-06-27 16:34:41 -05:00
Eric Eastwood
325856e14b Inclusive ranges 2024-06-27 15:57:01 -05:00
Eric Eastwood
f77403251c Add better comments 2024-06-27 15:39:43 -05:00
Eric Eastwood
ba56350642 Passing current tests 2024-06-27 15:31:18 -05:00
Eric Eastwood
eb159c11cd Don't worry about state_reset for now
See:

 - Why no `COALESCE` https://github.com/element-hq/synapse/pull/17320#discussion_r1657435662
 - Don't worry about `state_reset` for now, https://github.com/element-hq/synapse/pull/17320#discussion_r1657562645
2024-06-27 14:38:55 -05:00
Eric Eastwood
81c06bec20 Detect state resets 2024-06-27 11:50:18 -05:00
Eric Eastwood
15fcead2a5 Slight clean-up 2024-06-27 11:33:41 -05:00
Eric Eastwood
830e09d2de Grab prev_membership to see whether the server left the room (fixes tests)
See https://github.com/element-hq/synapse/pull/17320#discussion_r1657170493

`prev_membership` helps determine whether we should include the `event_id=null` row because
we can check whether we have already left.

 - When we leave the room causing the server to leave the room, the `prev_event_id` will be our join event
 - When the server leaves the room after us, the `prev_event_id` will be leave event
 - In the state reset case, `prev_event_id` will be our join event
2024-06-27 10:20:33 -05:00
Eric Eastwood
956f20ef74 (currently failing) Add test to make sure membership changes don't re-appear if the server leaves the room later 2024-06-27 01:24:37 -05:00
Eric Eastwood
f163fcf08a Remove need for topological_ordering 2024-06-27 01:21:49 -05:00
Eric Eastwood
935b98c474 All get_current_state_delta_membership_changes_for_user(...) tests passing 2024-06-27 00:49:51 -05:00
Eric Eastwood
7eb1806ee3 Fix lints 2024-06-26 21:06:05 -05:00
Eric Eastwood
b7914e7676 Add skipped test for state resets 2024-06-26 21:01:54 -05:00
Eric Eastwood
8df39d1baf Remove redundant instance_name column 2024-06-26 20:22:03 -05:00
Eric Eastwood
2a944ffcef Add state of the db in each situation 2024-06-26 20:20:17 -05:00
Eric Eastwood
48d0acfbcd Actually test get_current_state_delta_membership_changes_for_user(...) in remote join test 2024-06-26 19:52:12 -05:00
Eric Eastwood
0b9a903ca1 Add test that remotely joins room 2024-06-26 19:35:18 -05:00
Eric Eastwood
ec2d8dc1e3 Create events using helper 2024-06-26 18:52:24 -05:00
Eric Eastwood
2bf39231ed Add some tests for get_current_state_delta_membership_changes_for_user(...) 2024-06-26 18:40:36 -05:00
Eric Eastwood
7395e10420 Fix builtins.SyntaxError: EOL while scanning string literal (test_sync.py, line 1885)
See https://github.com/element-hq/synapse/actions/runs/9675073109/job/26692003103?pr=17320#step:9:5552

Worked fine locally but failed in CI with Python 3.8
2024-06-26 02:19:31 -05:00
Eric Eastwood
11db1befa2 Remove debug log 2024-06-26 02:05:25 -05:00
Eric Eastwood
c60aca755b Fix clause change 2024-06-26 02:02:34 -05:00
Eric Eastwood
5c213158d7 Merge branch 'develop' into madlittlemods/sliding-sync-room-data
Conflicts:
	tests/rest/client/test_sync.py
2024-06-26 01:36:23 -05:00
Eric Eastwood
39259f66fa Join both tables with stream_ordering 2024-06-26 01:16:46 -05:00
Eric Eastwood
62c6a4e860 Add newly_joined support to get_sync_room_ids_for_user(...) 2024-06-26 01:10:00 -05:00
Eric Eastwood
cccbd15e7e Refactor back to not pulling out full events 2024-06-25 23:40:55 -05:00
Eric Eastwood
daa7e3691a Add docstring 2024-06-25 23:01:28 -05:00
Eric Eastwood
d91aa0018c Remove extras 2024-06-25 22:45:59 -05:00
Eric Eastwood
fb8fbd489c Just fetch full events for get_current_state_delta_membership_changes_for_user(...)
Makes downstream logic simpler and although we may look-up some events
we don't use, the lookup is all done in one go instead of fetching events
from event_ids in a couple different places.
2024-06-25 22:37:45 -05:00
Eric Eastwood
27d74b023e Iterate 2024-06-25 22:20:27 -05:00
Eric Eastwood
6c791a88b3 WIP: Add back newly_left 2024-06-25 21:07:19 -05:00
Eric Eastwood
fbd92e1c9d Add get_current_state_delta_membership_changes_for_user(...) (using current_state_delta_stream)
(still need to add newly_left rooms back)
2024-06-25 20:16:50 -05:00
Eric Eastwood
83d6f76606 Describe current_state_delta_stream better 2024-06-25 15:27:49 -05:00
Eric Eastwood
4c22131442 Start testing for the correct room membership (failing) 2024-06-25 15:27:43 -05:00
Eric Eastwood
303d834b78 Add tracking discussion for not optional in the future 2024-06-25 12:38:09 -05:00
Eric Eastwood
e3e431fab4 Finish up stripped_state for invite rooms
See https://github.com/element-hq/synapse/pull/17320#discussion_r1646581077
2024-06-25 12:35:50 -05:00
Eric Eastwood
7aea406c22 Just stripped_state for invite rooms 2024-06-25 11:18:27 -05:00
Eric Eastwood
3568311989 Fix spelling typo 2024-06-24 19:08:18 -05:00
Eric Eastwood
21ca02c5ad newly_joined vs limited already being tracked in a discussion
See https://github.com/element-hq/synapse/pull/17320#discussion_r1646579623
if anything comes out of it.
2024-06-24 16:08:58 -05:00
Eric Eastwood
0e71a2f2d1 Add TODO for filtering call invites in public rooms 2024-06-24 15:56:27 -05:00
Eric Eastwood
87fac19fde Fix lints
See https://github.com/element-hq/synapse/pull/17320#discussion_r1647701997

```
synapse/federation/federation_server.py:677: error: Cannot determine type of "_join_rate_per_room_limiter"  [has-type]
synapse/federation/federation_server.py:720: error: Cannot determine type of "_join_rate_per_room_limiter"  [has-type]
```
2024-06-24 10:15:15 -05:00
Eric Eastwood
b1b4231ccb Merge branch 'develop' into madlittlemods/sliding-sync-room-data 2024-06-24 10:12:05 -05:00
Eric Eastwood
5fdbeff215 Make sure we add some specific tests 2024-06-20 23:52:49 -05:00
Eric Eastwood
8dc40098b8 Add a test 2024-06-20 23:48:06 -05:00
Eric Eastwood
41988a7358 Allow the next room to fill its place if we exclude a partially-stated room 2024-06-20 23:33:11 -05:00
Eric Eastwood
55ad5590bd Start of thinking to handle partial stated rooms 2024-06-20 17:24:32 -05:00
Eric Eastwood
ddbcd786fa Add future note about other people we might care about when lazy-loading membership 2024-06-20 16:36:08 -05:00
Eric Eastwood
cddcf1f419 Fetch required state 2024-06-20 16:20:18 -05:00
Eric Eastwood
b6edd6b43e Better RoomSyncConfig tests 2024-06-20 15:51:53 -05:00
Eric Eastwood
5b17d706df Add RoomSyncConfig tests 2024-06-20 15:35:26 -05:00
Eric Eastwood
0eb029472e Remove unused IncludeOldRooms class 2024-06-20 14:34:10 -05:00
Eric Eastwood
bbc3eb5f6f Different better 2024-06-20 14:33:17 -05:00
Eric Eastwood
ef3f606cbf Start of combinining RoomSyncConfig 2024-06-20 14:06:00 -05:00
Eric Eastwood
884b448012 Update some wording 2024-06-19 13:50:28 -05:00
Eric Eastwood
6942b6476a Merge branch 'develop' into madlittlemods/sliding-sync-room-data 2024-06-19 13:24:12 -05:00
Eric Eastwood
d801db0d96 Fix lints 2024-06-19 13:24:01 -05:00
Eric Eastwood
c81f3006a5 Add better support for leave/ban 2024-06-19 12:54:39 -05:00
Eric Eastwood
57ba0336bd Merge branch 'develop' into madlittlemods/sliding-sync-room-data 2024-06-18 22:31:27 -05:00
Eric Eastwood
1c06153a0d Determine limited before filtering 2024-06-18 22:30:40 -05:00
Eric Eastwood
9883b0f63f Add bundled aggregations 2024-06-18 21:00:26 -05:00
Eric Eastwood
39b4f10533 Update comments 2024-06-18 19:55:12 -05:00
Eric Eastwood
71eabe5e63 Make room name optional 2024-06-18 19:41:41 -05:00
Eric Eastwood
70ecd4d8d3 Fix lint 2024-06-18 19:38:35 -05:00
Eric Eastwood
9791209a3d Add more tests 2024-06-18 18:10:17 -05:00
Eric Eastwood
81d36f36c1 Add tests for limited 2024-06-18 15:28:23 -05:00
Eric Eastwood
19b2297171 Calculate num_live 2024-06-18 10:36:50 -05:00
Eric Eastwood
94279915d4 Clean up knock_state comments 2024-06-18 10:09:33 -05:00
Eric Eastwood
9089bfe4dc Remove required_state for now 2024-06-18 10:06:29 -05:00
Eric Eastwood
5c175d5488 Add some notes from pairing 2024-06-18 09:35:20 -05:00
Eric Eastwood
aa5f54aa13 Start on required_state 2024-06-18 08:26:10 -05:00
Eric Eastwood
8ce06f1452 Fix sort being lost 2024-06-17 18:54:23 -05:00
Eric Eastwood
5e2fd4e93c Add changelog 2024-06-17 18:29:44 -05:00
Eric Eastwood
3e0f759dbc Strip invite/knock event itself and avoid mutating event unsigned
Make sure we don't run into
https://github.com/element-hq/synapse/issues/14919
(https://github.com/matrix-org/synapse/issues/14919)
2024-06-17 18:26:59 -05:00
Eric Eastwood
079194c547 Return some room timeline data in Sliding Sync 2024-06-17 18:03:02 -05:00
3 changed files with 597 additions and 47 deletions

View File

@@ -18,7 +18,18 @@
#
#
import logging
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
from typing import (
TYPE_CHECKING,
AbstractSet,
Any,
Dict,
Final,
List,
Optional,
Set,
Tuple,
TypeVar,
)
import attr
from immutabledict import immutabledict
@@ -33,6 +44,7 @@ from synapse.types import (
PersistedEventPosition,
Requester,
RoomStreamToken,
StateMap,
StreamKeyType,
StreamToken,
UserID,
@@ -75,6 +87,17 @@ def filter_membership_for_sync(
return membership != Membership.LEAVE or sender not in (user_id, None)
R = TypeVar("R")
def get_first_item_in_set(target_set: Optional[AbstractSet[R]]) -> Optional[R]:
"""
Helper to grab the "first" item in a set. A set is an unordered collection so this
is just a way to grab some item in the set.
"""
return next(iter(target_set or []), None)
# We can't freeze this class because we want to update it in place with the
# de-duplicated data.
@attr.s(slots=True, auto_attribs=True)
@@ -84,14 +107,115 @@ class RoomSyncConfig:
Attributes:
timeline_limit: The maximum number of events to return in the timeline.
required_state: The set of state events requested for the room. The
values are close to `StateKey` but actually use a syntax where you can
provide `*` wildcard and `$LAZY` for lazy room members as the `state_key` part
of the tuple (type, state_key).
required_state_map: Map from state type to a set of state (type, state_key)
tuples requested for the room. The values are close to `StateKey` but actually
use a syntax where you can provide `*` wildcard and `$LAZY` for lazy room
members as the `state_key` part of the tuple (type, state_key).
"""
timeline_limit: int
required_state: Set[Tuple[str, str]]
required_state_map: Dict[str, Set[Tuple[str, str]]]
@classmethod
def from_room_config(
cls,
room_params: SlidingSyncConfig.CommonRoomParameters,
) -> "RoomSyncConfig":
"""
Create a `RoomSyncConfig` from a `SlidingSyncList`/`RoomSubscription` config.
Args:
room_params: `SlidingSyncConfig.SlidingSyncList` or `SlidingSyncConfig.RoomSubscription`
"""
required_state_map: Dict[str, Set[Tuple[str, str]]] = {}
for (
state_type,
state_key,
) in room_params.required_state:
# If we already have a wildcard, we don't need to add anything else
if (
# We assume that if a wildcard is present, it's the only thing in the
# set.
get_first_item_in_set(required_state_map.get(state_type))
== (state_type, StateKeys.WILDCARD)
):
continue
# If we're getting a wildcard, that's all that matters so get rid of any
# other state keys
if state_key == StateKeys.WILDCARD:
required_state_map[state_type] = {(state_type, state_key)}
# Otherwise, just add it to the set
else:
if required_state_map.get(state_type) is None:
required_state_map[state_type] = {(state_type, state_key)}
else:
required_state_map[state_type].add((state_type, state_key))
return cls(
timeline_limit=room_params.timeline_limit,
required_state_map=required_state_map,
)
def deep_copy(self) -> "RoomSyncConfig":
required_state_map: Dict[str, Set[Tuple[str, str]]] = {
state_type: state_key_set.copy()
for state_type, state_key_set in self.required_state_map.items()
}
return RoomSyncConfig(
timeline_limit=self.timeline_limit,
required_state_map=required_state_map,
)
def combine_room_sync_config(
self, other_room_sync_config: "RoomSyncConfig"
) -> None:
"""
Combine this `RoomSyncConfig` with another `RoomSyncConfig` and take the
superset union of the two.
"""
# Take the highest timeline limit
if self.timeline_limit < other_room_sync_config.timeline_limit:
self.timeline_limit = other_room_sync_config.timeline_limit
# Union the required state
for (
state_type,
state_key_set,
) in other_room_sync_config.required_state_map.items():
# If we already have a wildcard, we don't need to add anything else
if get_first_item_in_set(self.required_state_map.get(state_type)) == (
state_type,
StateKeys.WILDCARD,
):
continue
for _state_type, state_key in state_key_set:
# If we're getting a wildcard, that's all that matters so get rid of any
# other state keys
if state_key == StateKeys.WILDCARD:
self.required_state_map[state_type] = {(state_type, state_key)}
break
# Otherwise, just add it to the set
else:
if self.required_state_map.get(state_type) is None:
self.required_state_map[state_type] = {(state_type, state_key)}
else:
self.required_state_map[state_type].add((state_type, state_key))
class StateKeys:
"""
Understood values of the `state_key` part of the tuple (type, state_key) in
`required_state`.
"""
# Include all state events of the given type
WILDCARD: Final = "*"
# Lazy-load room membership events (include room membership events for any event
# `sender` in the timeline)
LAZY: Final = "$LAZY"
@attr.s(slots=True, frozen=True, auto_attribs=True)
@@ -236,6 +360,8 @@ class SlidingSyncHandler:
# Assemble sliding window lists
lists: Dict[str, SlidingSyncResult.SlidingWindowList] = {}
# Keep track of the rooms that we're going to display and need to fetch more
# info about
relevant_room_map: Dict[str, RoomSyncConfig] = {}
if sync_config.lists:
# Get all of the room IDs that the user should be able to see in the sync
@@ -254,49 +380,85 @@ class SlidingSyncHandler:
sync_config.user, sync_room_map, list_config.filters, to_token
)
# Sort the list
sorted_room_info = await self.sort_rooms(
filtered_sync_room_map, to_token
)
# Find which rooms are partially stated and may need to be filtered out
# depending on the `required_state` requested (see below).
partial_state_room_map = await self.store.is_partial_state_room_batched(
filtered_sync_room_map.keys()
)
# Since creating the `RoomSyncConfig` takes some work, let's just do it
# once and make a copy whenever we need it.
room_sync_config = RoomSyncConfig.from_room_config(list_config)
ops: List[SlidingSyncResult.SlidingWindowList.Operation] = []
if list_config.ranges:
for range in list_config.ranges:
sliced_room_ids = [
room_id
# Both sides of range are inclusive
for room_id, _ in sorted_room_info[range[0] : range[1] + 1]
]
room_ids_in_list = []
# We're going to loop through the sorted list of rooms starting
# at the range start index and keep adding rooms until we fill
# up the range or run out of rooms.
#
# Both sides of range are inclusive
current_range_index = range[0]
range_end_index = range[1]
while (
current_range_index <= range_end_index
and current_range_index <= len(sorted_room_info) - 1
):
room_id, _ = sorted_room_info[current_range_index]
membership_state_keys = (
room_sync_config.required_state_map.get(
EventTypes.Member
)
)
# Exclude partially stated rooms unless the `required_state`
# only has `["m.room.member", "$LAZY"]` for membership.
if (
partial_state_room_map.get(room_id)
and membership_state_keys is not None
and len(membership_state_keys) == 1
and get_first_item_in_set(membership_state_keys)
== (EventTypes.Member, StateKeys.LAZY)
):
# Since we're skipping this room, we need to allow
# for the next room to take its place in the list
range_end_index += 1
continue
# Take the superset of the `RoomSyncConfig` for each room.
#
# Update our `relevant_room_map` with the room we're going
# to display and need to fetch more info about.
existing_room_sync_config = relevant_room_map.get(room_id)
if existing_room_sync_config is not None:
existing_room_sync_config.combine_room_sync_config(
room_sync_config
)
else:
# Make a copy so if we modify it later, it doesn't
# affect all references.
relevant_room_map[room_id] = (
room_sync_config.deep_copy()
)
room_ids_in_list.append(room_id)
current_range_index += 1
ops.append(
SlidingSyncResult.SlidingWindowList.Operation(
op=OperationType.SYNC,
range=range,
room_ids=sliced_room_ids,
room_ids=room_ids_in_list,
)
)
# Take the superset of the `RoomSyncConfig` for each room
for room_id in sliced_room_ids:
if relevant_room_map.get(room_id) is not None:
# Take the highest timeline limit
if (
relevant_room_map[room_id].timeline_limit
< list_config.timeline_limit
):
relevant_room_map[room_id].timeline_limit = (
list_config.timeline_limit
)
# Union the required state
relevant_room_map[room_id].required_state.update(
list_config.required_state
)
else:
relevant_room_map[room_id] = RoomSyncConfig(
timeline_limit=list_config.timeline_limit,
required_state=set(list_config.required_state),
)
lists[list_key] = SlidingSyncResult.SlidingWindowList(
count=len(sorted_room_info),
ops=ops,
@@ -645,9 +807,6 @@ class SlidingSyncHandler:
user_id = user.to_string()
# TODO: Apply filters
#
# TODO: Exclude partially stated rooms unless the `required_state` has
# `["m.room.member", "$LAZY"]`
filtered_room_id_set = set(sync_room_map.keys())
@@ -818,7 +977,7 @@ class SlidingSyncHandler:
# Assemble the list of timeline events
#
# It would be nice to make the `rooms` response more uniform regardless of
# FIXME: It would be nice to make the `rooms` response more uniform regardless of
# membership. Currently, we have to make all of these optional because
# `invite`/`knock` rooms only have `stripped_state`. See
# https://github.com/matrix-org/matrix-spec-proposals/pull/3575#discussion_r1653045932
@@ -985,6 +1144,81 @@ class SlidingSyncHandler:
# state reset happened. Perhaps we should indicate this by setting `initial:
# True` and empty `required_state`.
# TODO: Since we can't determine whether we've already sent a room down this
# Sliding Sync connection before (we plan to add this optimization in the
# future), we're always returning the requested room state instead of
# updates.
initial = True
# Fetch the required state for the room
#
# No `required_state` for invite/knock rooms (just `stripped_state`)
#
# FIXME: It would be nice to make the `rooms` response more uniform regardless
# of membership. Currently, we have to make this optional because
# `invite`/`knock` rooms only have `stripped_state`. See
# https://github.com/matrix-org/matrix-spec-proposals/pull/3575#discussion_r1653045932
room_state: Optional[StateMap[EventBase]] = None
if rooms_membership_for_user_at_to_token.membership not in (
Membership.INVITE,
Membership.KNOCK,
):
# Calculate the required state for the room and make it into the form of a
# `StateFilter`
required_state_types: List[Tuple[str, Optional[str]]] = []
for (
state_type,
state_key_set,
) in room_sync_config.required_state_map.items():
for _state_type, state_key in state_key_set:
if state_key == StateKeys.WILDCARD:
# `None` is a wildcard in the `StateFilter`
required_state_types.append((state_type, None))
# We need to fetch all relevant people when we're lazy-loading membership
if state_type == EventTypes.Member and state_key == StateKeys.LAZY:
# Everyone in the timeline is relevant
timeline_membership: Set[str] = set()
if timeline_events is not None:
for timeline_event in timeline_events:
timeline_membership.add(timeline_event.sender)
for user_id in timeline_membership:
required_state_types.append((EventTypes.Member, user_id))
# TODO: We probably also care about invite, ban, kick, targets, etc
# but the spec only mentions "senders".
else:
required_state_types.append((state_type, state_key))
state_filter = StateFilter.from_types(required_state_types)
# We can return all of the state that was requested if we're doing an
# initial sync
if initial:
# People shouldn't see past their leave/ban event
if rooms_membership_for_user_at_to_token.membership in (
Membership.LEAVE,
Membership.BAN,
):
room_state = await self.storage_controllers.state.get_state_at(
room_id,
stream_position=rooms_membership_for_user_at_to_token.event_pos.to_room_stream_token(),
state_filter=state_filter,
await_full_state=False,
)
else:
# Otherwise, we can get the latest current state in the room
room_state = await self.storage_controllers.state.get_current_state(
room_id,
state_filter,
await_full_state=False,
)
# TODO: Query `current_state_delta_stream` and reverse/rewind back to the `to_token`
else:
# TODO: Once we can figure out if we've sent a room down this connection before,
# we can return updates instead of the full required state.
raise NotImplementedError()
return SlidingSyncResult.RoomResult(
# TODO: Dummy value
name=None,
@@ -992,13 +1226,8 @@ class SlidingSyncHandler:
avatar=None,
# TODO: Dummy value
heroes=None,
# TODO: Since we can't determine whether we've already sent a room down this
# Sliding Sync connection before (we plan to add this optimization in the
# future), we're always returning the requested room state instead of
# updates.
initial=True,
# TODO: Dummy value
required_state=[],
initial=initial,
required_state=list(room_state.values()) if room_state else None,
timeline_events=timeline_events,
bundled_aggregations=bundled_aggregations,
# TODO: Dummy value

View File

@@ -18,6 +18,7 @@
#
#
import logging
from copy import deepcopy
from unittest.mock import patch
from parameterized import parameterized
@@ -26,20 +27,280 @@ from twisted.test.proto_helpers import MemoryReactor
from synapse.api.constants import AccountDataTypes, EventTypes, JoinRules, Membership
from synapse.api.room_versions import RoomVersions
from synapse.handlers.sliding_sync import SlidingSyncConfig
from synapse.handlers.sliding_sync import RoomSyncConfig, StateKeys
from synapse.rest import admin
from synapse.rest.client import knock, login, room
from synapse.server import HomeServer
from synapse.storage.util.id_generators import MultiWriterIdGenerator
from synapse.types import JsonDict, UserID
from synapse.types.handlers import SlidingSyncConfig
from synapse.util import Clock
from tests.replication._base import BaseMultiWorkerStreamTestCase
from tests.unittest import HomeserverTestCase
from tests.unittest import HomeserverTestCase, TestCase
logger = logging.getLogger(__name__)
class RoomSyncConfigTestCase(TestCase):
def _assert_room_config_equal(
self, actual: RoomSyncConfig, expected: RoomSyncConfig
) -> None:
self.assertEqual(actual.timeline_limit, expected.timeline_limit)
# `self.assertEqual(...)` works fine to catch differences but the output is
# almost impossible to read because of the way it truncates the output
self.assertCountEqual(actual.required_state_map, expected.required_state_map)
for event_type, expected_state_keys in expected.required_state_map.items():
self.assertCountEqual(
actual.required_state_map[event_type], expected_state_keys
)
def test_from_list_config(self) -> None:
"""
Test that we can convert a `SlidingSyncConfig.SlidingSyncList` to a
`RoomSyncConfig`.
"""
list_config = SlidingSyncConfig.SlidingSyncList(
timeline_limit=10,
required_state=[
(EventTypes.Name, ""),
(EventTypes.Member, "@foo"),
(EventTypes.Member, "@bar"),
(EventTypes.Member, "@baz"),
(EventTypes.CanonicalAlias, ""),
],
)
room_sync_config = RoomSyncConfig.from_room_config(list_config)
self._assert_room_config_equal(
room_sync_config,
RoomSyncConfig(
timeline_limit=10,
required_state_map={
EventTypes.Name: {(EventTypes.Name, "")},
EventTypes.Member: {
(EventTypes.Member, "@foo"),
(EventTypes.Member, "@bar"),
(EventTypes.Member, "@baz"),
},
EventTypes.CanonicalAlias: {(EventTypes.CanonicalAlias, "")},
},
),
)
def test_from_room_subscription(self) -> None:
"""
Test that we can convert a `SlidingSyncConfig.RoomSubscription` to a
`RoomSyncConfig`.
"""
room_subscription_config = SlidingSyncConfig.RoomSubscription(
timeline_limit=10,
required_state=[
(EventTypes.Name, ""),
(EventTypes.Member, "@foo"),
(EventTypes.Member, "@bar"),
(EventTypes.Member, "@baz"),
(EventTypes.CanonicalAlias, ""),
],
)
room_sync_config = RoomSyncConfig.from_room_config(room_subscription_config)
self._assert_room_config_equal(
room_sync_config,
RoomSyncConfig(
timeline_limit=10,
required_state_map={
EventTypes.Name: {(EventTypes.Name, "")},
EventTypes.Member: {
(EventTypes.Member, "@foo"),
(EventTypes.Member, "@bar"),
(EventTypes.Member, "@baz"),
},
EventTypes.CanonicalAlias: {(EventTypes.CanonicalAlias, "")},
},
),
)
def test_from_room_config_wildcard(self) -> None:
"""
Test that a wildcard (*) will override all other values for the same event type.
"""
list_config = SlidingSyncConfig.SlidingSyncList(
timeline_limit=10,
required_state=[
(EventTypes.Name, ""),
(EventTypes.Member, "@foo"),
(EventTypes.Member, StateKeys.WILDCARD),
(EventTypes.Member, "@bar"),
(EventTypes.Member, StateKeys.LAZY),
(EventTypes.Member, "@baz"),
(EventTypes.CanonicalAlias, ""),
],
)
room_sync_config = RoomSyncConfig.from_room_config(list_config)
self._assert_room_config_equal(
room_sync_config,
RoomSyncConfig(
timeline_limit=10,
required_state_map={
EventTypes.Name: {(EventTypes.Name, "")},
EventTypes.Member: {
(EventTypes.Member, "*"),
},
EventTypes.CanonicalAlias: {(EventTypes.CanonicalAlias, "")},
},
),
)
def test_from_room_config_lazy_members(self) -> None:
"""
`$LAZY` room members should just be another additional key next to other
explicit keys. We will unroll the special `$LAZY` meaning later.
"""
list_config = SlidingSyncConfig.SlidingSyncList(
timeline_limit=10,
required_state=[
(EventTypes.Name, ""),
(EventTypes.Member, "@foo"),
(EventTypes.Member, "@bar"),
(EventTypes.Member, StateKeys.LAZY),
(EventTypes.Member, "@baz"),
(EventTypes.CanonicalAlias, ""),
],
)
room_sync_config = RoomSyncConfig.from_room_config(list_config)
self._assert_room_config_equal(
room_sync_config,
RoomSyncConfig(
timeline_limit=10,
required_state_map={
EventTypes.Name: {(EventTypes.Name, "")},
EventTypes.Member: {
(EventTypes.Member, "@foo"),
(EventTypes.Member, "@bar"),
(EventTypes.Member, StateKeys.LAZY),
(EventTypes.Member, "@baz"),
},
EventTypes.CanonicalAlias: {(EventTypes.CanonicalAlias, "")},
},
),
)
@parameterized.expand(
[
(
"No direct overlap",
# A
RoomSyncConfig(
timeline_limit=9,
required_state_map={
EventTypes.Name: {(EventTypes.Name, "")},
EventTypes.Member: {
(EventTypes.Member, "@foo"),
(EventTypes.Member, "@bar"),
},
},
),
# B
RoomSyncConfig(
timeline_limit=10,
required_state_map={
EventTypes.Member: {
(EventTypes.Member, StateKeys.LAZY),
(EventTypes.Member, "@baz"),
},
EventTypes.CanonicalAlias: {(EventTypes.CanonicalAlias, "")},
},
),
# Expected
RoomSyncConfig(
timeline_limit=10,
required_state_map={
EventTypes.Name: {(EventTypes.Name, "")},
EventTypes.Member: {
(EventTypes.Member, "@foo"),
(EventTypes.Member, "@bar"),
(EventTypes.Member, StateKeys.LAZY),
(EventTypes.Member, "@baz"),
},
EventTypes.CanonicalAlias: {(EventTypes.CanonicalAlias, "")},
},
),
),
(
"Wildcard overlap",
# A
RoomSyncConfig(
timeline_limit=10,
required_state_map={
EventTypes.Dummy: {(EventTypes.Dummy, "foo")},
EventTypes.Member: {
(EventTypes.Member, "*"),
},
"org.matrix.flowers": {("org.matrix.flowers", "*")},
},
),
# B
RoomSyncConfig(
timeline_limit=9,
required_state_map={
EventTypes.Dummy: {(EventTypes.Dummy, "*")},
"org.matrix.flowers": {("org.matrix.flowers", "tulips")},
},
),
# Expected
RoomSyncConfig(
timeline_limit=10,
required_state_map={
EventTypes.Dummy: {
(EventTypes.Dummy, "*"),
},
EventTypes.Member: {
(EventTypes.Member, "*"),
},
"org.matrix.flowers": {("org.matrix.flowers", "*")},
},
),
),
]
)
def test_combine_room_sync_config(
self,
_test_label: str,
a: RoomSyncConfig,
b: RoomSyncConfig,
expected: RoomSyncConfig,
) -> None:
"""
Combine A into B and B into A to make sure we get the same result.
"""
# Since we're mutating these in place, make a copy for each of our trials
room_sync_config_a = deepcopy(a)
room_sync_config_b = deepcopy(b)
# Combine B into A
room_sync_config_a.combine_room_sync_config(room_sync_config_b)
self._assert_room_config_equal(room_sync_config_a, expected)
# Since we're mutating these in place, make a copy for each of our trials
room_sync_config_a = deepcopy(a)
room_sync_config_b = deepcopy(b)
# Combine A into B
room_sync_config_b.combine_room_sync_config(room_sync_config_a)
self._assert_room_config_equal(room_sync_config_b, expected)
class GetSyncRoomIdsForUserTestCase(HomeserverTestCase):
"""
Tests Sliding Sync handler `get_sync_room_ids_for_user()` to make sure it returns

View File

@@ -1237,6 +1237,7 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
)
self.store = hs.get_datastores().main
self.event_sources = hs.get_event_sources()
self.storage_controllers = hs.get_storage_controllers()
def _add_new_dm_to_global_account_data(
self, source_user_id: str, target_user_id: str, target_room_id: str
@@ -2681,3 +2682,62 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
False,
channel.json_body["rooms"][room_id1],
)
def test_rooms_required_state_initial_sync(self) -> None:
"""
Test `rooms.required_state` returns requested state events in the room during an
initial sync.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")
room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok)
self.helper.join(room_id1, user1_id, tok=user1_tok)
# Make the Sliding Sync request
channel = self.make_request(
"POST",
self.sync_endpoint,
{
"lists": {
"foo-list": {
"ranges": [[0, 1]],
"required_state": [
[EventTypes.Create, ""],
[EventTypes.RoomHistoryVisibility, ""],
# This one doesn't exist in the room
[EventTypes.Tombstone, ""],
],
"timeline_limit": 3,
}
}
},
access_token=user1_tok,
)
self.assertEqual(channel.code, 200, channel.json_body)
state_map = self.get_success(
self.storage_controllers.state.get_current_state_ids(room_id1)
)
self.assertEqual(
[
state_event["event_id"]
for state_event in channel.json_body["rooms"][room_id1][
"required_state"
]
],
[
state_map[(EventTypes.Create, "")],
state_map[(EventTypes.RoomHistoryVisibility, "")],
],
channel.json_body["rooms"][room_id1]["required_state"],
)
# TODO: Add more `required_state` tests
# TODO: Add test for `"required_state": [ ["*","*"] ],`
# TODO: Add test for `"required_state": [ ["*","foobarbaz"] ],`
# TODO: Add tests for partially-stated rooms being excluded