def _get_rooms_for_user_with_stream_ordering_txn( self, txn, user_id: str) -> FrozenSet[GetRoomsForUserWithStreamOrdering]: # We use `current_state_events` here and not `local_current_membership` # as a) this gets called with remote users and b) this only gets called # for rooms the server is participating in. if self._current_state_events_membership_up_to_date: sql = """ SELECT room_id, e.instance_name, e.stream_ordering FROM current_state_events AS c INNER JOIN events AS e USING (room_id, event_id) WHERE c.type = 'm.room.member' AND state_key = ? AND c.membership = ? """ else: sql = """ SELECT room_id, e.instance_name, e.stream_ordering FROM current_state_events AS c INNER JOIN room_memberships AS m USING (room_id, event_id) INNER JOIN events AS e USING (room_id, event_id) WHERE c.type = 'm.room.member' AND state_key = ? AND m.membership = ? """ txn.execute(sql, (user_id, Membership.JOIN)) return frozenset( GetRoomsForUserWithStreamOrdering( room_id, PersistedEventPosition(instance, stream_id)) for room_id, instance, stream_id in txn)
def get_rooms_for_user_with_stream_ordering(self, user_id): """Returns a set of room_ids the user is currently joined to Args: user_id (str) Returns: Deferred[frozenset[GetRoomsForUserWithStreamOrdering]]: Returns the rooms the user is in currently, along with the stream ordering of the most recent join for that user and room. """ rooms = yield self.get_rooms_for_user_where_membership_is( user_id, membership_list=[Membership.JOIN]) return frozenset( GetRoomsForUserWithStreamOrdering(r.room_id, r.stream_ordering) for r in rooms)
def test_get_rooms_for_user_with_stream_ordering(self): """Check that the cache on get_rooms_for_user_with_stream_ordering is invalidated by rows in the events stream """ self.persist(type="m.room.create", key="", creator=USER_ID) self.persist(type="m.room.member", key=USER_ID, membership="join") self.replicate() self.check("get_rooms_for_user_with_stream_ordering", (USER_ID_2,), set()) j2 = self.persist( type="m.room.member", sender=USER_ID_2, key=USER_ID_2, membership="join" ) self.replicate() expected_pos = PersistedEventPosition( "master", j2.internal_metadata.stream_ordering ) self.check( "get_rooms_for_user_with_stream_ordering", (USER_ID_2,), {GetRoomsForUserWithStreamOrdering(ROOM_ID, expected_pos)}, )
def test_get_rooms_for_user_with_stream_ordering_with_multi_event_persist(self): """Check that current_state invalidation happens correctly with multiple events in the persistence batch. This test attempts to reproduce a race condition between the event persistence loop and a worker-based Sync handler. The problem occurred when the master persisted several events in one batch. It only updates the current_state at the end of each batch, so the obvious thing to do is then to issue a current_state_delta stream update corresponding to the last stream_id in the batch. However, that raises the possibility that a worker will see the replication notification for a join event before the current_state caches are invalidated. The test involves: * creating a join and a message event for a user, and persisting them in the same batch * controlling the replication stream so that updates are sent gradually * between each bunch of replication updates, check that we see a consistent snapshot of the state. """ self.persist(type="m.room.create", key="", creator=USER_ID) self.persist(type="m.room.member", key=USER_ID, membership="join") self.replicate() self.check("get_rooms_for_user_with_stream_ordering", (USER_ID_2,), set()) # limit the replication rate repl_transport = self._server_transport assert isinstance(repl_transport, FakeTransport) repl_transport.autoflush = False # build the join and message events and persist them in the same batch. logger.info("----- build test events ------") j2, j2ctx = self.build_event( type="m.room.member", sender=USER_ID_2, key=USER_ID_2, membership="join" ) msg, msgctx = self.build_event() self.get_success( self._storage_controllers.persistence.persist_events( [(j2, j2ctx), (msg, msgctx)] ) ) self.replicate() event_source = RoomEventSource(self.hs) event_source.store = self.slaved_store current_token = event_source.get_current_key() # gradually stream out the replication while repl_transport.buffer: logger.info("------ flush ------") repl_transport.flush(30) self.pump(0) prev_token = current_token current_token = event_source.get_current_key() # attempt to replicate the behaviour of the sync handler. # # First, we get a list of the rooms we are joined to joined_rooms = self.get_success( self.slaved_store.get_rooms_for_user_with_stream_ordering(USER_ID_2) ) # Then, we get a list of the events since the last sync membership_changes = self.get_success( self.slaved_store.get_membership_changes_for_user( USER_ID_2, prev_token, current_token ) ) logger.info( "%s->%s: joined_rooms=%r membership_changes=%r", prev_token, current_token, joined_rooms, membership_changes, ) # the membership change is only any use to us if the room is in the # joined_rooms list. if membership_changes: expected_pos = PersistedEventPosition( "master", j2.internal_metadata.stream_ordering ) self.assertEqual( joined_rooms, {GetRoomsForUserWithStreamOrdering(ROOM_ID, expected_pos)}, )