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

Fix a rare bug where initial /syncs would fail #15383

Merged
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/15383.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a rare bug introduced in Synapse 1.66.0 where initial syncs would fail when the user had been kicked from a faster joined room that had not finished syncing.
24 changes: 19 additions & 5 deletions synapse/handlers/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,8 @@ async def compute_state_delta(

timeline_state = {}

# Membership events to fetch that can be found in the room state, or in
# the case of partial state rooms, the auth events of timeline events.
members_to_fetch = set()
first_event_by_sender_map = {}
for event in batch.events:
Expand All @@ -964,9 +966,19 @@ async def compute_state_delta(
# (if we are) to fix https://github.com/vector-im/riot-web/issues/7209
# We only need apply this on full state syncs given we disabled
# LL for incr syncs in #3840.
members_to_fetch.add(sync_config.user.to_string())

state_filter = StateFilter.from_lazy_load_member_list(members_to_fetch)
# We don't insert ourselves into `members_to_fetch`, because in some
# rare cases (an empty event batch with a now_token after the user's
# leave in a partial state room which another local user has
# joined), the room state will be missing our membership and there
# is no guarantee that our membership will be in the auth events of
# timeline events when the room is partial stated.
state_filter = StateFilter.from_lazy_load_member_list(
members_to_fetch.union((sync_config.user.to_string(),))
)
else:
state_filter = StateFilter.from_lazy_load_member_list(
members_to_fetch
)

# We are happy to use partial state to compute the `/sync` response.
# Since partial state may not include the lazy-loaded memberships we
Expand All @@ -988,7 +1000,9 @@ async def compute_state_delta(
# sync's timeline and the start of the current sync's timeline.
# See the docstring above for details.
state_ids: StateMap[str]

# We need to know whether the state we fetch may be partial, so check
# whether the room is partial stated *before* fetching it.
is_partial_state_room = await self.store.is_partial_state_room(room_id)
Comment on lines +1003 to +1005
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess to make this 100% robust we'd need to atomically fetch is_partial_state_room while we fetch the state ids?

(To be explicit, I don't think we should do this.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would work, yes. I wonder if partial state-dness should really be a property of state rather than events or rooms.

if full_state:
if batch:
state_at_timeline_end = (
Expand Down Expand Up @@ -1119,7 +1133,7 @@ async def compute_state_delta(
# If we only have partial state for the room, `state_ids` may be missing the
# memberships we wanted. We attempt to find some by digging through the auth
# events of timeline events.
if lazy_load_members and await self.store.is_partial_state_room(room_id):
if lazy_load_members and is_partial_state_room:
assert members_to_fetch is not None
assert first_event_by_sender_map is not None

Expand Down