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

Commit dc9fe61

Browse files
authored
Fix incorrect get_rooms_for_user for remote user (#11999)
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).
1 parent 5598556 commit dc9fe61

File tree

3 files changed

+124
-11
lines changed

3 files changed

+124
-11
lines changed

changelog.d/11999.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix long standing bug where `get_rooms_for_user` was not correctly invalidated for remote users when the server left a room.

synapse/storage/databases/main/events.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,17 @@ def _update_current_state_txn(
975975
to_delete = delta_state.to_delete
976976
to_insert = delta_state.to_insert
977977

978+
# Figure out the changes of membership to invalidate the
979+
# `get_rooms_for_user` cache.
980+
# We find out which membership events we may have deleted
981+
# and which we have added, then we invalidate the caches for all
982+
# those users.
983+
members_changed = {
984+
state_key
985+
for ev_type, state_key in itertools.chain(to_delete, to_insert)
986+
if ev_type == EventTypes.Member
987+
}
988+
978989
if delta_state.no_longer_in_room:
979990
# Server is no longer in the room so we delete the room from
980991
# current_state_events, being careful we've already updated the
@@ -993,6 +1004,11 @@ def _update_current_state_txn(
9931004
"""
9941005
txn.execute(sql, (stream_id, self._instance_name, room_id))
9951006

1007+
# We also want to invalidate the membership caches for users
1008+
# that were in the room.
1009+
users_in_room = self.store.get_users_in_room_txn(txn, room_id)
1010+
members_changed.update(users_in_room)
1011+
9961012
self.db_pool.simple_delete_txn(
9971013
txn,
9981014
table="current_state_events",
@@ -1102,17 +1118,6 @@ def _update_current_state_txn(
11021118

11031119
# Invalidate the various caches
11041120

1105-
# Figure out the changes of membership to invalidate the
1106-
# `get_rooms_for_user` cache.
1107-
# We find out which membership events we may have deleted
1108-
# and which we have added, then we invalidate the caches for all
1109-
# those users.
1110-
members_changed = {
1111-
state_key
1112-
for ev_type, state_key in itertools.chain(to_delete, to_insert)
1113-
if ev_type == EventTypes.Member
1114-
}
1115-
11161121
for member in members_changed:
11171122
txn.call_after(
11181123
self.store.get_rooms_for_user_with_stream_ordering.invalidate,

tests/storage/test_events.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,110 @@ def test_do_not_prune_gap_if_not_dummy(self):
329329

330330
# Check the new extremity is just the new remote event.
331331
self.assert_extremities([local_message_event_id, remote_event_2.event_id])
332+
333+
334+
class InvalideUsersInRoomCacheTestCase(HomeserverTestCase):
335+
servlets = [
336+
admin.register_servlets,
337+
room.register_servlets,
338+
login.register_servlets,
339+
]
340+
341+
def prepare(self, reactor, clock, homeserver):
342+
self.state = self.hs.get_state_handler()
343+
self.persistence = self.hs.get_storage().persistence
344+
self.store = self.hs.get_datastore()
345+
346+
def test_remote_user_rooms_cache_invalidated(self):
347+
"""Test that if the server leaves a room the `get_rooms_for_user` cache
348+
is invalidated for remote users.
349+
"""
350+
351+
# Set up a room with a local and remote user in it.
352+
user_id = self.register_user("user", "pass")
353+
token = self.login("user", "pass")
354+
355+
room_id = self.helper.create_room_as(
356+
"user", room_version=RoomVersions.V6.identifier, tok=token
357+
)
358+
359+
body = self.helper.send(room_id, body="Test", tok=token)
360+
local_message_event_id = body["event_id"]
361+
362+
# Fudge a join event for a remote user.
363+
remote_user = "@user:other"
364+
remote_event_1 = event_from_pdu_json(
365+
{
366+
"type": EventTypes.Member,
367+
"state_key": remote_user,
368+
"content": {"membership": Membership.JOIN},
369+
"room_id": room_id,
370+
"sender": remote_user,
371+
"depth": 5,
372+
"prev_events": [local_message_event_id],
373+
"auth_events": [],
374+
"origin_server_ts": self.clock.time_msec(),
375+
},
376+
RoomVersions.V6,
377+
)
378+
379+
context = self.get_success(self.state.compute_event_context(remote_event_1))
380+
self.get_success(self.persistence.persist_event(remote_event_1, context))
381+
382+
# Call `get_rooms_for_user` to add the remote user to the cache
383+
rooms = self.get_success(self.store.get_rooms_for_user(remote_user))
384+
self.assertEqual(set(rooms), {room_id})
385+
386+
# Now we have the local server leave the room, and check that calling
387+
# `get_user_in_room` for the remote user no longer includes the room.
388+
self.helper.leave(room_id, user_id, tok=token)
389+
390+
rooms = self.get_success(self.store.get_rooms_for_user(remote_user))
391+
self.assertEqual(set(rooms), set())
392+
393+
def test_room_remote_user_cache_invalidated(self):
394+
"""Test that if the server leaves a room the `get_users_in_room` cache
395+
is invalidated for remote users.
396+
"""
397+
398+
# Set up a room with a local and remote user in it.
399+
user_id = self.register_user("user", "pass")
400+
token = self.login("user", "pass")
401+
402+
room_id = self.helper.create_room_as(
403+
"user", room_version=RoomVersions.V6.identifier, tok=token
404+
)
405+
406+
body = self.helper.send(room_id, body="Test", tok=token)
407+
local_message_event_id = body["event_id"]
408+
409+
# Fudge a join event for a remote user.
410+
remote_user = "@user:other"
411+
remote_event_1 = event_from_pdu_json(
412+
{
413+
"type": EventTypes.Member,
414+
"state_key": remote_user,
415+
"content": {"membership": Membership.JOIN},
416+
"room_id": room_id,
417+
"sender": remote_user,
418+
"depth": 5,
419+
"prev_events": [local_message_event_id],
420+
"auth_events": [],
421+
"origin_server_ts": self.clock.time_msec(),
422+
},
423+
RoomVersions.V6,
424+
)
425+
426+
context = self.get_success(self.state.compute_event_context(remote_event_1))
427+
self.get_success(self.persistence.persist_event(remote_event_1, context))
428+
429+
# Call `get_users_in_room` to add the remote user to the cache
430+
users = self.get_success(self.store.get_users_in_room(room_id))
431+
self.assertEqual(set(users), {user_id, remote_user})
432+
433+
# Now we have the local server leave the room, and check that calling
434+
# `get_user_in_room` for the remote user no longer includes the room.
435+
self.helper.leave(room_id, user_id, tok=token)
436+
437+
users = self.get_success(self.store.get_users_in_room(room_id))
438+
self.assertEqual(users, [])

0 commit comments

Comments
 (0)