Skip to content

Commit 4af3301

Browse files
authored
Fix joining remote rooms when a on_new_event callback is registered (#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.
1 parent 2d1bb0b commit 4af3301

File tree

4 files changed

+21
-16
lines changed

4 files changed

+21
-16
lines changed

changelog.d/16973.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

docs/modules/third_party_rules_callbacks.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ Called after sending an event into a room. The module is passed the event, as we
142142
as the state of the room _after_ the event. This means that if the event is a state event,
143143
it will be included in this state.
144144

145+
The state map may not be complete if Synapse hasn't yet loaded the full state
146+
of the room. This can happen for events in rooms that were just joined from
147+
a remote server.
148+
145149
Note that this callback is called when the event has already been processed and stored
146150
into the room, which means this callback cannot be used to deny persisting the event. To
147151
deny an incoming event, see [`check_event_for_spam`](spam_checker_callbacks.md#check_event_for_spam) instead.

synapse/module_api/callbacks/third_party_event_rules_callbacks.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ async def check_threepid_can_be_invited(
366366
if len(self._check_threepid_can_be_invited_callbacks) == 0:
367367
return True
368368

369-
state_events = await self._get_state_map_for_room(room_id)
369+
state_events = await self._storage_controllers.state.get_current_state(room_id)
370370

371371
for callback in self._check_threepid_can_be_invited_callbacks:
372372
try:
@@ -399,7 +399,7 @@ async def check_visibility_can_be_modified(
399399
if len(self._check_visibility_can_be_modified_callbacks) == 0:
400400
return True
401401

402-
state_events = await self._get_state_map_for_room(room_id)
402+
state_events = await self._storage_controllers.state.get_current_state(room_id)
403403

404404
for callback in self._check_visibility_can_be_modified_callbacks:
405405
try:
@@ -427,7 +427,13 @@ async def on_new_event(self, event_id: str) -> None:
427427
return
428428

429429
event = await self.store.get_event(event_id)
430-
state_events = await self._get_state_map_for_room(event.room_id)
430+
431+
# We *don't* want to wait for the full state here, because waiting for full
432+
# state will persist event, which in turn will call this method.
433+
# This would end up in a deadlock.
434+
state_events = await self._storage_controllers.state.get_current_state(
435+
event.room_id, await_full_state=False
436+
)
431437

432438
for callback in self._on_new_event_callbacks:
433439
try:
@@ -490,17 +496,6 @@ async def check_can_deactivate_user(
490496
)
491497
return True
492498

493-
async def _get_state_map_for_room(self, room_id: str) -> StateMap[EventBase]:
494-
"""Given a room ID, return the state events of that room.
495-
496-
Args:
497-
room_id: The ID of the room.
498-
499-
Returns:
500-
A dict mapping (event type, state key) to state event.
501-
"""
502-
return await self._storage_controllers.state.get_current_state(room_id)
503-
504499
async def on_profile_update(
505500
self, user_id: str, new_profile: ProfileInfo, by_admin: bool, deactivation: bool
506501
) -> None:

synapse/storage/controllers/state.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -562,10 +562,15 @@ async def get_current_state_deltas(
562562
@trace
563563
@tag_args
564564
async def get_current_state(
565-
self, room_id: str, state_filter: Optional[StateFilter] = None
565+
self,
566+
room_id: str,
567+
state_filter: Optional[StateFilter] = None,
568+
await_full_state: bool = True,
566569
) -> StateMap[EventBase]:
567570
"""Same as `get_current_state_ids` but also fetches the events"""
568-
state_map_ids = await self.get_current_state_ids(room_id, state_filter)
571+
state_map_ids = await self.get_current_state_ids(
572+
room_id, state_filter, await_full_state
573+
)
569574

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

0 commit comments

Comments
 (0)