Skip to content
Open
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
3 changes: 3 additions & 0 deletions changelog.d/19898.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix third-party (3pid) invites over federation failing intermittently in version 12 rooms, whose room IDs no longer encode a server name.

Fix `/createRoom` intermittently failing with a 500 error in version 12 rooms when the same user creates several rooms at once, due to colliding room IDs.
9 changes: 8 additions & 1 deletion synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1539,7 +1539,14 @@ async def exchange_third_party_invite(
if i == max_retries - 1:
raise e
else:
destinations = {x.split(":", 1)[-1] for x in (sender_user_id, room_id)}
# The sender always tells us a server to try. Pre-v12 room IDs also
# encode the resident server's domain, but v12+ room IDs are a hash
# with no domain component, so we must not treat them as a server
# name -- doing so raises an invalid-destination error which can
# abort the whole exchange before the valid destination is tried.
destinations = {get_domain_from_id(sender_user_id)}
if ":" in room_id:
destinations.add(get_domain_from_id(room_id))

try:
await self.federation_client.forward_third_party_invite(
Expand Down
66 changes: 46 additions & 20 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -1409,30 +1409,56 @@ async def _generate_create_event_for_room_id(
is_public: bool,
room_version: RoomVersion,
) -> tuple[EventBase, synapse.events.snapshot.EventContext]:
(
creation_event,
new_unpersisted_context,
) = await self.event_creation_handler.create_event(
creator,
{
# In MSC4291 rooms the room ID is the reference hash of the create event,
# so two rooms whose create events have identical content collide on the
# same room ID. This happens in practice when the same user creates
# several rooms at once (e.g. concurrent /createRoom requests with the
# same config), as the only entropy in the create event is
# `origin_server_ts`, which has millisecond resolution. Retry a few times
# on collision, perturbing `origin_server_ts` so the create event hashes
# to a fresh room ID. This mirrors the collision handling in
# `_generate_and_create_room_id` used for older room versions.
attempts = 0
while attempts < 5:
event_dict: JsonDict = {
"content": creation_content,
"sender": creator.user.to_string(),
"type": EventTypes.Create,
"state_key": "",
},
prev_event_ids=[],
depth=1,
state_map={},
for_batch=False,
)
await self.store.store_room(
room_id=creation_event.room_id,
room_creator_user_id=creator.user.to_string(),
is_public=is_public,
room_version=room_version,
)
creation_context = await new_unpersisted_context.persist(creation_event)
return (creation_event, creation_context)
}
if attempts > 0:
# Bump the timestamp to give the create event (and hence the
# room ID it hashes to) different content from the colliding one.
event_dict["origin_server_ts"] = (
self.clock.time_msec() + random.randint(1, 10)
)

(
creation_event,
new_unpersisted_context,
) = await self.event_creation_handler.create_event(
creator,
event_dict,
prev_event_ids=[],
depth=1,
state_map={},
for_batch=False,
)
try:
await self.store.store_room(
room_id=creation_event.room_id,
room_creator_user_id=creator.user.to_string(),
is_public=is_public,
room_version=room_version,
)
except StoreError:
attempts += 1
continue

creation_context = await new_unpersisted_context.persist(creation_event)
return (creation_event, creation_context)

raise StoreError(500, "Couldn't generate a unique room ID.")

async def _send_events_for_new_room(
self,
Expand Down
Loading