diff --git a/synapse/storage/databases/main/events.py b/synapse/storage/databases/main/events.py
index 23fa089bca..ca2a9ba9d1 100644
--- a/synapse/storage/databases/main/events.py
+++ b/synapse/storage/databases/main/events.py
@@ -2145,6 +2145,14 @@ class PersistEventsStore:
state_groups = {}
for event, context in events_and_contexts:
if event.internal_metadata.is_outlier():
+ # double-check that we don't have any events that claim to be outliers
+ # *and* have partial state (which is meaningless: we should have no
+ # state at all for an outlier)
+ if context.partial_state:
+ raise ValueError(
+ "Outlier event %s claims to have partial state", event.event_id
+ )
+
continue
# if the event was rejected, just give it the same state as its
@@ -2155,6 +2163,23 @@ class PersistEventsStore:
state_groups[event.event_id] = context.state_group
+ # if we have partial state for these events, record the fact. (This happens
+ # here rather than in _store_event_txn because it also needs to happen when
+ # we de-outlier an event.)
+ self.db_pool.simple_insert_many_txn(
+ txn,
+ table="partial_state_events",
+ keys=("room_id", "event_id"),
+ values=[
+ (
+ event.room_id,
+ event.event_id,
+ )
+ for event, ctx in events_and_contexts
+ if ctx.partial_state
+ ],
+ )
+
self.db_pool.simple_upsert_many_txn(
txn,
table="event_to_state_groups",
diff --git a/synapse/storage/databases/main/events_worker.py b/synapse/storage/databases/main/events_worker.py
index 2a255d1031..26784f755e 100644
--- a/synapse/storage/databases/main/events_worker.py
+++ b/synapse/storage/databases/main/events_worker.py
@@ -1953,3 +1953,31 @@ class EventsWorkerStore(SQLBaseStore):
"get_event_id_for_timestamp_txn",
get_event_id_for_timestamp_txn,
)
+
+ @cachedList("is_partial_state_event", list_name="event_ids")
+ async def get_partial_state_events(
+ self, event_ids: Collection[str]
+ ) -> Dict[str, bool]:
+ """Checks which of the given events have partial state"""
+ result = await self.db_pool.simple_select_many_batch(
+ table="partial_state_events",
+ column="event_id",
+ iterable=event_ids,
+ retcols=["event_id"],
+ desc="get_partial_state_events",
+ )
+ # convert the result to a dict, to make @cachedList work
+ partial = {r["event_id"] for r in result}
+ return {e_id: e_id in partial for e_id in event_ids}
+
+ @cached()
+ async def is_partial_state_event(self, event_id: str) -> bool:
+ """Checks if the given event has partial state"""
+ result = await self.db_pool.simple_select_one_onecol(
+ table="partial_state_events",
+ keyvalues={"event_id": event_id},
+ retcol="1",
+ allow_none=True,
+ desc="is_partial_state_event",
+ )
+ return result is not None
diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py
index 0416df64ce..94068940b9 100644
--- a/synapse/storage/databases/main/room.py
+++ b/synapse/storage/databases/main/room.py
@@ -20,6 +20,7 @@ from typing import (
TYPE_CHECKING,
Any,
Awaitable,
+ Collection,
Dict,
List,
Optional,
@@ -1543,6 +1544,42 @@ class RoomStore(RoomBackgroundUpdateStore, RoomWorkerStore):
lock=False,
)
+ async def store_partial_state_room(
+ self,
+ room_id: str,
+ servers: Collection[str],
+ ) -> None:
+ """Mark the given room as containing events with partial state
+
+ Args:
+ room_id: the ID of the room
+ servers: other servers known to be in the room
+ """
+ await self.db_pool.runInteraction(
+ "store_partial_state_room",
+ self._store_partial_state_room_txn,
+ room_id,
+ servers,
+ )
+
+ @staticmethod
+ def _store_partial_state_room_txn(
+ txn: LoggingTransaction, room_id: str, servers: Collection[str]
+ ) -> None:
+ DatabasePool.simple_insert_txn(
+ txn,
+ table="partial_state_rooms",
+ values={
+ "room_id": room_id,
+ },
+ )
+ DatabasePool.simple_insert_many_txn(
+ txn,
+ table="partial_state_rooms_servers",
+ keys=("room_id", "server_name"),
+ values=((room_id, s) for s in servers),
+ )
+
async def maybe_store_room_on_outlier_membership(
self, room_id: str, room_version: RoomVersion
) -> None:
|