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

Commit 5dae42f

Browse files
author
Mathieu Velten
committed
Faster joins: don't stale when an user joins during a fast join
Signed-off-by: Mathieu Velten <mathieuv@matrix.org>
1 parent 2888d7e commit 5dae42f

File tree

5 files changed

+50
-22
lines changed

5 files changed

+50
-22
lines changed

changelog.d/14606.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Faster joins: don't stale when another user joins during a fast join resync.

synapse/handlers/event_auth.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ async def check_restricted_join_rules(
202202
state_ids: StateMap[str],
203203
room_version: RoomVersion,
204204
user_id: str,
205-
prev_member_event: Optional[EventBase],
205+
prev_membership: Optional[str],
206206
) -> None:
207207
"""
208208
Check whether a user can join a room without an invite due to restricted join rules.
@@ -214,15 +214,13 @@ async def check_restricted_join_rules(
214214
state_ids: The state of the room as it currently is.
215215
room_version: The room version of the room being joined.
216216
user_id: The user joining the room.
217-
prev_member_event: The current membership event for this user.
217+
prev_membership: The current membership state for this user.
218218
219219
Raises:
220220
AuthError if the user cannot join the room.
221221
"""
222222
# If the member is invited or currently joined, then nothing to do.
223-
if prev_member_event and (
224-
prev_member_event.membership in (Membership.JOIN, Membership.INVITE)
225-
):
223+
if prev_membership in (Membership.JOIN, Membership.INVITE):
226224
return
227225

228226
# This is not a room with a restricted join rule, so we don't need to do the

synapse/handlers/federation.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,8 @@ async def do_invite_join(
599599
except ValueError:
600600
pass
601601

602+
already_partially_joined = await self.store.is_partial_state_room(room_id)
603+
602604
ret = await self.federation_client.send_join(
603605
host_list, event, room_version_obj
604606
)
@@ -629,7 +631,7 @@ async def do_invite_join(
629631
state_events=state,
630632
)
631633

632-
if ret.partial_state:
634+
if ret.partial_state and not already_partially_joined:
633635
# Mark the room as having partial state.
634636
# The background process is responsible for unmarking this flag,
635637
# even if the join fails.
@@ -676,7 +678,7 @@ async def do_invite_join(
676678
# state for the room.
677679
# If the join failed, the background process is responsible for
678680
# cleaning up — including unmarking the room as a partial state room.
679-
if ret.partial_state:
681+
if ret.partial_state and not already_partially_joined:
680682
# Kick off the process of asynchronously fetching the state for this
681683
# room.
682684
run_as_background_process(

synapse/handlers/federation_event.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,16 +438,17 @@ async def check_join_restrictions(
438438
# Check if the user is already in the room or invited to the room.
439439
user_id = event.state_key
440440
prev_member_event_id = prev_state_ids.get((EventTypes.Member, user_id), None)
441-
prev_member_event = None
441+
prev_membership = None
442442
if prev_member_event_id:
443443
prev_member_event = await self._store.get_event(prev_member_event_id)
444+
prev_membership = prev_member_event.membership
444445

445446
# Check if the member should be allowed access via membership in a space.
446447
await self._event_auth_handler.check_restricted_join_rules(
447448
prev_state_ids,
448449
event.room_version,
449450
user_id,
450-
prev_member_event,
451+
prev_membership,
451452
)
452453

453454
@trace

synapse/handlers/room_member.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -823,8 +823,14 @@ async def update_membership_locked(
823823

824824
latest_event_ids = await self.store.get_prev_events_for_room(room_id)
825825

826+
# There are 2 reasons that mandate to have the full state:
827+
# - to calculate `is_host_in_room`, however we now approximate that we consider the server
828+
# in the room if we are still in the middle of a fast join.
829+
# - to calculate if another user trying to join is allowed to. In this case we now
830+
# make another `make/send_join` to a resident server to validate that. We could just accept
831+
# it but it would mean that we would potentially leaked the history to a banned user.
826832
state_before_join = await self.state_handler.compute_state_after_events(
827-
room_id, latest_event_ids
833+
room_id, latest_event_ids, await_full_state=False
828834
)
829835

830836
# TODO: Refactor into dictionary of explicitly allowed transitions
@@ -881,7 +887,10 @@ async def update_membership_locked(
881887
if action == "kick":
882888
raise AuthError(403, "The target user is not in the room")
883889

884-
is_host_in_room = await self._is_host_in_room(state_before_join)
890+
is_partial_state_room = await self.store.is_partial_state_room(room_id)
891+
is_host_in_room = await self._is_host_in_room(
892+
is_partial_state_room, state_before_join
893+
)
885894

886895
if effective_membership_state == Membership.JOIN:
887896
if requester.is_guest:
@@ -927,6 +936,7 @@ async def update_membership_locked(
927936
room_id,
928937
remote_room_hosts,
929938
content,
939+
is_partial_state_room,
930940
is_host_in_room,
931941
state_before_join,
932942
)
@@ -1073,6 +1083,7 @@ async def _should_perform_remote_join(
10731083
room_id: str,
10741084
remote_room_hosts: List[str],
10751085
content: JsonDict,
1086+
is_partial_state_room: bool,
10761087
is_host_in_room: bool,
10771088
state_before_join: StateMap[str],
10781089
) -> Tuple[bool, List[str]]:
@@ -1093,6 +1104,8 @@ async def _should_perform_remote_join(
10931104
remote_room_hosts: A list of remote room hosts.
10941105
content: The content to use as the event body of the join. This may
10951106
be modified.
1107+
is_partial_state_room: True if the server currently doesn't hold the fully
1108+
state of the room.
10961109
is_host_in_room: True if the host is in the room.
10971110
state_before_join: The state before the join event (i.e. the resolution of
10981111
the states after its parent events).
@@ -1109,6 +1122,20 @@ async def _should_perform_remote_join(
11091122
if not is_host_in_room:
11101123
return True, remote_room_hosts
11111124

1125+
prev_member_event_id = state_before_join.get((EventTypes.Member, user_id), None)
1126+
previous_membership = None
1127+
if prev_member_event_id:
1128+
prev_member_event = await self.store.get_event(prev_member_event_id)
1129+
previous_membership = prev_member_event.membership
1130+
1131+
# If we are not fully join yet, and the target is not already in the room,
1132+
# let's do a remote join so another server with the full state can validate
1133+
# that the user has not been ban for example.
1134+
# We could just accept the join and wait for state-res to resolve that later on
1135+
# but we would then leak room history to this person until then, which is pretty bad.
1136+
if is_partial_state_room and previous_membership != Membership.JOIN:
1137+
return True, remote_room_hosts
1138+
11121139
# If the host is in the room, but not one of the authorised hosts
11131140
# for restricted join rules, a remote join must be used.
11141141
room_version = await self.store.get_room_version(room_id)
@@ -1122,15 +1149,8 @@ async def _should_perform_remote_join(
11221149

11231150
# If the user is invited to the room or already joined, the join
11241151
# event can always be issued locally.
1125-
prev_member_event_id = state_before_join.get((EventTypes.Member, user_id), None)
1126-
prev_member_event = None
1127-
if prev_member_event_id:
1128-
prev_member_event = await self.store.get_event(prev_member_event_id)
1129-
if prev_member_event.membership in (
1130-
Membership.JOIN,
1131-
Membership.INVITE,
1132-
):
1133-
return False, []
1152+
if previous_membership in (Membership.JOIN, Membership.INVITE):
1153+
return False, []
11341154

11351155
# If the local host has a user who can issue invites, then a local
11361156
# join can be done.
@@ -1154,7 +1174,7 @@ async def _should_perform_remote_join(
11541174

11551175
# Ensure the member should be allowed access via membership in a room.
11561176
await self.event_auth_handler.check_restricted_join_rules(
1157-
state_before_join, room_version, user_id, prev_member_event
1177+
state_before_join, room_version, user_id, previous_membership
11581178
)
11591179

11601180
# If this is going to be a local join, additional information must
@@ -1634,7 +1654,13 @@ async def _make_and_store_3pid_invite(
16341654
)
16351655
return event, stream_id
16361656

1637-
async def _is_host_in_room(self, current_state_ids: StateMap[str]) -> bool:
1657+
async def _is_host_in_room(
1658+
self, is_partial_state_room: bool, current_state_ids: StateMap[str]
1659+
) -> bool:
1660+
# When we only have a partial state, let's assume we are still in the room
1661+
if is_partial_state_room:
1662+
return True
1663+
16381664
# Have we just created the room, and is this about to be the very
16391665
# first member event?
16401666
create_event_id = current_state_ids.get(("m.room.create", ""))

0 commit comments

Comments
 (0)