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

Commit 2029b2e

Browse files
Stop sending receipts to large rooms
1 parent 16e080c commit 2029b2e

File tree

4 files changed

+78
-3
lines changed

4 files changed

+78
-3
lines changed

synapse/api/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ class ReceiptTypes:
276276
FULLY_READ: Final = "m.fully_read"
277277
BEEPER_INBOX_DONE: Final = "com.beeper.inbox.done"
278278

279+
RECEIPTS_MAX_ROOM_SIZE = 100
279280

280281
class PublicRoomsFilterFields:
281282
"""Fields in the search filter for `/publicRooms` that we understand.

synapse/handlers/receipts.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import logging
1515
from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple
1616

17-
from synapse.api.constants import EduTypes, ReceiptTypes
17+
from synapse.api.constants import EduTypes, RECEIPTS_MAX_ROOM_SIZE, ReceiptTypes
1818
from synapse.appservice import ApplicationService
1919
from synapse.streams import EventSource
2020
from synapse.types import (
@@ -116,6 +116,15 @@ async def _handle_new_receipts(self, receipts: List[ReadReceipt]) -> bool:
116116
min_batch_id: Optional[int] = None
117117
max_batch_id: Optional[int] = None
118118

119+
# Beeper: we don't want to send read receipts to large rooms,
120+
# so we convert messages to private, that are over RECEIPT_MAX_ROOM_SIZE.
121+
for r in receipts:
122+
if r.receipt_type != ReceiptTypes.READ_PRIVATE:
123+
num_users = await self.store.get_number_joined_users_in_room(r.room_id)
124+
if num_users > RECEIPTS_MAX_ROOM_SIZE:
125+
r = r.copy_with_modification(receipt_type=ReceiptTypes.READ_PRIVATE)
126+
127+
119128
for receipt in receipts:
120129
res = await self.store.insert_receipt(
121130
receipt.room_id,
@@ -179,8 +188,17 @@ async def received_client_receipt(
179188
if not is_new:
180189
return
181190

182-
if self.federation_sender and receipt_type != ReceiptTypes.READ_PRIVATE:
183-
await self.federation_sender.send_read_receipt(receipt)
191+
if receipt_type == ReceiptTypes.READ_PRIVATE:
192+
return
193+
194+
if not self.federation_sender:
195+
return
196+
197+
num_users = await self.store.get_number_joined_users_in_room(room_id)
198+
if num_users > RECEIPTS_MAX_ROOM_SIZE:
199+
return
200+
201+
await self.federation_sender.send_read_receipt(receipt)
184202

185203

186204
class ReceiptEventSource(EventSource[int, JsonDict]):

synapse/types/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,22 @@ class ReadReceipt:
853853
thread_id: Optional[str]
854854
data: JsonDict
855855

856+
# Beeper: we don't want to alter the frozen attr, but
857+
# do occasionally need to make a private copy to
858+
# avoid traffic to large rooms.
859+
def copy_with_modification(self, **mods):
860+
new_attrs = {
861+
'room_id': self.room_id,
862+
'receipt_type': self.receipt_type,
863+
'user_id': self.user_id,
864+
'event_ids': self.event_ids,
865+
'thread_id': self.thread_id,
866+
'data': self.data,
867+
**mods
868+
}
869+
return ReadReceipt(**new_attrs)
870+
871+
856872

857873
@attr.s(slots=True, frozen=True, auto_attribs=True)
858874
class DeviceListUpdates:

tests/rest/client/test_sync.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
EventContentFields,
2727
EventTypes,
2828
ReceiptTypes,
29+
RECEIPTS_MAX_ROOM_SIZE,
2930
RelationTypes,
3031
)
3132
from synapse.rest.client import devices, knock, login, read_marker, receipts, room, sync
@@ -382,6 +383,7 @@ class ReadReceiptsTestCase(unittest.HomeserverTestCase):
382383
servlets = [
383384
synapse.rest.admin.register_servlets,
384385
login.register_servlets,
386+
read_marker.register_servlets,
385387
receipts.register_servlets,
386388
room.register_servlets,
387389
sync.register_servlets,
@@ -499,6 +501,35 @@ def test_read_receipt_with_empty_body_is_rejected(self) -> None:
499501
self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST)
500502
self.assertEqual(channel.json_body["errcode"], "M_NOT_JSON", channel.json_body)
501503

504+
def test_read_receipt_not_sent_to_large_rooms(self) -> None:
505+
# Beeper: we don't send read receipts on rooms with more
506+
# users than # RECEIPTS_MAX_ROOM_SIZE
507+
for i in range(RECEIPTS_MAX_ROOM_SIZE):
508+
user = self.register_user(f"user_{i}", f"secure_password_{i}")
509+
tok = self.login(f"user_{i}", f"secure_password_{i}")
510+
self.helper.join(room=self.room_id, user=user, tok=tok)
511+
512+
res = self.helper.send(
513+
self.room_id, body="woah, this is a big room!", tok=self.tok
514+
)
515+
516+
for receipt_type in (
517+
ReceiptTypes.FULLY_READ,
518+
ReceiptTypes.READ_PRIVATE,
519+
# ReceiptTypes.READ
520+
):
521+
# Send a read receipt
522+
channel = self.make_request(
523+
"POST",
524+
f"/rooms/{self.room_id}/receipt/{receipt_type}/{res['event_id']}",
525+
{},
526+
access_token=self.tok2,
527+
)
528+
self.assertEqual(channel.code, 200)
529+
530+
# Test that we didn't get a read receipt.
531+
self.assertIsNone(self._get_read_receipt())
532+
502533
def _get_read_receipt(self) -> Optional[JsonDict]:
503534
"""Syncs and returns the read receipt."""
504535

@@ -728,6 +759,15 @@ def test_unread_counts(self) -> None:
728759
self.assertEqual(channel.code, 200, channel.json_body)
729760
self._check_unread_count(0)
730761

762+
def test_large_rooms_dont_alter_unread_count_behaviour(self) -> None:
763+
# Beeper: create a lot of users and join them to the room
764+
for i in range(RECEIPTS_MAX_ROOM_SIZE):
765+
user = self.register_user(f"user_{i}", f"secure_password_{i}")
766+
tok = self.login(f"user_{i}", f"secure_password_{i}")
767+
self.helper.join(self.room_id, user=user, tok=tok)
768+
769+
self.test_unread_counts()
770+
731771
# We test for all three receipt types that influence notification counts
732772
@parameterized.expand(
733773
[

0 commit comments

Comments
 (0)