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

Commit 2b19e69

Browse files
Stop sending receipts to large rooms
1 parent 16e080c commit 2b19e69

File tree

4 files changed

+68
-1
lines changed

4 files changed

+68
-1
lines changed

synapse/api/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,9 @@ class ReceiptTypes:
277277
BEEPER_INBOX_DONE: Final = "com.beeper.inbox.done"
278278

279279

280+
RECEIPTS_MAX_ROOM_SIZE = 100
281+
282+
280283
class PublicRoomsFilterFields:
281284
"""Fields in the search filter for `/publicRooms` that we understand.
282285

synapse/handlers/receipts.py

Lines changed: 9 additions & 1 deletion
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 RECEIPTS_MAX_ROOM_SIZE, EduTypes, ReceiptTypes
1818
from synapse.appservice import ApplicationService
1919
from synapse.streams import EventSource
2020
from synapse.types import (
@@ -116,6 +116,14 @@ 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 i, r in enumerate(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+
receipts[i] = r.make_private_copy()
126+
119127
for receipt in receipts:
120128
res = await self.store.insert_receipt(
121129
receipt.room_id,

synapse/types/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
15+
from __future__ import annotations
16+
1517
import abc
1618
import re
1719
import string
@@ -52,6 +54,7 @@
5254
IReactorTime,
5355
)
5456

57+
from synapse.api.constants import ReceiptTypes
5558
from synapse.api.errors import Codes, SynapseError
5659
from synapse.util.cancellation import cancellable
5760
from synapse.util.stringutils import parse_and_validate_server_name
@@ -853,6 +856,19 @@ class ReadReceipt:
853856
thread_id: Optional[str]
854857
data: JsonDict
855858

859+
# Beeper: we don't want to alter the frozen attr, but
860+
# do occasionally need to make a private copy to
861+
# avoid traffic to large rooms.
862+
def make_private_copy(self) -> ReadReceipt:
863+
return ReadReceipt(
864+
room_id=self.room_id,
865+
user_id=self.user_id,
866+
event_ids=self.event_ids,
867+
thread_id=self.thread_id,
868+
data=self.data,
869+
receipt_type=ReceiptTypes.READ_PRIVATE,
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
@@ -22,6 +22,7 @@
2222

2323
import synapse.rest.admin
2424
from synapse.api.constants import (
25+
RECEIPTS_MAX_ROOM_SIZE,
2526
EduTypes,
2627
EventContentFields,
2728
EventTypes,
@@ -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)