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

Commit 6cddf24

Browse files
Mathieu VeltenSean QuahDavid Robertson
authored
Faster joins: don't stall when a user joins during a fast join (#14606)
Fixes #12801. Complement tests are at matrix-org/complement#567. Avoid blocking on full state when handling a subsequent join into a partial state room. Also always perform a remote join into partial state rooms, since we do not know whether the joining user has been banned and want to avoid leaking history to banned users. Signed-off-by: Mathieu Velten <[email protected]> Co-authored-by: Sean Quah <[email protected]> Co-authored-by: David Robertson <[email protected]>
1 parent d0c713c commit 6cddf24

File tree

12 files changed

+196
-94
lines changed

12 files changed

+196
-94
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 stall when another user joins during a fast join resync.

synapse/api/errors.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,3 +751,25 @@ class ModuleFailedException(Exception):
751751
Raised when a module API callback fails, for example because it raised an
752752
exception.
753753
"""
754+
755+
756+
class PartialStateConflictError(SynapseError):
757+
"""An internal error raised when attempting to persist an event with partial state
758+
after the room containing the event has been un-partial stated.
759+
760+
This error should be handled by recomputing the event context and trying again.
761+
762+
This error has an HTTP status code so that it can be transported over replication.
763+
It should not be exposed to clients.
764+
"""
765+
766+
@staticmethod
767+
def message() -> str:
768+
return "Cannot persist partial state event in un-partial stated room"
769+
770+
def __init__(self) -> None:
771+
super().__init__(
772+
HTTPStatus.CONFLICT,
773+
msg=PartialStateConflictError.message(),
774+
errcode=Codes.UNKNOWN,
775+
)

synapse/federation/federation_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
FederationError,
4949
IncompatibleRoomVersionError,
5050
NotFoundError,
51+
PartialStateConflictError,
5152
SynapseError,
5253
UnsupportedRoomVersionError,
5354
)
@@ -81,7 +82,6 @@
8182
ReplicationFederationSendEduRestServlet,
8283
ReplicationGetQueryRestServlet,
8384
)
84-
from synapse.storage.databases.main.events import PartialStateConflictError
8585
from synapse.storage.databases.main.lock import Lock
8686
from synapse.storage.databases.main.roommember import extract_heroes_from_room_summary
8787
from synapse.storage.roommember import MemberSummary

synapse/handlers/event_auth.py

Lines changed: 8 additions & 8 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,14 @@ 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. `None` if the
218+
user has never joined the room (equivalent to "leave").
218219
219220
Raises:
220221
AuthError if the user cannot join the room.
221222
"""
222223
# 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-
):
224+
if prev_membership in (Membership.JOIN, Membership.INVITE):
226225
return
227226

228227
# This is not a room with a restricted join rule, so we don't need to do the
@@ -255,13 +254,14 @@ async def check_restricted_join_rules(
255254
)
256255

257256
async def has_restricted_join_rules(
258-
self, state_ids: StateMap[str], room_version: RoomVersion
257+
self, partial_state_ids: StateMap[str], room_version: RoomVersion
259258
) -> bool:
260259
"""
261260
Return if the room has the proper join rules set for access via rooms.
262261
263262
Args:
264-
state_ids: The state of the room as it currently is.
263+
state_ids: The state of the room as it currently is. May be full or partial
264+
state.
265265
room_version: The room version of the room to query.
266266
267267
Returns:
@@ -272,7 +272,7 @@ async def has_restricted_join_rules(
272272
return False
273273

274274
# If there's no join rule, then it defaults to invite (so this doesn't apply).
275-
join_rules_event_id = state_ids.get((EventTypes.JoinRules, ""), None)
275+
join_rules_event_id = partial_state_ids.get((EventTypes.JoinRules, ""), None)
276276
if not join_rules_event_id:
277277
return False
278278

synapse/handlers/federation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
FederationPullAttemptBackoffError,
5050
HttpResponseException,
5151
NotFoundError,
52+
PartialStateConflictError,
5253
RequestSendFailed,
5354
SynapseError,
5455
)
@@ -68,7 +69,6 @@
6869
ReplicationCleanRoomRestServlet,
6970
ReplicationStoreRoomOnOutlierMembershipRestServlet,
7071
)
71-
from synapse.storage.databases.main.events import PartialStateConflictError
7272
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
7373
from synapse.types import JsonDict, StrCollection, get_domain_from_id
7474
from synapse.types.state import StateFilter

synapse/handlers/federation_event.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
FederationError,
4848
FederationPullAttemptBackoffError,
4949
HttpResponseException,
50+
PartialStateConflictError,
5051
RequestSendFailed,
5152
SynapseError,
5253
)
@@ -74,7 +75,6 @@
7475
ReplicationFederationSendEventsRestServlet,
7576
)
7677
from synapse.state import StateResolutionStore
77-
from synapse.storage.databases.main.events import PartialStateConflictError
7878
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
7979
from synapse.types import (
8080
PersistedEventPosition,
@@ -441,16 +441,17 @@ async def check_join_restrictions(
441441
# Check if the user is already in the room or invited to the room.
442442
user_id = event.state_key
443443
prev_member_event_id = prev_state_ids.get((EventTypes.Member, user_id), None)
444-
prev_member_event = None
444+
prev_membership = None
445445
if prev_member_event_id:
446446
prev_member_event = await self._store.get_event(prev_member_event_id)
447+
prev_membership = prev_member_event.membership
447448

448449
# Check if the member should be allowed access via membership in a space.
449450
await self._event_auth_handler.check_restricted_join_rules(
450451
prev_state_ids,
451452
event.room_version,
452453
user_id,
453-
prev_member_event,
454+
prev_membership,
454455
)
455456

456457
@trace
@@ -526,11 +527,57 @@ async def process_remote_join(
526527
"Peristing join-via-remote %s (partial_state: %s)", event, partial_state
527528
)
528529
with nested_logging_context(suffix=event.event_id):
530+
if partial_state:
531+
# When handling a second partial state join into a partial state room,
532+
# the returned state will exclude the membership from the first join. To
533+
# preserve prior memberships, we try to compute the partial state before
534+
# the event ourselves if we know about any of the prev events.
535+
#
536+
# When we don't know about any of the prev events, it's fine to just use
537+
# the returned state, since the new join will create a new forward
538+
# extremity, and leave the forward extremity containing our prior
539+
# memberships alone.
540+
prev_event_ids = set(event.prev_event_ids())
541+
seen_event_ids = await self._store.have_events_in_timeline(
542+
prev_event_ids
543+
)
544+
missing_event_ids = prev_event_ids - seen_event_ids
545+
546+
state_maps_to_resolve: List[StateMap[str]] = []
547+
548+
# Fetch the state after the prev events that we know about.
549+
state_maps_to_resolve.extend(
550+
(
551+
await self._state_storage_controller.get_state_groups_ids(
552+
room_id, seen_event_ids, await_full_state=False
553+
)
554+
).values()
555+
)
556+
557+
# When there are prev events we do not have the state for, we state
558+
# resolve with the state returned by the remote homeserver.
559+
if missing_event_ids or len(state_maps_to_resolve) == 0:
560+
state_maps_to_resolve.append(
561+
{(e.type, e.state_key): e.event_id for e in state}
562+
)
563+
564+
state_ids_before_event = (
565+
await self._state_resolution_handler.resolve_events_with_store(
566+
event.room_id,
567+
room_version.identifier,
568+
state_maps_to_resolve,
569+
event_map=None,
570+
state_res_store=StateResolutionStore(self._store),
571+
)
572+
)
573+
else:
574+
state_ids_before_event = {
575+
(e.type, e.state_key): e.event_id for e in state
576+
}
577+
529578
context = await self._state_handler.compute_event_context(
530579
event,
531-
state_ids_before_event={
532-
(e.type, e.state_key): e.event_id for e in state
533-
},
580+
state_ids_before_event=state_ids_before_event,
534581
partial_state=partial_state,
535582
)
536583

synapse/handlers/message.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
Codes,
3939
ConsentNotGivenError,
4040
NotFoundError,
41+
PartialStateConflictError,
4142
ShadowBanError,
4243
SynapseError,
4344
UnstableSpecAuthError,
@@ -57,7 +58,6 @@
5758
from synapse.metrics.background_process_metrics import run_as_background_process
5859
from synapse.replication.http.send_event import ReplicationSendEventRestServlet
5960
from synapse.replication.http.send_events import ReplicationSendEventsRestServlet
60-
from synapse.storage.databases.main.events import PartialStateConflictError
6161
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
6262
from synapse.types import (
6363
MutableStateMap,

synapse/handlers/room.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
Codes,
4444
LimitExceededError,
4545
NotFoundError,
46+
PartialStateConflictError,
4647
StoreError,
4748
SynapseError,
4849
)
@@ -54,7 +55,6 @@
5455
from synapse.handlers.relations import BundledAggregations
5556
from synapse.module_api import NOT_SPAM
5657
from synapse.rest.admin._base import assert_user_is_admin
57-
from synapse.storage.databases.main.events import PartialStateConflictError
5858
from synapse.streams import EventSource
5959
from synapse.types import (
6060
JsonDict,

0 commit comments

Comments
 (0)