diff --git a/synapse/storage/persist_events.py b/synapse/storage/persist_events.py index 3186061f96c9..61fc49c69c8d 100644 --- a/synapse/storage/persist_events.py +++ b/synapse/storage/persist_events.py @@ -846,9 +846,32 @@ async def _prune_extremities( one_day_ago = self._clock.time_msec() - 24 * 60 * 60 * 1000 current_depth = max(e.depth for e, _ in events_context) for event in dropped_events.values(): - if self.is_mine_id(event.sender) and event.type != EventTypes.Dummy: - logger.debug("Not dropping own event") - return new_latest_event_ids + # If the event is a local dummy event then we should check it + # doesn't reference any local events, as we want to reference those + # if we send any new events. + # + # Note we do this recursively to handle the case where a dummy event + # references a dummy event that only references remote events. + # + # Ideally we'd figure out a way of still being able to drop old + # dummy events that reference local events, but this is good enough + # as a first cut. + events_to_check = [event] + while events_to_check: + new_events = set() + for event_to_check in events_to_check: + if self.is_mine_id(event_to_check.sender): + if event_to_check.type != EventTypes.Dummy: + logger.debug("Not dropping own event") + return new_latest_event_ids + new_events.update(event_to_check.prev_event_ids()) + + prev_events = await self.main_store.get_events( + new_events, + allow_rejected=True, + redact_behaviour=EventRedactBehaviour.AS_IS, + ) + events_to_check = prev_events.values() if ( event.origin_server_ts < one_day_ago diff --git a/tests/storage/test_events.py b/tests/storage/test_events.py index 1db0dc648e54..71210ce6065c 100644 --- a/tests/storage/test_events.py +++ b/tests/storage/test_events.py @@ -217,9 +217,9 @@ def test_do_not_prune_gap_if_other_server(self): # Check the new extremity is just the new remote event. self.assert_extremities([self.remote_event_1.event_id, remote_event_2.event_id]) - def test_prune_gap_if_dummy(self): + def test_prune_gap_if_dummy_remote(self): """Test that we drop extremities after a gap when the previous extremity - is a local dummy event and "old". + is a local dummy event and only points to remote events. """ body = self.helper.send_event( @@ -257,6 +257,48 @@ def test_prune_gap_if_dummy(self): # Check the new extremity is just the new remote event. self.assert_extremities([remote_event_2.event_id]) + def test_prune_gap_if_dummy_local(self): + """Test that we don't drop extremities after a gap when the previous + extremity is a local dummy event and points to local events. + """ + + body = self.helper.send(self.room_id, body="Test", tok=self.token) + + body = self.helper.send_event( + self.room_id, type=EventTypes.Dummy, content={}, tok=self.token + ) + local_message_event_id = body["event_id"] + self.assert_extremities([local_message_event_id]) + + # Advance the clock for many days to make the old extremity "old". We + # also set the depth to "lots". + self.reactor.advance(7 * 24 * 60 * 60) + + # Fudge a second event which points to an event we don't have. This is a + # state event so that the state changes (otherwise we won't prune the + # extremity as they'll have the same state group). + remote_event_2 = event_from_pdu_json( + { + "type": EventTypes.Member, + "state_key": "@user:other2", + "content": {"membership": Membership.JOIN}, + "room_id": self.room_id, + "sender": "@user:other2", + "depth": 10000, + "prev_events": ["$some_unknown_message"], + "auth_events": [], + "origin_server_ts": self.clock.time_msec(), + }, + RoomVersions.V6, + ) + + state_before_gap = self.get_success(self.state.get_current_state(self.room_id)) + + self.persist_event(remote_event_2, state=state_before_gap.values()) + + # Check the new extremity is just the new remote event. + self.assert_extremities([remote_event_2.event_id, local_message_event_id]) + def test_do_not_prune_gap_if_not_dummy(self): """Test that we do not drop extremities after a gap when the previous extremity is not a dummy event.