mirror of
https://github.com/element-hq/synapse.git
synced 2026-10-23 13:03:28 +00:00
Fix new state saved with the last local user's leave missing from state_after (#20314)
> [!NOTE] > This PR will be rebased on `develop` once https://github.com/element-hq/synapse/pull/20081 is merged This PR is a follow up to https://github.com/element-hq/synapse/pull/20081 and is based on its code. The scope of that original PR is limited to recovering the leave event missing from `state_after`. I noticed that state events from the leave event batch would only be recovered if they were previously defined. That made me add a pre-condition to the test: the topic had to be set first. https://github.com/element-hq/synapse/blob/bccf1679aed0a5d86de87165c548f8c459758055/tests/handlers/test_sync.py#L1096-L1099 This PR attempts to recover new state events that only appeared first in the leave batch. ### Pull Request Checklist <!-- Please read https://element-hq.github.io/synapse/latest/development/contributing_guide.html before submitting your pull request --> * [x] Pull request is based on the develop branch * [x] Pull request includes a [changelog file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog). The entry should: - Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from `EventStore` to `EventWorkerStore`.". - Use markdown where necessary, mostly for `code blocks`. - End with either a period (.) or an exclamation mark (!). - Start with a capital letter. - Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry. * [x] [Code style](https://element-hq.github.io/synapse/latest/code_style.html) is correct (run the [linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters)) --------- Co-authored-by: Fefaine <fefaine@gmail.com> Co-authored-by: Quentin Gliech <quenting@element.io> Co-authored-by: Eric Eastwood <madlittlemods@gmail.com> Co-authored-by: Eric Eastwood <erice@element.io>
This commit is contained in:
3 files changed
+37
-22
No files matched your search
@@ -0,0 +1 @@
|
||||
Fix new state saved with the last local user's leave being missing from `state_after` ([MSC4222](https://github.com/matrix-org/matrix-spec-proposals/pull/4222)) on incremental `/sync`.
|
||||
+17
-11
@@ -1738,11 +1738,12 @@ class SyncHandler:
|
||||
# any earlier delta for it is stale. That batch ends the
|
||||
# timeline, so we look up the keys in the timeline in state
|
||||
# groups, which still have the state at `end_token`.
|
||||
# - has no delta at all if it is new.
|
||||
# TODO: Recover it.
|
||||
# - has no delta at all if it is new, so we also look up all the
|
||||
# keys in the timeline (see below).
|
||||
# Keys that only changed earlier keep their earlier delta, and
|
||||
# other keys need no update.
|
||||
cleared_state_keys: set[tuple[str, str]] = set()
|
||||
own_membership_key = (EventTypes.Member, sync_config.user.to_string())
|
||||
state_keys_to_look_up: set[tuple[str, str]] = set()
|
||||
for delta in deltas:
|
||||
key = (delta.event_type, delta.state_key)
|
||||
if delta.event_id is None:
|
||||
@@ -1750,11 +1751,8 @@ class SyncHandler:
|
||||
# - it is in the timeline (see above).
|
||||
# - OR it is the user's own membership, in case a filter
|
||||
# removed their leave from the timeline.
|
||||
if key in timeline_state or key == (
|
||||
EventTypes.Member,
|
||||
sync_config.user.to_string(),
|
||||
):
|
||||
cleared_state_keys.add(key)
|
||||
if key in timeline_state or key == own_membership_key:
|
||||
state_keys_to_look_up.add(key)
|
||||
continue
|
||||
|
||||
# Note that deltas are in stream ordering, so if there are
|
||||
@@ -1762,13 +1760,21 @@ class SyncHandler:
|
||||
# the latest one.
|
||||
delta_state_ids[key] = delta.event_id
|
||||
changed_keys.add(key)
|
||||
cleared_state_keys.discard(key)
|
||||
state_keys_to_look_up.discard(key)
|
||||
|
||||
if cleared_state_keys:
|
||||
if own_membership_key in state_keys_to_look_up:
|
||||
# The server left the room in this window. It only wrote deltas
|
||||
# for the keys that were in the room state before the batch that
|
||||
# made it leave, so the keys that batch added have no delta at
|
||||
# all. Look up all the keys in the timeline: the ones with a delta
|
||||
# are already in the set.
|
||||
state_keys_to_look_up.update(timeline_state)
|
||||
|
||||
if state_keys_to_look_up:
|
||||
state_at_end = await self._state_storage_controller.get_state_ids_at(
|
||||
room_id,
|
||||
stream_position=end_token,
|
||||
state_filter=StateFilter.from_types(cleared_state_keys),
|
||||
state_filter=StateFilter.from_types(state_keys_to_look_up),
|
||||
# Don't wait for the full state. Partial state has every state
|
||||
# event except remote memberships, and any remote membership
|
||||
# we look up here is from an event in the timeline, which we
|
||||
|
||||
+19
-11
@@ -1093,18 +1093,26 @@ class SyncTestCase(tests.unittest.HomeserverTestCase):
|
||||
{("m.room.member", alice): leave_event},
|
||||
)
|
||||
|
||||
@parameterized.expand(
|
||||
[(True,), (False,)],
|
||||
name_func=lambda func, num, p: (
|
||||
f"{func.__name__}_{'existing' if p.args[0] else 'new'}_topic"
|
||||
),
|
||||
)
|
||||
def test_state_after_leave_last_local_user_with_state_change_persisted_together(
|
||||
self,
|
||||
self, topic_existed: bool
|
||||
) -> None:
|
||||
"""When another state event is persisted in the same batch as the last local
|
||||
user's leave, both the leave and that state event must appear in state_after
|
||||
on an incremental sync.
|
||||
on an incremental sync, whether or not the room already had that state.
|
||||
|
||||
This is to make sure we play nicely with this behavior: When the server leaves a
|
||||
room, it will insert new rows with `event_id = null` into the
|
||||
`current_state_delta_stream` table for all current state.
|
||||
Neither the leave nor the state event persisted with it gets a row of its own,
|
||||
only the `event_id = null` rows of the state they replace.
|
||||
Neither the leave nor the state event persisted with it gets a row of its own:
|
||||
the leave only gets the `event_id = null` row of the leaving user's membership,
|
||||
and the state event only gets one if the room already had that state, so the
|
||||
two cases are recovered differently.
|
||||
"""
|
||||
if not self.use_state_after:
|
||||
self.skipTest("Only relevant for `state_after` (MSC4222)")
|
||||
@@ -1115,9 +1123,10 @@ class SyncTestCase(tests.unittest.HomeserverTestCase):
|
||||
alice_requester = create_requester(alice)
|
||||
|
||||
room_id = self.helper.create_room_as(alice, tok=alice_tok)
|
||||
self.helper.send_state(
|
||||
room_id, EventTypes.Topic, {"topic": "before leaving"}, tok=alice_tok
|
||||
)
|
||||
if topic_existed:
|
||||
self.helper.send_state(
|
||||
room_id, EventTypes.Topic, {"topic": "before leaving"}, tok=alice_tok
|
||||
)
|
||||
|
||||
# Sync up to get a since_token.
|
||||
initial_sync_result = self.get_success(
|
||||
@@ -1128,10 +1137,9 @@ class SyncTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
)
|
||||
|
||||
# Alice changes the topic and leaves, with both events persisted in one
|
||||
# batch. She is the last local user, so the server clears
|
||||
# current_state_events for this room, and neither event gets a delta of its
|
||||
# own.
|
||||
# Alice sets the topic and leaves, with both events persisted in one batch.
|
||||
# She is the last local user, so the server clears current_state_events for
|
||||
# this room, and neither event gets a delta of its own.
|
||||
self.get_success(
|
||||
self.hs.get_event_creation_handler().create_and_send_new_client_events(
|
||||
requester=alice_requester,
|
||||
|
||||
Reference in new issue
Block a user