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

Commit 6bf81a7

Browse files
authored
Bundle aggregations outside of the serialization method. (#11612)
This makes the serialization of events synchronous (and it no longer access the database), but we must manually calculate and provide the bundled aggregations. Overall this should cause no change in behavior, but is prep work for other improvements.
1 parent 6c68e87 commit 6bf81a7

File tree

17 files changed

+249
-156
lines changed

17 files changed

+249
-156
lines changed

changelog.d/11612.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Avoid database access in the JSON serialization process.

synapse/events/utils.py

+37-89
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,18 @@
1414
# limitations under the License.
1515
import collections.abc
1616
import re
17-
from typing import (
18-
TYPE_CHECKING,
19-
Any,
20-
Callable,
21-
Dict,
22-
Iterable,
23-
List,
24-
Mapping,
25-
Optional,
26-
Union,
27-
)
17+
from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Union
2818

2919
from frozendict import frozendict
3020

3121
from synapse.api.constants import EventContentFields, EventTypes, RelationTypes
3222
from synapse.api.errors import Codes, SynapseError
3323
from synapse.api.room_versions import RoomVersion
3424
from synapse.types import JsonDict
35-
from synapse.util.async_helpers import yieldable_gather_results
3625
from synapse.util.frozenutils import unfreeze
3726

3827
from . import EventBase
3928

40-
if TYPE_CHECKING:
41-
from synapse.server import HomeServer
42-
4329
# Split strings on "." but not "\." This uses a negative lookbehind assertion for '\'
4430
# (?<!stuff) matches if the current position in the string is not preceded
4531
# by a match for 'stuff'.
@@ -385,17 +371,12 @@ class EventClientSerializer:
385371
clients.
386372
"""
387373

388-
def __init__(self, hs: "HomeServer"):
389-
self.store = hs.get_datastore()
390-
self._msc1849_enabled = hs.config.experimental.msc1849_enabled
391-
self._msc3440_enabled = hs.config.experimental.msc3440_enabled
392-
393-
async def serialize_event(
374+
def serialize_event(
394375
self,
395376
event: Union[JsonDict, EventBase],
396377
time_now: int,
397378
*,
398-
bundle_aggregations: bool = False,
379+
bundle_aggregations: Optional[Dict[str, JsonDict]] = None,
399380
**kwargs: Any,
400381
) -> JsonDict:
401382
"""Serializes a single event.
@@ -418,66 +399,41 @@ async def serialize_event(
418399
serialized_event = serialize_event(event, time_now, **kwargs)
419400

420401
# Check if there are any bundled aggregations to include with the event.
421-
#
422-
# Do not bundle aggregations if any of the following at true:
423-
#
424-
# * Support is disabled via the configuration or the caller.
425-
# * The event is a state event.
426-
# * The event has been redacted.
427-
if (
428-
self._msc1849_enabled
429-
and bundle_aggregations
430-
and not event.is_state()
431-
and not event.internal_metadata.is_redacted()
432-
):
433-
await self._injected_bundled_aggregations(event, time_now, serialized_event)
402+
if bundle_aggregations:
403+
event_aggregations = bundle_aggregations.get(event.event_id)
404+
if event_aggregations:
405+
self._injected_bundled_aggregations(
406+
event,
407+
time_now,
408+
bundle_aggregations[event.event_id],
409+
serialized_event,
410+
)
434411

435412
return serialized_event
436413

437-
async def _injected_bundled_aggregations(
438-
self, event: EventBase, time_now: int, serialized_event: JsonDict
414+
def _injected_bundled_aggregations(
415+
self,
416+
event: EventBase,
417+
time_now: int,
418+
aggregations: JsonDict,
419+
serialized_event: JsonDict,
439420
) -> None:
440421
"""Potentially injects bundled aggregations into the unsigned portion of the serialized event.
441422
442423
Args:
443424
event: The event being serialized.
444425
time_now: The current time in milliseconds
426+
aggregations: The bundled aggregation to serialize.
445427
serialized_event: The serialized event which may be modified.
446428
447429
"""
448-
# Do not bundle aggregations for an event which represents an edit or an
449-
# annotation. It does not make sense for them to have related events.
450-
relates_to = event.content.get("m.relates_to")
451-
if isinstance(relates_to, (dict, frozendict)):
452-
relation_type = relates_to.get("rel_type")
453-
if relation_type in (RelationTypes.ANNOTATION, RelationTypes.REPLACE):
454-
return
455-
456-
event_id = event.event_id
457-
room_id = event.room_id
458-
459-
# The bundled aggregations to include.
460-
aggregations = {}
461-
462-
annotations = await self.store.get_aggregation_groups_for_event(
463-
event_id, room_id
464-
)
465-
if annotations.chunk:
466-
aggregations[RelationTypes.ANNOTATION] = annotations.to_dict()
430+
# Make a copy in-case the object is cached.
431+
aggregations = aggregations.copy()
467432

468-
references = await self.store.get_relations_for_event(
469-
event_id, room_id, RelationTypes.REFERENCE, direction="f"
470-
)
471-
if references.chunk:
472-
aggregations[RelationTypes.REFERENCE] = references.to_dict()
473-
474-
edit = None
475-
if event.type == EventTypes.Message:
476-
edit = await self.store.get_applicable_edit(event_id, room_id)
477-
478-
if edit:
433+
if RelationTypes.REPLACE in aggregations:
479434
# If there is an edit replace the content, preserving existing
480435
# relations.
436+
edit = aggregations[RelationTypes.REPLACE]
481437

482438
# Ensure we take copies of the edit content, otherwise we risk modifying
483439
# the original event.
@@ -502,27 +458,19 @@ async def _injected_bundled_aggregations(
502458
}
503459

504460
# If this event is the start of a thread, include a summary of the replies.
505-
if self._msc3440_enabled:
506-
(
507-
thread_count,
508-
latest_thread_event,
509-
) = await self.store.get_thread_summary(event_id, room_id)
510-
if latest_thread_event:
511-
aggregations[RelationTypes.THREAD] = {
512-
# Don't bundle aggregations as this could recurse forever.
513-
"latest_event": await self.serialize_event(
514-
latest_thread_event, time_now, bundle_aggregations=False
515-
),
516-
"count": thread_count,
517-
}
518-
519-
# If any bundled aggregations were found, include them.
520-
if aggregations:
521-
serialized_event["unsigned"].setdefault("m.relations", {}).update(
522-
aggregations
461+
if RelationTypes.THREAD in aggregations:
462+
# Serialize the latest thread event.
463+
latest_thread_event = aggregations[RelationTypes.THREAD]["latest_event"]
464+
465+
# Don't bundle aggregations as this could recurse forever.
466+
aggregations[RelationTypes.THREAD]["latest_event"] = self.serialize_event(
467+
latest_thread_event, time_now, bundle_aggregations=None
523468
)
524469

525-
async def serialize_events(
470+
# Include the bundled aggregations in the event.
471+
serialized_event["unsigned"].setdefault("m.relations", {}).update(aggregations)
472+
473+
def serialize_events(
526474
self, events: Iterable[Union[JsonDict, EventBase]], time_now: int, **kwargs: Any
527475
) -> List[JsonDict]:
528476
"""Serializes multiple events.
@@ -535,9 +483,9 @@ async def serialize_events(
535483
Returns:
536484
The list of serialized events
537485
"""
538-
return await yieldable_gather_results(
539-
self.serialize_event, events, time_now=time_now, **kwargs
540-
)
486+
return [
487+
self.serialize_event(event, time_now=time_now, **kwargs) for event in events
488+
]
541489

542490

543491
def copy_power_levels_contents(

synapse/handlers/events.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ async def get_stream(
119119

120120
events.extend(to_add)
121121

122-
chunks = await self._event_serializer.serialize_events(
122+
chunks = self._event_serializer.serialize_events(
123123
events,
124124
time_now,
125125
as_client_event=as_client_event,

synapse/handlers/initial_sync.py

+7-9
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ async def handle_room(event: RoomsForUser) -> None:
170170
d["inviter"] = event.sender
171171

172172
invite_event = await self.store.get_event(event.event_id)
173-
d["invite"] = await self._event_serializer.serialize_event(
173+
d["invite"] = self._event_serializer.serialize_event(
174174
invite_event,
175175
time_now,
176176
as_client_event=as_client_event,
@@ -222,7 +222,7 @@ async def handle_room(event: RoomsForUser) -> None:
222222

223223
d["messages"] = {
224224
"chunk": (
225-
await self._event_serializer.serialize_events(
225+
self._event_serializer.serialize_events(
226226
messages,
227227
time_now=time_now,
228228
as_client_event=as_client_event,
@@ -232,7 +232,7 @@ async def handle_room(event: RoomsForUser) -> None:
232232
"end": await end_token.to_string(self.store),
233233
}
234234

235-
d["state"] = await self._event_serializer.serialize_events(
235+
d["state"] = self._event_serializer.serialize_events(
236236
current_state.values(),
237237
time_now=time_now,
238238
as_client_event=as_client_event,
@@ -376,16 +376,14 @@ async def _room_initial_sync_parted(
376376
"messages": {
377377
"chunk": (
378378
# Don't bundle aggregations as this is a deprecated API.
379-
await self._event_serializer.serialize_events(messages, time_now)
379+
self._event_serializer.serialize_events(messages, time_now)
380380
),
381381
"start": await start_token.to_string(self.store),
382382
"end": await end_token.to_string(self.store),
383383
},
384384
"state": (
385385
# Don't bundle aggregations as this is a deprecated API.
386-
await self._event_serializer.serialize_events(
387-
room_state.values(), time_now
388-
)
386+
self._event_serializer.serialize_events(room_state.values(), time_now)
389387
),
390388
"presence": [],
391389
"receipts": [],
@@ -404,7 +402,7 @@ async def _room_initial_sync_joined(
404402
# TODO: These concurrently
405403
time_now = self.clock.time_msec()
406404
# Don't bundle aggregations as this is a deprecated API.
407-
state = await self._event_serializer.serialize_events(
405+
state = self._event_serializer.serialize_events(
408406
current_state.values(), time_now
409407
)
410408

@@ -480,7 +478,7 @@ async def get_receipts() -> List[JsonDict]:
480478
"messages": {
481479
"chunk": (
482480
# Don't bundle aggregations as this is a deprecated API.
483-
await self._event_serializer.serialize_events(messages, time_now)
481+
self._event_serializer.serialize_events(messages, time_now)
484482
),
485483
"start": await start_token.to_string(self.store),
486484
"end": await end_token.to_string(self.store),

synapse/handlers/message.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ async def get_state_events(
246246
room_state = room_state_events[membership_event_id]
247247

248248
now = self.clock.time_msec()
249-
events = await self._event_serializer.serialize_events(room_state.values(), now)
249+
events = self._event_serializer.serialize_events(room_state.values(), now)
250250
return events
251251

252252
async def get_joined_members(self, requester: Requester, room_id: str) -> dict:

synapse/handlers/pagination.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -537,14 +537,16 @@ async def get_messages(
537537
state_dict = await self.store.get_events(list(state_ids.values()))
538538
state = state_dict.values()
539539

540+
aggregations = await self.store.get_bundled_aggregations(events)
541+
540542
time_now = self.clock.time_msec()
541543

542544
chunk = {
543545
"chunk": (
544-
await self._event_serializer.serialize_events(
546+
self._event_serializer.serialize_events(
545547
events,
546548
time_now,
547-
bundle_aggregations=True,
549+
bundle_aggregations=aggregations,
548550
as_client_event=as_client_event,
549551
)
550552
),
@@ -553,7 +555,7 @@ async def get_messages(
553555
}
554556

555557
if state:
556-
chunk["state"] = await self._event_serializer.serialize_events(
558+
chunk["state"] = self._event_serializer.serialize_events(
557559
state, time_now, as_client_event=as_client_event
558560
)
559561

synapse/handlers/room.py

+10
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,16 @@ async def filter_evts(events: List[EventBase]) -> List[EventBase]:
11811181
# `filtered` rather than the event we retrieved from the datastore.
11821182
results["event"] = filtered[0]
11831183

1184+
# Fetch the aggregations.
1185+
aggregations = await self.store.get_bundled_aggregations([results["event"]])
1186+
aggregations.update(
1187+
await self.store.get_bundled_aggregations(results["events_before"])
1188+
)
1189+
aggregations.update(
1190+
await self.store.get_bundled_aggregations(results["events_after"])
1191+
)
1192+
results["aggregations"] = aggregations
1193+
11841194
if results["events_after"]:
11851195
last_event_id = results["events_after"][-1].event_id
11861196
else:

synapse/handlers/search.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -420,10 +420,10 @@ async def search(
420420
time_now = self.clock.time_msec()
421421

422422
for context in contexts.values():
423-
context["events_before"] = await self._event_serializer.serialize_events(
423+
context["events_before"] = self._event_serializer.serialize_events(
424424
context["events_before"], time_now
425425
)
426-
context["events_after"] = await self._event_serializer.serialize_events(
426+
context["events_after"] = self._event_serializer.serialize_events(
427427
context["events_after"], time_now
428428
)
429429

@@ -441,9 +441,7 @@ async def search(
441441
results.append(
442442
{
443443
"rank": rank_map[e.event_id],
444-
"result": (
445-
await self._event_serializer.serialize_event(e, time_now)
446-
),
444+
"result": self._event_serializer.serialize_event(e, time_now),
447445
"context": contexts.get(e.event_id, {}),
448446
}
449447
)
@@ -457,7 +455,7 @@ async def search(
457455
if state_results:
458456
s = {}
459457
for room_id, state_events in state_results.items():
460-
s[room_id] = await self._event_serializer.serialize_events(
458+
s[room_id] = self._event_serializer.serialize_events(
461459
state_events, time_now
462460
)
463461

synapse/rest/admin/rooms.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ async def on_GET(
424424
event_ids = await self.store.get_current_state_ids(room_id)
425425
events = await self.store.get_events(event_ids.values())
426426
now = self.clock.time_msec()
427-
room_state = await self._event_serializer.serialize_events(events.values(), now)
427+
room_state = self._event_serializer.serialize_events(events.values(), now)
428428
ret = {"state": room_state}
429429

430430
return HTTPStatus.OK, ret
@@ -744,22 +744,22 @@ async def on_GET(
744744
)
745745

746746
time_now = self.clock.time_msec()
747-
results["events_before"] = await self._event_serializer.serialize_events(
747+
results["events_before"] = self._event_serializer.serialize_events(
748748
results["events_before"],
749749
time_now,
750-
bundle_aggregations=True,
750+
bundle_aggregations=results["aggregations"],
751751
)
752-
results["event"] = await self._event_serializer.serialize_event(
752+
results["event"] = self._event_serializer.serialize_event(
753753
results["event"],
754754
time_now,
755-
bundle_aggregations=True,
755+
bundle_aggregations=results["aggregations"],
756756
)
757-
results["events_after"] = await self._event_serializer.serialize_events(
757+
results["events_after"] = self._event_serializer.serialize_events(
758758
results["events_after"],
759759
time_now,
760-
bundle_aggregations=True,
760+
bundle_aggregations=results["aggregations"],
761761
)
762-
results["state"] = await self._event_serializer.serialize_events(
762+
results["state"] = self._event_serializer.serialize_events(
763763
results["state"], time_now
764764
)
765765

0 commit comments

Comments
 (0)