Skip to content

Commit

Permalink
Fix joining remote rooms when a on_new_event callback is registered (
Browse files Browse the repository at this point in the history
…#16973)

Since Synapse 1.76.0, any module which registers a `on_new_event`
callback would brick the ability to join remote rooms.
This is because this callback tried to get the full state of the room,
which would end up in a deadlock.

Related:
matrix-org/synapse-auto-accept-invite#18

The following module would brick the ability to join remote rooms:

```python
from typing import Any, Dict, Literal, Union
import logging

from synapse.module_api import ModuleApi, EventBase

logger = logging.getLogger(__name__)

class MyModule:
    def __init__(self, config: None, api: ModuleApi):
        self._api = api
        self._config = config

        self._api.register_third_party_rules_callbacks(
            on_new_event=self.on_new_event,
        )

    async def on_new_event(self, event: EventBase, _state_map: Any) -> None:
        logger.info(f"Received new event: {event}")

    @staticmethod
    def parse_config(_config: Dict[str, Any]) -> None:
        return None
```

This is technically a breaking change, as we are now passing partial
state on the `on_new_event` callback.
However, this callback was broken for federated rooms since 1.76.0, and
local rooms have full state anyway, so it's unlikely that it would
change anything.
  • Loading branch information
sandhose committed Mar 6, 2024
1 parent 2d1bb0b commit 4af3301
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 16 deletions.
1 change: 1 addition & 0 deletions changelog.d/16973.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix joining remote rooms when a module uses the `on_new_event` callback. This callback may now pass partial state events instead of the full state for remote rooms.
4 changes: 4 additions & 0 deletions docs/modules/third_party_rules_callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ Called after sending an event into a room. The module is passed the event, as we
as the state of the room _after_ the event. This means that if the event is a state event,
it will be included in this state.

The state map may not be complete if Synapse hasn't yet loaded the full state
of the room. This can happen for events in rooms that were just joined from
a remote server.

Note that this callback is called when the event has already been processed and stored
into the room, which means this callback cannot be used to deny persisting the event. To
deny an incoming event, see [`check_event_for_spam`](spam_checker_callbacks.md#check_event_for_spam) instead.
Expand Down
23 changes: 9 additions & 14 deletions synapse/module_api/callbacks/third_party_event_rules_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ async def check_threepid_can_be_invited(
if len(self._check_threepid_can_be_invited_callbacks) == 0:
return True

state_events = await self._get_state_map_for_room(room_id)
state_events = await self._storage_controllers.state.get_current_state(room_id)

for callback in self._check_threepid_can_be_invited_callbacks:
try:
Expand Down Expand Up @@ -399,7 +399,7 @@ async def check_visibility_can_be_modified(
if len(self._check_visibility_can_be_modified_callbacks) == 0:
return True

state_events = await self._get_state_map_for_room(room_id)
state_events = await self._storage_controllers.state.get_current_state(room_id)

for callback in self._check_visibility_can_be_modified_callbacks:
try:
Expand Down Expand Up @@ -427,7 +427,13 @@ async def on_new_event(self, event_id: str) -> None:
return

event = await self.store.get_event(event_id)
state_events = await self._get_state_map_for_room(event.room_id)

# We *don't* want to wait for the full state here, because waiting for full
# state will persist event, which in turn will call this method.
# This would end up in a deadlock.
state_events = await self._storage_controllers.state.get_current_state(
event.room_id, await_full_state=False
)

for callback in self._on_new_event_callbacks:
try:
Expand Down Expand Up @@ -490,17 +496,6 @@ async def check_can_deactivate_user(
)
return True

async def _get_state_map_for_room(self, room_id: str) -> StateMap[EventBase]:
"""Given a room ID, return the state events of that room.
Args:
room_id: The ID of the room.
Returns:
A dict mapping (event type, state key) to state event.
"""
return await self._storage_controllers.state.get_current_state(room_id)

async def on_profile_update(
self, user_id: str, new_profile: ProfileInfo, by_admin: bool, deactivation: bool
) -> None:
Expand Down
9 changes: 7 additions & 2 deletions synapse/storage/controllers/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,10 +562,15 @@ async def get_current_state_deltas(
@trace
@tag_args
async def get_current_state(
self, room_id: str, state_filter: Optional[StateFilter] = None
self,
room_id: str,
state_filter: Optional[StateFilter] = None,
await_full_state: bool = True,
) -> StateMap[EventBase]:
"""Same as `get_current_state_ids` but also fetches the events"""
state_map_ids = await self.get_current_state_ids(room_id, state_filter)
state_map_ids = await self.get_current_state_ids(
room_id, state_filter, await_full_state
)

event_map = await self.stores.main.get_events(list(state_map_ids.values()))

Expand Down

0 comments on commit 4af3301

Please sign in to comment.