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

Add a class UnpersistedEventContext to allow for the batching up of storing state groups #14675

Merged
merged 32 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
286469f
add class UnpersistedEventContext
H-Shay Dec 13, 2022
6322dac
modify create new client event to create unpersistedeventcontexts
H-Shay Dec 13, 2022
54fa7f0
persist event contexts after creation
H-Shay Dec 13, 2022
d23d1be
fix tests to persist unpersisted event contexts
H-Shay Dec 13, 2022
b483243
cleanup
H-Shay Dec 13, 2022
c008b6f
misc lints + cleanup
H-Shay Dec 13, 2022
c6ef7bb
Merge branch 'develop' into shay/rework_event_context
H-Shay Dec 13, 2022
0f07c0e
changelog + fix comments
H-Shay Dec 13, 2022
a66cc71
Merge branch 'shay/rework_event_context' of https://github.com/matrix…
H-Shay Dec 13, 2022
78a71ed
lints
H-Shay Dec 13, 2022
6321576
fix batch insertion?
H-Shay Dec 14, 2022
d6454ce
reduce redundant calculation
H-Shay Dec 14, 2022
8b99436
add unpersisted event classes
H-Shay Jan 9, 2023
1668fd2
rework compute_event_context, split into function that returns unpers…
H-Shay Jan 9, 2023
7e494fc
use calculate_context_info to create unpersisted event contexts
H-Shay Jan 9, 2023
61c8de0
update typing
H-Shay Jan 9, 2023
ed2e305
Merge branch 'develop' into shay/rework_event_context
H-Shay Jan 9, 2023
f7eafcf
$%#^&*
H-Shay Jan 10, 2023
0402917
black
H-Shay Jan 10, 2023
373e331
fix comments and consolidate classes, use attr.s for class
H-Shay Jan 12, 2023
408fa21
requested changes
H-Shay Jan 12, 2023
345d746
lint
H-Shay Jan 12, 2023
b9ee9d3
Merge branch 'develop' into shay/rework_event_context
H-Shay Jan 20, 2023
cdce9a2
requested changes
H-Shay Jan 20, 2023
b69a87c
requested changes
H-Shay Jan 23, 2023
8830c94
refactor to be stupidly explicit
H-Shay Jan 26, 2023
d4c380d
Merge branch 'develop' into shay/rework_event_context
H-Shay Jan 26, 2023
f439b23
Merge branch 'develop' into shay/rework_event_context
erikjohnston Feb 7, 2023
3614ed0
clearer renaming and flow
H-Shay Feb 8, 2023
a0bcc0a
Merge branch 'shay/rework_event_context' of https://github.com/matrix…
H-Shay Feb 8, 2023
427d007
make partial state non-optional
H-Shay Feb 8, 2023
c3bdb6e
update docstrings
H-Shay Feb 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/14675.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a class UnpersistedEventContext to allow for the batching up of storing state groups.
245 changes: 244 additions & 1 deletion synapse/events/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, List, Optional, Tuple

import attr
from frozendict import frozendict

# from synapse.api.constants import EventTypes
H-Shay marked this conversation as resolved.
Show resolved Hide resolved
from synapse.appservice import ApplicationService
from synapse.events import EventBase
from synapse.types import JsonDict, StateMap
Expand All @@ -26,8 +28,27 @@
from synapse.types.state import StateFilter


class UnpersistedEventContextBase(ABC):
H-Shay marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, storage_controller: "StorageControllers"):
self._storage: "StorageControllers" = storage_controller
self.app_service: Optional[ApplicationService] = None

@abstractmethod
async def persist(
self,
event: EventBase,
) -> "EventContext":
pass

@abstractmethod
async def get_prev_state_ids(
self, state_filter: Optional["StateFilter"] = None
) -> StateMap[str]:
pass


@attr.s(slots=True, auto_attribs=True)
class EventContext:
class EventContext(UnpersistedEventContextBase):
"""
Holds information relevant to persisting an event

Expand Down Expand Up @@ -122,6 +143,9 @@ def for_outlier(
"""Return an EventContext instance suitable for persisting an outlier event"""
return EventContext(storage=storage)

async def persist(self, event: EventBase) -> "EventContext":
return self

async def serialize(self, event: EventBase, store: "DataStore") -> JsonDict:
"""Converts self to a type that can be serialized as JSON, and then
deserialized by `deserialize`
Expand Down Expand Up @@ -254,6 +278,225 @@ async def get_prev_state_ids(
)


class UnpersistedEventContextForBatched(UnpersistedEventContextBase):
"""
This is a version of an EventContext before the new state group (if any) has been
computed and stored. It contains information about the state before the event (which
also may be the information after the event, if the event is not a state event). The
UnpersistedEventContext must be converted into an EventContext by calling the method
'persist' on it before it is suitable to be sent to the DB for processing.

state_group_before_event:
the state group at/before the event. This is required if `for_batch` is True.

state_group:
this will always be None until it is persisted

state_group_before_event: The ID of the state group representing the state
H-Shay marked this conversation as resolved.
Show resolved Hide resolved
of the room before this event.

state_delta_due_to_event: If the event is a state event, then this is the
delta of the state between `state_group` and `state_group_before_event`

prev_group: If it is known, ``state_group``'s prev_group.

If the event is a state event, this is normally the same as
``state_group_before_event``.

delta_ids: If ``prev_group`` is not None, the state delta between ``prev_group``
and ``state_group``.

state_map: A map of the current state of the room
H-Shay marked this conversation as resolved.
Show resolved Hide resolved
"""

def __init__(
self,
storage_controller: "StorageControllers",
state_group: Optional[int],
state_delta_due_to_event: dict,
prev_group: Optional[int],
delta_ids: Optional[StateMap[str]],
partial_state: bool,
state_map: StateMap[str],
state_group_before_event: int,
) -> None:
super().__init__(storage_controller)
self.state_group = state_group
self.state_delta_due_to_event = state_delta_due_to_event
self.prev_group = prev_group
self.delta_ids = delta_ids
self.partial_state = partial_state
self.state_map = state_map
self.state_group_before_event = state_group_before_event
H-Shay marked this conversation as resolved.
Show resolved Hide resolved

async def get_prev_state_ids(
self, state_filter: Optional["StateFilter"] = None
) -> StateMap[str]:
"""
Gets the room state map, excluding this event.

Args:
state_filter: specifies the type of state event to fetch from DB, example: EventTypes.JoinRules
H-Shay marked this conversation as resolved.
Show resolved Hide resolved

Returns:
Maps a (type, state_key) to the event ID of the state event matching
this tuple.
"""
return self.state_map

async def persist(self, event: EventBase) -> EventContext:
"""
If the event is a state event, calculates the current state group for the context,
stores it, and returns a EventContext. If the event is not state, returns
an EventContext.
H-Shay marked this conversation as resolved.
Show resolved Hide resolved

Args:
event: event that the EventContext is associated with.

Returns: An EventContext suitable for sending to the database with the event
for persisting
"""
assert self.partial_state is not None
H-Shay marked this conversation as resolved.
Show resolved Hide resolved
if not event.is_state():
return EventContext.with_state(
storage=self._storage,
state_group=self.state_group_before_event,
state_group_before_event=self.state_group_before_event,
state_delta_due_to_event=self.state_delta_due_to_event,
partial_state=self.partial_state,
prev_group=self.prev_group,
delta_ids=self.delta_ids,
)
else:
state_group_after_event = await self._storage.state.store_state_group(
event.event_id,
event.room_id,
prev_group=self.prev_group,
delta_ids=self.delta_ids,
current_state_ids=None,
H-Shay marked this conversation as resolved.
Show resolved Hide resolved
)

return EventContext.with_state(
storage=self._storage,
state_group=state_group_after_event,
state_group_before_event=self.state_group_before_event,
state_delta_due_to_event=self.state_delta_due_to_event,
partial_state=self.partial_state,
prev_group=self.prev_group,
delta_ids=self.delta_ids,
)


class UnpersistedEventContext(UnpersistedEventContextBase):
H-Shay marked this conversation as resolved.
Show resolved Hide resolved
"""
This is a version of an EventContext before the new state group (if any) has been
computed and stored. It contains information about the state before the event (which
also may be the information after the event, if the event is not a state event). The
UnpersistedEventContext must be converted into an EventContext by calling the method
'persist' on it before it is suitable to be sent to the DB for processing.

state_group_before_event:
the state group at/before the event. This is required if `for_batch` is True.
state_group:
this will always be None until it is persisted

state_group_before_event: The ID of the state group representing the state
of the room before this event.

state_delta_due_to_event: If the event is a state event, then this is the
delta of the state between `state_group` and `state_group_before_event`

prev_group: If it is known, ``state_group``'s prev_group.

If the event is a state event, this is normally the same as
``state_group_before_event``.

delta_ids: If ``prev_group`` is not None, the state delta between ``prev_group``
and ``state_group``.

partial_state: Whether the event has partial state.
"""

def __init__(
self,
storage_controller: "StorageControllers",
state_group_before_event: Optional[int],
state_group: Optional[int],
state_delta_due_to_event: Optional[dict],
prev_group: Optional[int],
delta_ids: Optional[StateMap[str]],
partial_state: Optional[bool],
):
super().__init__(storage_controller)
self.state_group_before_event = state_group_before_event
self.state_group = state_group
self.state_delta_due_to_event = state_delta_due_to_event
self.prev_group = prev_group
self.delta_ids = delta_ids
self.partial_state = partial_state

async def get_prev_state_ids(
self, state_filter: Optional["StateFilter"] = None
) -> StateMap[str]:
"""
Gets the room state map, excluding this event.

Args:
state_filter: specifies the type of state event to fetch from DB, example: EventTypes.JoinRules

Returns:
Maps a (type, state_key) to the event ID of the state event matching
this tuple.
"""

assert self.state_group_before_event is not None
return await self._storage.state.get_state_ids_for_group(
self.state_group_before_event, state_filter
)

async def persist(self, event: EventBase) -> EventContext:
"""
If the event is a state event, calculates the current state group for the context,
stores it, and returns a EventContext. If the event is not state, returns
an EventContext.

Args:
event: event that the EventContext is associated with.

Returns: An EventContext suitable for sending to the database with the event
for persisting
"""
assert self.partial_state is not None
if not event.is_state():
return EventContext.with_state(
storage=self._storage,
state_group=self.state_group_before_event,
state_group_before_event=self.state_group_before_event,
state_delta_due_to_event=self.state_delta_due_to_event,
partial_state=self.partial_state,
prev_group=self.prev_group,
delta_ids=self.delta_ids,
)
else:
state_group_after_event = await self._storage.state.store_state_group(
event.event_id,
event.room_id,
prev_group=self.prev_group,
delta_ids=self.delta_ids,
current_state_ids=None,
)

return EventContext.with_state(
storage=self._storage,
state_group=state_group_after_event,
state_group_before_event=self.state_group_before_event,
state_delta_due_to_event=self.state_delta_due_to_event,
partial_state=self.partial_state,
prev_group=self.prev_group,
delta_ids=self.delta_ids,
)


def _encode_state_dict(
state_dict: Optional[StateMap[str]],
) -> Optional[List[Tuple[str, str, str]]]:
Expand Down
8 changes: 5 additions & 3 deletions synapse/events/third_party_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, Optional, Tuple
from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, Optional, Tuple, Union

from twisted.internet.defer import CancelledError

from synapse.api.errors import ModuleFailedException, SynapseError
from synapse.events import EventBase
from synapse.events.snapshot import EventContext
from synapse.events.snapshot import EventContext, UnpersistedEventContextBase
from synapse.storage.roommember import ProfileInfo
from synapse.types import Requester, StateMap
from synapse.util.async_helpers import delay_cancellation, maybe_awaitable
Expand Down Expand Up @@ -231,7 +231,9 @@ def register_third_party_rules_callbacks(
self._on_threepid_bind_callbacks.append(on_threepid_bind)

async def check_event_allowed(
self, event: EventBase, context: EventContext
self,
event: EventBase,
context: Union[EventContext, UnpersistedEventContextBase],
H-Shay marked this conversation as resolved.
Show resolved Hide resolved
) -> Tuple[bool, Optional[dict]]:
"""Check if a provided event should be allowed in the given context.

Expand Down
Loading