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

Commit

Permalink
Fix incorrect get_rooms_for_user for remote user (#11999)
Browse files Browse the repository at this point in the history
When the server leaves a room the `get_rooms_for_user` cache is not
correctly invalidated for the remote users in the room. This means that
subsequent calls to `get_rooms_for_user` for the remote users would
incorrectly include the room (it shouldn't be included because the
server no longer knows anything about the room).
  • Loading branch information
erikjohnston committed Feb 15, 2022
1 parent 5598556 commit dc9fe61
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 11 deletions.
1 change: 1 addition & 0 deletions changelog.d/11999.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix long standing bug where `get_rooms_for_user` was not correctly invalidated for remote users when the server left a room.
27 changes: 16 additions & 11 deletions synapse/storage/databases/main/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,17 @@ def _update_current_state_txn(
to_delete = delta_state.to_delete
to_insert = delta_state.to_insert

# Figure out the changes of membership to invalidate the
# `get_rooms_for_user` cache.
# We find out which membership events we may have deleted
# and which we have added, then we invalidate the caches for all
# those users.
members_changed = {
state_key
for ev_type, state_key in itertools.chain(to_delete, to_insert)
if ev_type == EventTypes.Member
}

if delta_state.no_longer_in_room:
# Server is no longer in the room so we delete the room from
# current_state_events, being careful we've already updated the
Expand All @@ -993,6 +1004,11 @@ def _update_current_state_txn(
"""
txn.execute(sql, (stream_id, self._instance_name, room_id))

# We also want to invalidate the membership caches for users
# that were in the room.
users_in_room = self.store.get_users_in_room_txn(txn, room_id)
members_changed.update(users_in_room)

self.db_pool.simple_delete_txn(
txn,
table="current_state_events",
Expand Down Expand Up @@ -1102,17 +1118,6 @@ def _update_current_state_txn(

# Invalidate the various caches

# Figure out the changes of membership to invalidate the
# `get_rooms_for_user` cache.
# We find out which membership events we may have deleted
# and which we have added, then we invalidate the caches for all
# those users.
members_changed = {
state_key
for ev_type, state_key in itertools.chain(to_delete, to_insert)
if ev_type == EventTypes.Member
}

for member in members_changed:
txn.call_after(
self.store.get_rooms_for_user_with_stream_ordering.invalidate,
Expand Down
107 changes: 107 additions & 0 deletions tests/storage/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,110 @@ def test_do_not_prune_gap_if_not_dummy(self):

# Check the new extremity is just the new remote event.
self.assert_extremities([local_message_event_id, remote_event_2.event_id])


class InvalideUsersInRoomCacheTestCase(HomeserverTestCase):
servlets = [
admin.register_servlets,
room.register_servlets,
login.register_servlets,
]

def prepare(self, reactor, clock, homeserver):
self.state = self.hs.get_state_handler()
self.persistence = self.hs.get_storage().persistence
self.store = self.hs.get_datastore()

def test_remote_user_rooms_cache_invalidated(self):
"""Test that if the server leaves a room the `get_rooms_for_user` cache
is invalidated for remote users.
"""

# Set up a room with a local and remote user in it.
user_id = self.register_user("user", "pass")
token = self.login("user", "pass")

room_id = self.helper.create_room_as(
"user", room_version=RoomVersions.V6.identifier, tok=token
)

body = self.helper.send(room_id, body="Test", tok=token)
local_message_event_id = body["event_id"]

# Fudge a join event for a remote user.
remote_user = "@user:other"
remote_event_1 = event_from_pdu_json(
{
"type": EventTypes.Member,
"state_key": remote_user,
"content": {"membership": Membership.JOIN},
"room_id": room_id,
"sender": remote_user,
"depth": 5,
"prev_events": [local_message_event_id],
"auth_events": [],
"origin_server_ts": self.clock.time_msec(),
},
RoomVersions.V6,
)

context = self.get_success(self.state.compute_event_context(remote_event_1))
self.get_success(self.persistence.persist_event(remote_event_1, context))

# Call `get_rooms_for_user` to add the remote user to the cache
rooms = self.get_success(self.store.get_rooms_for_user(remote_user))
self.assertEqual(set(rooms), {room_id})

# Now we have the local server leave the room, and check that calling
# `get_user_in_room` for the remote user no longer includes the room.
self.helper.leave(room_id, user_id, tok=token)

rooms = self.get_success(self.store.get_rooms_for_user(remote_user))
self.assertEqual(set(rooms), set())

def test_room_remote_user_cache_invalidated(self):
"""Test that if the server leaves a room the `get_users_in_room` cache
is invalidated for remote users.
"""

# Set up a room with a local and remote user in it.
user_id = self.register_user("user", "pass")
token = self.login("user", "pass")

room_id = self.helper.create_room_as(
"user", room_version=RoomVersions.V6.identifier, tok=token
)

body = self.helper.send(room_id, body="Test", tok=token)
local_message_event_id = body["event_id"]

# Fudge a join event for a remote user.
remote_user = "@user:other"
remote_event_1 = event_from_pdu_json(
{
"type": EventTypes.Member,
"state_key": remote_user,
"content": {"membership": Membership.JOIN},
"room_id": room_id,
"sender": remote_user,
"depth": 5,
"prev_events": [local_message_event_id],
"auth_events": [],
"origin_server_ts": self.clock.time_msec(),
},
RoomVersions.V6,
)

context = self.get_success(self.state.compute_event_context(remote_event_1))
self.get_success(self.persistence.persist_event(remote_event_1, context))

# Call `get_users_in_room` to add the remote user to the cache
users = self.get_success(self.store.get_users_in_room(room_id))
self.assertEqual(set(users), {user_id, remote_user})

# Now we have the local server leave the room, and check that calling
# `get_user_in_room` for the remote user no longer includes the room.
self.helper.leave(room_id, user_id, tok=token)

users = self.get_success(self.store.get_users_in_room(room_id))
self.assertEqual(users, [])

0 comments on commit dc9fe61

Please sign in to comment.