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

Commit dc02d9f

Browse files
Avoid checking the event cache when backfilling events (#14164)
1 parent 828b550 commit dc02d9f

File tree

4 files changed

+140
-15
lines changed

4 files changed

+140
-15
lines changed

changelog.d/14164.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a bug introduced in 1.30.0 where purging and rejoining a room without restarting in-between would result in a broken room.

synapse/handlers/federation_event.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -798,9 +798,42 @@ async def _process_pulled_events(
798798
],
799799
)
800800

801+
# Check if we already any of these have these events.
802+
# Note: we currently make a lookup in the database directly here rather than
803+
# checking the event cache, due to:
804+
# https://github.com/matrix-org/synapse/issues/13476
805+
existing_events_map = await self._store._get_events_from_db(
806+
[event.event_id for event in events]
807+
)
808+
809+
new_events = []
810+
for event in events:
811+
event_id = event.event_id
812+
813+
# If we've already seen this event ID...
814+
if event_id in existing_events_map:
815+
existing_event = existing_events_map[event_id]
816+
817+
# ...and the event itself was not previously stored as an outlier...
818+
if not existing_event.event.internal_metadata.is_outlier():
819+
# ...then there's no need to persist it. We have it already.
820+
logger.info(
821+
"_process_pulled_event: Ignoring received event %s which we "
822+
"have already seen",
823+
event.event_id,
824+
)
825+
continue
826+
827+
# While we have seen this event before, it was stored as an outlier.
828+
# We'll now persist it as a non-outlier.
829+
logger.info("De-outliering event %s", event_id)
830+
831+
# Continue on with the events that are new to us.
832+
new_events.append(event)
833+
801834
# We want to sort these by depth so we process them and
802835
# tell clients about them in order.
803-
sorted_events = sorted(events, key=lambda x: x.depth)
836+
sorted_events = sorted(new_events, key=lambda x: x.depth)
804837
for ev in sorted_events:
805838
with nested_logging_context(ev.event_id):
806839
await self._process_pulled_event(origin, ev, backfilled=backfilled)
@@ -852,18 +885,6 @@ async def _process_pulled_event(
852885

853886
event_id = event.event_id
854887

855-
existing = await self._store.get_event(
856-
event_id, allow_none=True, allow_rejected=True
857-
)
858-
if existing:
859-
if not existing.internal_metadata.is_outlier():
860-
logger.info(
861-
"_process_pulled_event: Ignoring received event %s which we have already seen",
862-
event_id,
863-
)
864-
return
865-
logger.info("De-outliering event %s", event_id)
866-
867888
try:
868889
self._sanity_check_event(event)
869890
except SynapseError as err:

synapse/storage/databases/main/events_worker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ async def get_event(
374374
If there is a mismatch, behave as per allow_none.
375375
376376
Returns:
377-
The event, or None if the event was not found.
377+
The event, or None if the event was not found and allow_none is `True`.
378378
"""
379379
if not isinstance(event_id, str):
380380
raise TypeError("Invalid event event_id %r" % (event_id,))

tests/handlers/test_federation.py

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@
1919
from twisted.test.proto_helpers import MemoryReactor
2020

2121
from synapse.api.constants import EventTypes
22-
from synapse.api.errors import AuthError, Codes, LimitExceededError, SynapseError
22+
from synapse.api.errors import (
23+
AuthError,
24+
Codes,
25+
LimitExceededError,
26+
NotFoundError,
27+
SynapseError,
28+
)
2329
from synapse.api.room_versions import RoomVersions
2430
from synapse.events import EventBase, make_event_from_dict
2531
from synapse.federation.federation_base import event_from_pdu_json
@@ -28,6 +34,7 @@
2834
from synapse.rest import admin
2935
from synapse.rest.client import login, room
3036
from synapse.server import HomeServer
37+
from synapse.storage.databases.main.events_worker import EventCacheEntry
3138
from synapse.util import Clock
3239
from synapse.util.stringutils import random_string
3340

@@ -322,6 +329,102 @@ def test_backfill_with_many_backward_extremities(self) -> None:
322329
)
323330
self.get_success(d)
324331

332+
def test_backfill_ignores_known_events(self) -> None:
333+
"""
334+
Tests that events that we already know about are ignored when backfilling.
335+
"""
336+
# Set up users
337+
user_id = self.register_user("kermit", "test")
338+
tok = self.login("kermit", "test")
339+
340+
other_server = "otherserver"
341+
other_user = "@otheruser:" + other_server
342+
343+
# Create a room to backfill events into
344+
room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
345+
room_version = self.get_success(self.store.get_room_version(room_id))
346+
347+
# Build an event to backfill
348+
event = event_from_pdu_json(
349+
{
350+
"type": EventTypes.Message,
351+
"content": {"body": "hello world", "msgtype": "m.text"},
352+
"room_id": room_id,
353+
"sender": other_user,
354+
"depth": 32,
355+
"prev_events": [],
356+
"auth_events": [],
357+
"origin_server_ts": self.clock.time_msec(),
358+
},
359+
room_version,
360+
)
361+
362+
# Ensure the event is not already in the DB
363+
self.get_failure(
364+
self.store.get_event(event.event_id),
365+
NotFoundError,
366+
)
367+
368+
# Backfill the event and check that it has entered the DB.
369+
370+
# We mock out the FederationClient.backfill method, to pretend that a remote
371+
# server has returned our fake event.
372+
federation_client_backfill_mock = Mock(return_value=make_awaitable([event]))
373+
self.hs.get_federation_client().backfill = federation_client_backfill_mock
374+
375+
# We also mock the persist method with a side effect of itself. This allows us
376+
# to track when it has been called while preserving its function.
377+
persist_events_and_notify_mock = Mock(
378+
side_effect=self.hs.get_federation_event_handler().persist_events_and_notify
379+
)
380+
self.hs.get_federation_event_handler().persist_events_and_notify = (
381+
persist_events_and_notify_mock
382+
)
383+
384+
# Small side-tangent. We populate the event cache with the event, even though
385+
# it is not yet in the DB. This is an invalid scenario that can currently occur
386+
# due to not properly invalidating the event cache.
387+
# See https://github.com/matrix-org/synapse/issues/13476.
388+
#
389+
# As a result, backfill should not rely on the event cache to check whether
390+
# we already have an event in the DB.
391+
# TODO: Remove this bit when the event cache is properly invalidated.
392+
cache_entry = EventCacheEntry(
393+
event=event,
394+
redacted_event=None,
395+
)
396+
self.store._get_event_cache.set_local((event.event_id,), cache_entry)
397+
398+
# We now call FederationEventHandler.backfill (a separate method) to trigger
399+
# a backfill request. It should receive the fake event.
400+
self.get_success(
401+
self.hs.get_federation_event_handler().backfill(
402+
other_user,
403+
room_id,
404+
limit=10,
405+
extremities=[],
406+
)
407+
)
408+
409+
# Check that our fake event was persisted.
410+
persist_events_and_notify_mock.assert_called_once()
411+
persist_events_and_notify_mock.reset_mock()
412+
413+
# Now we repeat the backfill, having the homeserver receive the fake event
414+
# again.
415+
self.get_success(
416+
self.hs.get_federation_event_handler().backfill(
417+
other_user,
418+
room_id,
419+
limit=10,
420+
extremities=[],
421+
),
422+
)
423+
424+
# This time, we expect no event persistence to have occurred, as we already
425+
# have this event.
426+
persist_events_and_notify_mock.assert_not_called()
427+
325428
@unittest.override_config(
326429
{"rc_invites": {"per_user": {"per_second": 0.5, "burst_count": 3}}}
327430
)

0 commit comments

Comments
 (0)