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)
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