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

Commit 5f659d4

Browse files
authoredSep 28, 2022
Handle local device list updates during partial join (#13934)
1 parent df8b91e commit 5f659d4

File tree

4 files changed

+141
-15
lines changed

4 files changed

+141
-15
lines changed
 

‎changelog.d/13934.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Correctly handle sending local device list updates to remote servers during a partial join.

‎synapse/handlers/device.py

+82-2
Original file line numberDiff line numberDiff line change
@@ -762,10 +762,90 @@ async def handle_room_un_partial_stated(self, room_id: str) -> None:
762762
gone from partial to full state.
763763
"""
764764

765-
# We defer to the device list updater implementation as we're on the
766-
# right worker.
765+
# We defer to the device list updater to handle pending remote device
766+
# list updates.
767767
await self.device_list_updater.handle_room_un_partial_stated(room_id)
768768

769+
# Replay local updates.
770+
(
771+
join_event_id,
772+
device_lists_stream_id,
773+
) = await self.store.get_join_event_id_and_device_lists_stream_id_for_partial_state(
774+
room_id
775+
)
776+
777+
# Get the local device list changes that have happened in the room since
778+
# we started joining. If there are no updates there's nothing left to do.
779+
changes = await self.store.get_device_list_changes_in_room(
780+
room_id, device_lists_stream_id
781+
)
782+
local_changes = {(u, d) for u, d in changes if self.hs.is_mine_id(u)}
783+
if not local_changes:
784+
return
785+
786+
# Note: We have persisted the full state at this point, we just haven't
787+
# cleared the `partial_room` flag.
788+
join_state_ids = await self._state_storage.get_state_ids_for_event(
789+
join_event_id, await_full_state=False
790+
)
791+
current_state_ids = await self.store.get_partial_current_state_ids(room_id)
792+
793+
# Now we need to work out all servers that might have been in the room
794+
# at any point during our join.
795+
796+
# First we look for any membership states that have changed between the
797+
# initial join and now...
798+
all_keys = set(join_state_ids)
799+
all_keys.update(current_state_ids)
800+
801+
potentially_changed_hosts = set()
802+
for etype, state_key in all_keys:
803+
if etype != EventTypes.Member:
804+
continue
805+
806+
prev = join_state_ids.get((etype, state_key))
807+
current = current_state_ids.get((etype, state_key))
808+
809+
if prev != current:
810+
potentially_changed_hosts.add(get_domain_from_id(state_key))
811+
812+
# ... then we add all the hosts that are currently joined to the room...
813+
current_hosts_in_room = await self.store.get_current_hosts_in_room(room_id)
814+
potentially_changed_hosts.update(current_hosts_in_room)
815+
816+
# ... and finally we remove any hosts that we were told about, as we
817+
# will have sent device list updates to those hosts when they happened.
818+
known_hosts_at_join = await self.store.get_partial_state_servers_at_join(
819+
room_id
820+
)
821+
potentially_changed_hosts.difference_update(known_hosts_at_join)
822+
823+
potentially_changed_hosts.discard(self.server_name)
824+
825+
if not potentially_changed_hosts:
826+
# Nothing to do.
827+
return
828+
829+
logger.info(
830+
"Found %d changed hosts to send device list updates to",
831+
len(potentially_changed_hosts),
832+
)
833+
834+
for user_id, device_id in local_changes:
835+
await self.store.add_device_list_outbound_pokes(
836+
user_id=user_id,
837+
device_id=device_id,
838+
room_id=room_id,
839+
stream_id=None,
840+
hosts=potentially_changed_hosts,
841+
context=None,
842+
)
843+
844+
# Notify things that device lists need to be sent out.
845+
self.notifier.notify_replication()
846+
for host in potentially_changed_hosts:
847+
self.federation_sender.send_device_messages(host, immediate=False)
848+
769849

770850
def _update_device_from_client_ips(
771851
device: JsonDict, client_ips: Mapping[Tuple[str, str], Mapping[str, Any]]

‎synapse/storage/databases/main/devices.py

+42-13
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,33 @@ def _get_device_list_changes_in_rooms_txn(
13071307

13081308
return changes
13091309

1310+
async def get_device_list_changes_in_room(
1311+
self, room_id: str, min_stream_id: int
1312+
) -> Collection[Tuple[str, str]]:
1313+
"""Get all device list changes that happened in the room since the given
1314+
stream ID.
1315+
1316+
Returns:
1317+
Collection of user ID/device ID tuples of all devices that have
1318+
changed
1319+
"""
1320+
1321+
sql = """
1322+
SELECT DISTINCT user_id, device_id FROM device_lists_changes_in_room
1323+
WHERE room_id = ? AND stream_id > ?
1324+
"""
1325+
1326+
def get_device_list_changes_in_room_txn(
1327+
txn: LoggingTransaction,
1328+
) -> Collection[Tuple[str, str]]:
1329+
txn.execute(sql, (room_id, min_stream_id))
1330+
return cast(Collection[Tuple[str, str]], txn.fetchall())
1331+
1332+
return await self.db_pool.runInteraction(
1333+
"get_device_list_changes_in_room",
1334+
get_device_list_changes_in_room_txn,
1335+
)
1336+
13101337

13111338
class DeviceBackgroundUpdateStore(SQLBaseStore):
13121339
def __init__(
@@ -1946,14 +1973,15 @@ async def add_device_list_outbound_pokes(
19461973
user_id: str,
19471974
device_id: str,
19481975
room_id: str,
1949-
stream_id: int,
1976+
stream_id: Optional[int],
19501977
hosts: Collection[str],
19511978
context: Optional[Dict[str, str]],
19521979
) -> None:
19531980
"""Queue the device update to be sent to the given set of hosts,
19541981
calculated from the room ID.
19551982
1956-
Marks the associated row in `device_lists_changes_in_room` as handled.
1983+
Marks the associated row in `device_lists_changes_in_room` as handled,
1984+
if `stream_id` is provided.
19571985
"""
19581986

19591987
def add_device_list_outbound_pokes_txn(
@@ -1969,17 +1997,18 @@ def add_device_list_outbound_pokes_txn(
19691997
context=context,
19701998
)
19711999

1972-
self.db_pool.simple_update_txn(
1973-
txn,
1974-
table="device_lists_changes_in_room",
1975-
keyvalues={
1976-
"user_id": user_id,
1977-
"device_id": device_id,
1978-
"stream_id": stream_id,
1979-
"room_id": room_id,
1980-
},
1981-
updatevalues={"converted_to_destinations": True},
1982-
)
2000+
if stream_id:
2001+
self.db_pool.simple_update_txn(
2002+
txn,
2003+
table="device_lists_changes_in_room",
2004+
keyvalues={
2005+
"user_id": user_id,
2006+
"device_id": device_id,
2007+
"stream_id": stream_id,
2008+
"room_id": room_id,
2009+
},
2010+
updatevalues={"converted_to_destinations": True},
2011+
)
19832012

19842013
if not hosts:
19852014
# If there are no hosts then we don't try and generate stream IDs.

‎synapse/storage/databases/main/room.py

+16
Original file line numberDiff line numberDiff line change
@@ -1256,6 +1256,22 @@ async def is_partial_state_room(self, room_id: str) -> bool:
12561256

12571257
return entry is not None
12581258

1259+
async def get_join_event_id_and_device_lists_stream_id_for_partial_state(
1260+
self, room_id: str
1261+
) -> Tuple[str, int]:
1262+
"""Get the event ID of the initial join that started the partial
1263+
join, and the device list stream ID at the point we started the partial
1264+
join.
1265+
"""
1266+
1267+
result = await self.db_pool.simple_select_one(
1268+
table="partial_state_rooms",
1269+
keyvalues={"room_id": room_id},
1270+
retcols=("join_event_id", "device_lists_stream_id"),
1271+
desc="get_join_event_id_for_partial_state",
1272+
)
1273+
return result["join_event_id"], result["device_lists_stream_id"]
1274+
12591275

12601276
class _BackgroundUpdates:
12611277
REMOVE_TOMESTONED_ROOMS_BG_UPDATE = "remove_tombstoned_rooms_from_directory"

0 commit comments

Comments
 (0)
This repository has been archived.