Skip to content

Commit 44bee74

Browse files
erikjohnstonH-Shay
authored andcommitted
Fix deduplicating of membership events to not create unused state groups. (element-hq#17164)
We try and deduplicate in two places: 1) really early on, and 2) just before we persist the event. The first case was broken due to it occuring before the profile information was added, and so it thought the event contents were different. The second case did catch it and handle it correctly, however doing so creates a redundant state group leading to bloat. Fixes element-hq#3791
1 parent 641b6b2 commit 44bee74

File tree

4 files changed

+54
-35
lines changed

4 files changed

+54
-35
lines changed

changelog.d/17164.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix deduplicating of membership events to not create unused state groups.

synapse/handlers/message.py

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -496,13 +496,6 @@ def __init__(self, hs: "HomeServer"):
496496

497497
self.room_prejoin_state_types = self.hs.config.api.room_prejoin_state
498498

499-
self.membership_types_to_include_profile_data_in = {
500-
Membership.JOIN,
501-
Membership.KNOCK,
502-
}
503-
if self.hs.config.server.include_profile_data_on_invite:
504-
self.membership_types_to_include_profile_data_in.add(Membership.INVITE)
505-
506499
self.send_event = ReplicationSendEventRestServlet.make_client(hs)
507500
self.send_events = ReplicationSendEventsRestServlet.make_client(hs)
508501

@@ -594,8 +587,6 @@ async def create_event(
594587
Creates an FrozenEvent object, filling out auth_events, prev_events,
595588
etc.
596589
597-
Adds display names to Join membership events.
598-
599590
Args:
600591
requester
601592
event_dict: An entire event
@@ -683,29 +674,6 @@ async def create_event(
683674

684675
self.validator.validate_builder(builder)
685676

686-
if builder.type == EventTypes.Member:
687-
membership = builder.content.get("membership", None)
688-
target = UserID.from_string(builder.state_key)
689-
690-
if membership in self.membership_types_to_include_profile_data_in:
691-
# If event doesn't include a display name, add one.
692-
profile = self.profile_handler
693-
content = builder.content
694-
695-
try:
696-
if "displayname" not in content:
697-
displayname = await profile.get_displayname(target)
698-
if displayname is not None:
699-
content["displayname"] = displayname
700-
if "avatar_url" not in content:
701-
avatar_url = await profile.get_avatar_url(target)
702-
if avatar_url is not None:
703-
content["avatar_url"] = avatar_url
704-
except Exception as e:
705-
logger.info(
706-
"Failed to get profile information for %r: %s", target, e
707-
)
708-
709677
is_exempt = await self._is_exempt_from_privacy_policy(builder, requester)
710678
if require_consent and not is_exempt:
711679
await self.assert_accepted_privacy_policy(requester)

synapse/handlers/room_member.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ def __init__(self, hs: "HomeServer"):
106106
self.event_auth_handler = hs.get_event_auth_handler()
107107
self._worker_lock_handler = hs.get_worker_locks_handler()
108108

109+
self._membership_types_to_include_profile_data_in = {
110+
Membership.JOIN,
111+
Membership.KNOCK,
112+
}
113+
if self.hs.config.server.include_profile_data_on_invite:
114+
self._membership_types_to_include_profile_data_in.add(Membership.INVITE)
115+
109116
self.member_linearizer: Linearizer = Linearizer(name="member")
110117
self.member_as_limiter = Linearizer(max_count=10, name="member_as_limiter")
111118

@@ -785,9 +792,8 @@ async def update_membership_locked(
785792
if (
786793
not self.allow_per_room_profiles and not is_requester_server_notices_user
787794
) or requester.shadow_banned:
788-
# Strip profile data, knowing that new profile data will be added to the
789-
# event's content in event_creation_handler.create_event() using the target's
790-
# global profile.
795+
# Strip profile data, knowing that new profile data will be added to
796+
# the event's content below using the target's global profile.
791797
content.pop("displayname", None)
792798
content.pop("avatar_url", None)
793799

@@ -823,6 +829,29 @@ async def update_membership_locked(
823829
if action in ["kick", "unban"]:
824830
effective_membership_state = "leave"
825831

832+
if effective_membership_state not in Membership.LIST:
833+
raise SynapseError(400, "Invalid membership key")
834+
835+
# Add profile data for joins etc, if no per-room profile.
836+
if (
837+
effective_membership_state
838+
in self._membership_types_to_include_profile_data_in
839+
):
840+
# If event doesn't include a display name, add one.
841+
profile = self.profile_handler
842+
843+
try:
844+
if "displayname" not in content:
845+
displayname = await profile.get_displayname(target)
846+
if displayname is not None:
847+
content["displayname"] = displayname
848+
if "avatar_url" not in content:
849+
avatar_url = await profile.get_avatar_url(target)
850+
if avatar_url is not None:
851+
content["avatar_url"] = avatar_url
852+
except Exception as e:
853+
logger.info("Failed to get profile information for %r: %s", target, e)
854+
826855
# if this is a join with a 3pid signature, we may need to turn a 3pid
827856
# invite into a normal invite before we can handle the join.
828857
if third_party_signed is not None:

tests/handlers/test_room_member.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,24 @@ def test_rejoin_forgotten_by_user(self) -> None:
407407
self.assertFalse(
408408
self.get_success(self.store.did_forget(self.alice, self.room_id))
409409
)
410+
411+
def test_deduplicate_joins(self) -> None:
412+
"""
413+
Test that calling /join multiple times does not store a new state group.
414+
"""
415+
416+
self.helper.join(self.room_id, user=self.bob, tok=self.bob_token)
417+
418+
sql = "SELECT COUNT(*) FROM state_groups WHERE room_id = ?"
419+
rows = self.get_success(
420+
self.store.db_pool.execute("test_deduplicate_joins", sql, self.room_id)
421+
)
422+
initial_count = rows[0][0]
423+
424+
self.helper.join(self.room_id, user=self.bob, tok=self.bob_token)
425+
rows = self.get_success(
426+
self.store.db_pool.execute("test_deduplicate_joins", sql, self.room_id)
427+
)
428+
new_count = rows[0][0]
429+
430+
self.assertEqual(initial_count, new_count)

0 commit comments

Comments
 (0)