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

Commit ba47fea

Browse files
authored
Allow multiple workers to write to receipts stream. (#16432)
Fixes #16417
1 parent e182dbb commit ba47fea

15 files changed

Lines changed: 604 additions & 89 deletions

File tree

changelog.d/16432.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow multiple workers to write to receipts stream.

synapse/config/workers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,9 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
358358
"Must only specify one instance to handle `account_data` messages."
359359
)
360360

361-
if len(self.writers.receipts) != 1:
361+
if len(self.writers.receipts) == 0:
362362
raise ConfigError(
363-
"Must only specify one instance to handle `receipts` messages."
363+
"Must specify at least one instance to handle `receipts` messages."
364364
)
365365

366366
if len(self.writers.events) == 0:

synapse/handlers/appservice.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
DeviceListUpdates,
4848
JsonDict,
4949
JsonMapping,
50+
MultiWriterStreamToken,
5051
RoomAlias,
5152
RoomStreamToken,
5253
StreamKeyType,
@@ -217,7 +218,7 @@ async def handle_room_events(events: Iterable[EventBase]) -> None:
217218
def notify_interested_services_ephemeral(
218219
self,
219220
stream_key: StreamKeyType,
220-
new_token: Union[int, RoomStreamToken],
221+
new_token: Union[int, RoomStreamToken, MultiWriterStreamToken],
221222
users: Collection[Union[str, UserID]],
222223
) -> None:
223224
"""
@@ -259,19 +260,6 @@ def notify_interested_services_ephemeral(
259260
):
260261
return
261262

262-
# Assert that new_token is an integer (and not a RoomStreamToken).
263-
# All of the supported streams that this function handles use an
264-
# integer to track progress (rather than a RoomStreamToken - a
265-
# vector clock implementation) as they don't support multiple
266-
# stream writers.
267-
#
268-
# As a result, we simply assert that new_token is an integer.
269-
# If we do end up needing to pass a RoomStreamToken down here
270-
# in the future, using RoomStreamToken.stream (the minimum stream
271-
# position) to convert to an ascending integer value should work.
272-
# Additional context: https://github.com/matrix-org/synapse/pull/11137
273-
assert isinstance(new_token, int)
274-
275263
# Ignore to-device messages if the feature flag is not enabled
276264
if (
277265
stream_key == StreamKeyType.TO_DEVICE
@@ -286,6 +274,9 @@ def notify_interested_services_ephemeral(
286274
):
287275
return
288276

277+
# We know we're not a `RoomStreamToken` at this point.
278+
assert not isinstance(new_token, RoomStreamToken)
279+
289280
# Check whether there are any appservices which have registered to receive
290281
# ephemeral events.
291282
#
@@ -327,7 +318,7 @@ async def _notify_interested_services_ephemeral(
327318
self,
328319
services: List[ApplicationService],
329320
stream_key: StreamKeyType,
330-
new_token: int,
321+
new_token: Union[int, MultiWriterStreamToken],
331322
users: Collection[Union[str, UserID]],
332323
) -> None:
333324
logger.debug("Checking interested services for %s", stream_key)
@@ -340,6 +331,7 @@ async def _notify_interested_services_ephemeral(
340331
#
341332
# Instead we simply grab the latest typing updates in _handle_typing
342333
# and, if they apply to this application service, send it off.
334+
assert isinstance(new_token, int)
343335
events = await self._handle_typing(service, new_token)
344336
if events:
345337
self.scheduler.enqueue_for_appservice(service, ephemeral=events)
@@ -350,15 +342,23 @@ async def _notify_interested_services_ephemeral(
350342
(service.id, stream_key)
351343
):
352344
if stream_key == StreamKeyType.RECEIPT:
345+
assert isinstance(new_token, MultiWriterStreamToken)
346+
347+
# We store appservice tokens as integers, so we ignore
348+
# the `instance_map` components and instead simply
349+
# follow the base stream position.
350+
new_token = MultiWriterStreamToken(stream=new_token.stream)
351+
353352
events = await self._handle_receipts(service, new_token)
354353
self.scheduler.enqueue_for_appservice(service, ephemeral=events)
355354

356355
# Persist the latest handled stream token for this appservice
357356
await self.store.set_appservice_stream_type_pos(
358-
service, "read_receipt", new_token
357+
service, "read_receipt", new_token.stream
359358
)
360359

361360
elif stream_key == StreamKeyType.PRESENCE:
361+
assert isinstance(new_token, int)
362362
events = await self._handle_presence(service, users, new_token)
363363
self.scheduler.enqueue_for_appservice(service, ephemeral=events)
364364

@@ -368,6 +368,7 @@ async def _notify_interested_services_ephemeral(
368368
)
369369

370370
elif stream_key == StreamKeyType.TO_DEVICE:
371+
assert isinstance(new_token, int)
371372
# Retrieve a list of to-device message events, as well as the
372373
# maximum stream token of the messages we were able to retrieve.
373374
to_device_messages = await self._get_to_device_messages(
@@ -383,6 +384,7 @@ async def _notify_interested_services_ephemeral(
383384
)
384385

385386
elif stream_key == StreamKeyType.DEVICE_LIST:
387+
assert isinstance(new_token, int)
386388
device_list_summary = await self._get_device_list_summary(
387389
service, new_token
388390
)
@@ -432,7 +434,7 @@ async def _handle_typing(
432434
return typing
433435

434436
async def _handle_receipts(
435-
self, service: ApplicationService, new_token: int
437+
self, service: ApplicationService, new_token: MultiWriterStreamToken
436438
) -> List[JsonMapping]:
437439
"""
438440
Return the latest read receipts that the given application service should receive.
@@ -455,15 +457,17 @@ async def _handle_receipts(
455457
from_key = await self.store.get_type_stream_id_for_appservice(
456458
service, "read_receipt"
457459
)
458-
if new_token is not None and new_token <= from_key:
460+
if new_token is not None and new_token.stream <= from_key:
459461
logger.debug(
460462
"Rejecting token lower than or equal to stored: %s" % (new_token,)
461463
)
462464
return []
463465

466+
from_token = MultiWriterStreamToken(stream=from_key)
467+
464468
receipts_source = self.event_sources.sources.receipt
465469
receipts, _ = await receipts_source.get_new_events_as(
466-
service=service, from_key=from_key, to_key=new_token
470+
service=service, from_key=from_token, to_key=new_token
467471
)
468472
return receipts
469473

synapse/handlers/initial_sync.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ async def _snapshot_all_rooms(
145145
joined_rooms = [r.room_id for r in room_list if r.membership == Membership.JOIN]
146146
receipt = await self.store.get_linearized_receipts_for_rooms(
147147
joined_rooms,
148-
to_key=int(now_token.receipt_key),
148+
to_key=now_token.receipt_key,
149149
)
150150

151151
receipt = ReceiptEventSource.filter_out_private_receipts(receipt, user_id)

synapse/handlers/receipts.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from synapse.types import (
2121
JsonDict,
2222
JsonMapping,
23+
MultiWriterStreamToken,
2324
ReadReceipt,
2425
StreamKeyType,
2526
UserID,
@@ -200,7 +201,7 @@ async def received_client_receipt(
200201
await self.federation_sender.send_read_receipt(receipt)
201202

202203

203-
class ReceiptEventSource(EventSource[int, JsonMapping]):
204+
class ReceiptEventSource(EventSource[MultiWriterStreamToken, JsonMapping]):
204205
def __init__(self, hs: "HomeServer"):
205206
self.store = hs.get_datastores().main
206207
self.config = hs.config
@@ -273,13 +274,12 @@ def filter_out_private_receipts(
273274
async def get_new_events(
274275
self,
275276
user: UserID,
276-
from_key: int,
277+
from_key: MultiWriterStreamToken,
277278
limit: int,
278279
room_ids: Iterable[str],
279280
is_guest: bool,
280281
explicit_room_id: Optional[str] = None,
281-
) -> Tuple[List[JsonMapping], int]:
282-
from_key = int(from_key)
282+
) -> Tuple[List[JsonMapping], MultiWriterStreamToken]:
283283
to_key = self.get_current_key()
284284

285285
if from_key == to_key:
@@ -296,8 +296,11 @@ async def get_new_events(
296296
return events, to_key
297297

298298
async def get_new_events_as(
299-
self, from_key: int, to_key: int, service: ApplicationService
300-
) -> Tuple[List[JsonMapping], int]:
299+
self,
300+
from_key: MultiWriterStreamToken,
301+
to_key: MultiWriterStreamToken,
302+
service: ApplicationService,
303+
) -> Tuple[List[JsonMapping], MultiWriterStreamToken]:
301304
"""Returns a set of new read receipt events that an appservice
302305
may be interested in.
303306
@@ -312,8 +315,6 @@ async def get_new_events_as(
312315
appservice may be interested in.
313316
* The current read receipt stream token.
314317
"""
315-
from_key = int(from_key)
316-
317318
if from_key == to_key:
318319
return [], to_key
319320

@@ -333,5 +334,5 @@ async def get_new_events_as(
333334

334335
return events, to_key
335336

336-
def get_current_key(self) -> int:
337+
def get_current_key(self) -> MultiWriterStreamToken:
337338
return self.store.get_max_receipt_stream_id()

synapse/handlers/sync.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
DeviceListUpdates,
5858
JsonDict,
5959
JsonMapping,
60+
MultiWriterStreamToken,
6061
MutableStateMap,
6162
Requester,
6263
RoomStreamToken,
@@ -477,7 +478,11 @@ async def ephemeral_by_room(
477478
event_copy = {k: v for (k, v) in event.items() if k != "room_id"}
478479
ephemeral_by_room.setdefault(room_id, []).append(event_copy)
479480

480-
receipt_key = since_token.receipt_key if since_token else 0
481+
receipt_key = (
482+
since_token.receipt_key
483+
if since_token
484+
else MultiWriterStreamToken(stream=0)
485+
)
481486

482487
receipt_source = self.event_sources.sources.receipt
483488
receipts, receipt_key = await receipt_source.get_new_events(

synapse/notifier.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
Dict,
2222
Iterable,
2323
List,
24+
Literal,
2425
Optional,
2526
Set,
2627
Tuple,
2728
TypeVar,
2829
Union,
30+
overload,
2931
)
3032

3133
import attr
@@ -44,6 +46,7 @@
4446
from synapse.streams.config import PaginationConfig
4547
from synapse.types import (
4648
JsonDict,
49+
MultiWriterStreamToken,
4750
PersistedEventPosition,
4851
RoomStreamToken,
4952
StrCollection,
@@ -127,7 +130,7 @@ def __init__(
127130
def notify(
128131
self,
129132
stream_key: StreamKeyType,
130-
stream_id: Union[int, RoomStreamToken],
133+
stream_id: Union[int, RoomStreamToken, MultiWriterStreamToken],
131134
time_now_ms: int,
132135
) -> None:
133136
"""Notify any listeners for this user of a new event from an
@@ -452,10 +455,48 @@ def _notify_pusher_pool(self, max_room_stream_token: RoomStreamToken) -> None:
452455
except Exception:
453456
logger.exception("Error pusher pool of event")
454457

458+
@overload
459+
def on_new_event(
460+
self,
461+
stream_key: Literal[StreamKeyType.ROOM],
462+
new_token: RoomStreamToken,
463+
users: Optional[Collection[Union[str, UserID]]] = None,
464+
rooms: Optional[StrCollection] = None,
465+
) -> None:
466+
...
467+
468+
@overload
469+
def on_new_event(
470+
self,
471+
stream_key: Literal[StreamKeyType.RECEIPT],
472+
new_token: MultiWriterStreamToken,
473+
users: Optional[Collection[Union[str, UserID]]] = None,
474+
rooms: Optional[StrCollection] = None,
475+
) -> None:
476+
...
477+
478+
@overload
479+
def on_new_event(
480+
self,
481+
stream_key: Literal[
482+
StreamKeyType.ACCOUNT_DATA,
483+
StreamKeyType.DEVICE_LIST,
484+
StreamKeyType.PRESENCE,
485+
StreamKeyType.PUSH_RULES,
486+
StreamKeyType.TO_DEVICE,
487+
StreamKeyType.TYPING,
488+
StreamKeyType.UN_PARTIAL_STATED_ROOMS,
489+
],
490+
new_token: int,
491+
users: Optional[Collection[Union[str, UserID]]] = None,
492+
rooms: Optional[StrCollection] = None,
493+
) -> None:
494+
...
495+
455496
def on_new_event(
456497
self,
457498
stream_key: StreamKeyType,
458-
new_token: Union[int, RoomStreamToken],
499+
new_token: Union[int, RoomStreamToken, MultiWriterStreamToken],
459500
users: Optional[Collection[Union[str, UserID]]] = None,
460501
rooms: Optional[StrCollection] = None,
461502
) -> None:

synapse/replication/tcp/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ async def on_rdata(
126126
StreamKeyType.ACCOUNT_DATA, token, users=[row.user_id for row in rows]
127127
)
128128
elif stream_name == ReceiptsStream.NAME:
129+
new_token = self.store.get_max_receipt_stream_id()
129130
self.notifier.on_new_event(
130-
StreamKeyType.RECEIPT, token, rooms=[row.room_id for row in rows]
131+
StreamKeyType.RECEIPT, new_token, rooms=[row.room_id for row in rows]
131132
)
132133
await self._pusher_pool.on_new_receipts({row.user_id for row in rows})
133134
elif stream_name == ToDeviceStream.NAME:

0 commit comments

Comments
 (0)