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

Delete stale non-e2e devices for users #14038

Merged
merged 11 commits into from
Nov 29, 2022
12 changes: 11 additions & 1 deletion synapse/handlers/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,8 @@ async def check_device_registered(

self._check_device_name_length(initial_device_display_name)

await self._delete_stale_devices(user_id)

if device_id is not None:
new_device = await self.store.store_device(
user_id=user_id,
Expand Down Expand Up @@ -433,6 +435,14 @@ async def check_device_registered(

raise errors.StoreError(500, "Couldn't generate a device ID.")

async def _delete_stale_devices(self, user_id: str) -> None:
"""Delete any stale devices this user may have."""
erikjohnston marked this conversation as resolved.
Show resolved Hide resolved
device_ids = await self.store.get_stale_devices(user_id)
if not device_ids:
return

await self.delete_devices(user_id, device_ids)

async def _delete_stale_devices(self) -> None:
erikjohnston marked this conversation as resolved.
Show resolved Hide resolved
"""Background task that deletes devices which haven't been accessed for more than
a configured time period.
Expand Down Expand Up @@ -462,7 +472,7 @@ async def delete_all_devices_for_user(
device_ids = [d for d in device_ids if d != except_device_id]
await self.delete_devices(user_id, device_ids)

async def delete_devices(self, user_id: str, device_ids: List[str]) -> None:
async def delete_devices(self, user_id: str, device_ids: Collection[str]) -> None:
"""Delete several devices

Args:
Expand Down
38 changes: 37 additions & 1 deletion synapse/storage/databases/main/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -1466,6 +1466,40 @@ def _txn(txn: LoggingTransaction) -> int:

return rows

async def get_stale_devices(self, user_id: str) -> Collection[str]:
"""Get set of devices that we could delete for this user."""

num_devices = await self.db_pool.simple_select_one_onecol(
table="devices",
keyvalues={"user_id": user_id, "hidden": "false"},
erikjohnston marked this conversation as resolved.
Show resolved Hide resolved
retcol="COALESCE(COUNT(*), 0)",
desc="count_devices",
)

if num_devices > 10:
return ()
erikjohnston marked this conversation as resolved.
Show resolved Hide resolved

max_last_seen = self._clock.time_msec() - 14 * 24 * 60 * 60 * 1000

sql = """
SELECT DISTINCT device_id FROM devices
LEFT JOIN e2e_device_keys_json USING (user_id, device_id)
WHERE
user_id = ?
AND not hidden
AND last_seen < ?
AND key_json IS NULL
"""

def get_stale_devices_txn(txn: LoggingTransaction) -> Collection[str]:
txn.execute(sql, (user_id, max_last_seen))
return {device_id for device_id, in txn}

return await self.db_pool.runInteraction(
"get_stale_devices",
get_stale_devices_txn,
)


class DeviceStore(DeviceWorkerStore, DeviceBackgroundUpdateStore):
def __init__(
Expand Down Expand Up @@ -1517,7 +1551,9 @@ async def store_device(
"user_id": user_id,
"device_id": device_id,
},
values={},
values={
"last_seen": self._clock.time_msec(),
},
insertion_values={
"display_name": initial_device_display_name,
"hidden": False,
Expand Down