Exemplo n.º 1
0
    async def _calculate_state_delta(
            self, room_id: str, current_state: StateMap[str]) -> DeltaState:
        """Calculate the new state deltas for a room.

        Assumes that we are only persisting events for one room at a time.
        """
        existing_state = await self.main_store.get_current_state_ids(room_id)

        to_delete = [key for key in existing_state if key not in current_state]

        to_insert = {
            key: ev_id
            for key, ev_id in current_state.items()
            if ev_id != existing_state.get(key)
        }

        return DeltaState(to_delete=to_delete, to_insert=to_insert)
Exemplo n.º 2
0
    async def _persist_event_batch(
        self,
        events_and_contexts: List[Tuple[EventBase, EventContext]],
        backfilled: bool = False,
    ) -> Dict[str, str]:
        """Callback for the _event_persist_queue

        Calculates the change to current state and forward extremities, and
        persists the given events and with those updates.

        Returns:
            A dictionary of event ID to event ID we didn't persist as we already
            had another event persisted with the same TXN ID.
        """
        replaced_events: Dict[str, str] = {}
        if not events_and_contexts:
            return replaced_events

        # Check if any of the events have a transaction ID that has already been
        # persisted, and if so we don't persist it again.
        #
        # We should have checked this a long time before we get here, but it's
        # possible that different send event requests race in such a way that
        # they both pass the earlier checks. Checking here isn't racey as we can
        # have only one `_persist_events` per room being called at a time.
        replaced_events = await self.main_store.get_already_persisted_events(
            (event for event, _ in events_and_contexts))

        if replaced_events:
            events_and_contexts = [(e, ctx) for e, ctx in events_and_contexts
                                   if e.event_id not in replaced_events]

            if not events_and_contexts:
                return replaced_events

        chunks = [
            events_and_contexts[x:x + 100]
            for x in range(0, len(events_and_contexts), 100)
        ]

        for chunk in chunks:
            # We can't easily parallelize these since different chunks
            # might contain the same event. :(

            # NB: Assumes that we are only persisting events for one room
            # at a time.

            # map room_id->set[event_ids] giving the new forward
            # extremities in each room
            new_forward_extremities: Dict[str, Set[str]] = {}

            # map room_id->(to_delete, to_insert) where to_delete is a list
            # of type/state keys to remove from current state, and to_insert
            # is a map (type,key)->event_id giving the state delta in each
            # room
            state_delta_for_room: Dict[str, DeltaState] = {}

            # Set of remote users which were in rooms the server has left. We
            # should check if we still share any rooms and if not we mark their
            # device lists as stale.
            potentially_left_users: Set[str] = set()

            if not backfilled:
                with Measure(self._clock, "_calculate_state_and_extrem"):
                    # Work out the new "current state" for each room.
                    # We do this by working out what the new extremities are and then
                    # calculating the state from that.
                    events_by_room: Dict[str, List[Tuple[EventBase,
                                                         EventContext]]] = {}
                    for event, context in chunk:
                        events_by_room.setdefault(event.room_id, []).append(
                            (event, context))

                    for room_id, ev_ctx_rm in events_by_room.items():
                        latest_event_ids = set(
                            await self.main_store.get_latest_event_ids_in_room(
                                room_id))
                        new_latest_event_ids = await self._calculate_new_extremities(
                            room_id, ev_ctx_rm, latest_event_ids)

                        if new_latest_event_ids == latest_event_ids:
                            # No change in extremities, so no change in state
                            continue

                        # there should always be at least one forward extremity.
                        # (except during the initial persistence of the send_join
                        # results, in which case there will be no existing
                        # extremities, so we'll `continue` above and skip this bit.)
                        assert new_latest_event_ids, "No forward extremities left!"

                        new_forward_extremities[room_id] = new_latest_event_ids

                        len_1 = (len(latest_event_ids) == 1
                                 and len(new_latest_event_ids) == 1)
                        if len_1:
                            all_single_prev_not_state = all(
                                len(event.prev_event_ids()) == 1
                                and not event.is_state()
                                for event, ctx in ev_ctx_rm)
                            # Don't bother calculating state if they're just
                            # a long chain of single ancestor non-state events.
                            if all_single_prev_not_state:
                                continue

                        state_delta_counter.inc()
                        if len(new_latest_event_ids) == 1:
                            state_delta_single_event_counter.inc()

                            # This is a fairly handwavey check to see if we could
                            # have guessed what the delta would have been when
                            # processing one of these events.
                            # What we're interested in is if the latest extremities
                            # were the same when we created the event as they are
                            # now. When this server creates a new event (as opposed
                            # to receiving it over federation) it will use the
                            # forward extremities as the prev_events, so we can
                            # guess this by looking at the prev_events and checking
                            # if they match the current forward extremities.
                            for ev, _ in ev_ctx_rm:
                                prev_event_ids = set(ev.prev_event_ids())
                                if latest_event_ids == prev_event_ids:
                                    state_delta_reuse_delta_counter.inc()
                                    break

                        logger.debug("Calculating state delta for room %s",
                                     room_id)
                        with Measure(
                                self._clock,
                                "persist_events.get_new_state_after_events"):
                            res = await self._get_new_state_after_events(
                                room_id,
                                ev_ctx_rm,
                                latest_event_ids,
                                new_latest_event_ids,
                            )
                            current_state, delta_ids, new_latest_event_ids = res

                            # there should always be at least one forward extremity.
                            # (except during the initial persistence of the send_join
                            # results, in which case there will be no existing
                            # extremities, so we'll `continue` above and skip this bit.)
                            assert new_latest_event_ids, "No forward extremities left!"

                            new_forward_extremities[
                                room_id] = new_latest_event_ids

                        # If either are not None then there has been a change,
                        # and we need to work out the delta (or use that
                        # given)
                        delta = None
                        if delta_ids is not None:
                            # If there is a delta we know that we've
                            # only added or replaced state, never
                            # removed keys entirely.
                            delta = DeltaState([], delta_ids)
                        elif current_state is not None:
                            with Measure(
                                    self._clock,
                                    "persist_events.calculate_state_delta"):
                                delta = await self._calculate_state_delta(
                                    room_id, current_state)

                        if delta:
                            # If we have a change of state then lets check
                            # whether we're actually still a member of the room,
                            # or if our last user left. If we're no longer in
                            # the room then we delete the current state and
                            # extremities.
                            is_still_joined = await self._is_server_still_joined(
                                room_id,
                                ev_ctx_rm,
                                delta,
                                current_state,
                                potentially_left_users,
                            )
                            if not is_still_joined:
                                logger.info("Server no longer in room %s",
                                            room_id)
                                latest_event_ids = set()
                                current_state = {}
                                delta.no_longer_in_room = True

                            state_delta_for_room[room_id] = delta

            await self.persist_events_store._persist_events_and_state_updates(
                chunk,
                state_delta_for_room=state_delta_for_room,
                new_forward_extremities=new_forward_extremities,
                use_negative_stream_ordering=backfilled,
                inhibit_local_membership_updates=backfilled,
            )

            await self._handle_potentially_left_users(potentially_left_users)

        return replaced_events