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

Commit

Permalink
Fix race which caused deleted devices to reappear (#6514)
Browse files Browse the repository at this point in the history
Stop the `update_client_ips` background job from recreating deleted devices.
  • Loading branch information
richvdh authored Dec 10, 2019
1 parent c3dda28 commit 40eda84
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 23 deletions.
1 change: 1 addition & 0 deletions changelog.d/6514.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix race which occasionally caused deleted devices to reappear.
8 changes: 5 additions & 3 deletions synapse/storage/data_stores/main/client_ips.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,16 +451,18 @@ def _update_client_ips_batch_txn(self, txn, to_update):
# Technically an access token might not be associated with
# a device so we need to check.
if device_id:
self.db.simple_upsert_txn(
# this is always an update rather than an upsert: the row should
# already exist, and if it doesn't, that may be because it has been
# deleted, and we don't want to re-create it.
self.db.simple_update_txn(
txn,
table="devices",
keyvalues={"user_id": user_id, "device_id": device_id},
values={
updatevalues={
"user_agent": user_agent,
"last_seen": last_seen,
"ip": ip,
},
lock=False,
)
except Exception as e:
# Failed to upsert, log and continue
Expand Down
49 changes: 29 additions & 20 deletions tests/storage/test_client_ips.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,28 @@ def test_insert_new_client_ip(self):
self.reactor.advance(12345678)

user_id = "@user:id"
device_id = "MY_DEVICE"

# Insert a user IP
self.get_success(self.store.store_device(user_id, device_id, "display name",))
self.get_success(
self.store.insert_client_ip(
user_id, "access_token", "ip", "user_agent", "device_id"
user_id, "access_token", "ip", "user_agent", device_id
)
)

# Trigger the storage loop
self.reactor.advance(10)

result = self.get_success(
self.store.get_last_client_ip_by_device(user_id, "device_id")
self.store.get_last_client_ip_by_device(user_id, device_id)
)

r = result[(user_id, "device_id")]
r = result[(user_id, device_id)]
self.assertDictContainsSubset(
{
"user_id": user_id,
"device_id": "device_id",
"device_id": device_id,
"ip": "ip",
"user_agent": "user_agent",
"last_seen": 12345678000,
Expand Down Expand Up @@ -209,37 +213,39 @@ def test_devices_last_seen_bg_update(self):
self.store.db.updates.do_next_background_update(100), by=0.1
)

# Insert a user IP
user_id = "@user:id"
device_id = "MY_DEVICE"

# Insert a user IP
self.get_success(self.store.store_device(user_id, device_id, "display name",))
self.get_success(
self.store.insert_client_ip(
user_id, "access_token", "ip", "user_agent", "device_id"
user_id, "access_token", "ip", "user_agent", device_id
)
)

# Force persisting to disk
self.reactor.advance(200)

# But clear the associated entry in devices table
self.get_success(
self.store.db.simple_update(
table="devices",
keyvalues={"user_id": user_id, "device_id": "device_id"},
keyvalues={"user_id": user_id, "device_id": device_id},
updatevalues={"last_seen": None, "ip": None, "user_agent": None},
desc="test_devices_last_seen_bg_update",
)
)

# We should now get nulls when querying
result = self.get_success(
self.store.get_last_client_ip_by_device(user_id, "device_id")
self.store.get_last_client_ip_by_device(user_id, device_id)
)

r = result[(user_id, "device_id")]
r = result[(user_id, device_id)]
self.assertDictContainsSubset(
{
"user_id": user_id,
"device_id": "device_id",
"device_id": device_id,
"ip": None,
"user_agent": None,
"last_seen": None,
Expand Down Expand Up @@ -272,14 +278,14 @@ def test_devices_last_seen_bg_update(self):

# We should now get the correct result again
result = self.get_success(
self.store.get_last_client_ip_by_device(user_id, "device_id")
self.store.get_last_client_ip_by_device(user_id, device_id)
)

r = result[(user_id, "device_id")]
r = result[(user_id, device_id)]
self.assertDictContainsSubset(
{
"user_id": user_id,
"device_id": "device_id",
"device_id": device_id,
"ip": "ip",
"user_agent": "user_agent",
"last_seen": 0,
Expand All @@ -296,11 +302,14 @@ def test_old_user_ips_pruned(self):
self.store.db.updates.do_next_background_update(100), by=0.1
)

# Insert a user IP
user_id = "@user:id"
device_id = "MY_DEVICE"

# Insert a user IP
self.get_success(self.store.store_device(user_id, device_id, "display name",))
self.get_success(
self.store.insert_client_ip(
user_id, "access_token", "ip", "user_agent", "device_id"
user_id, "access_token", "ip", "user_agent", device_id
)
)

Expand All @@ -324,7 +333,7 @@ def test_old_user_ips_pruned(self):
"access_token": "access_token",
"ip": "ip",
"user_agent": "user_agent",
"device_id": "device_id",
"device_id": device_id,
"last_seen": 0,
}
],
Expand All @@ -347,14 +356,14 @@ def test_old_user_ips_pruned(self):

# But we should still get the correct values for the device
result = self.get_success(
self.store.get_last_client_ip_by_device(user_id, "device_id")
self.store.get_last_client_ip_by_device(user_id, device_id)
)

r = result[(user_id, "device_id")]
r = result[(user_id, device_id)]
self.assertDictContainsSubset(
{
"user_id": user_id,
"device_id": "device_id",
"device_id": device_id,
"ip": "ip",
"user_agent": "user_agent",
"last_seen": 0,
Expand Down

0 comments on commit 40eda84

Please sign in to comment.