summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--synapse/handlers/sliding_sync.py32
-rw-r--r--tests/rest/client/test_sync.py70
2 files changed, 100 insertions, 2 deletions
diff --git a/synapse/handlers/sliding_sync.py b/synapse/handlers/sliding_sync.py
index d94160c59b..a88c66c4f7 100644
--- a/synapse/handlers/sliding_sync.py
+++ b/synapse/handlers/sliding_sync.py
@@ -653,6 +653,13 @@ class SlidingSyncHandler:
                 else:
                     assert_never(status.status)
 
+                if status.timeline_limit is not None and (
+                    status.timeline_limit < relevant_room_map[room_id].timeline_limit
+                ):
+                    # If the timeline limit has increased we want to send down
+                    # more historic events (even if nothing has since changed).
+                    rooms_should_send.add(room_id)
+
             # We only need to check for new events since any state changes
             # will also come down as new events.
             rooms_that_have_updates = self.store.get_rooms_that_might_have_updates(
@@ -1476,7 +1483,12 @@ class SlidingSyncHandler:
         #  - When users `newly_joined`
         #  - For an incremental sync where we haven't sent it down this
         #    connection before
+        #
+        # We also decide if we should ignore the timeline bound or not. This is
+        # to handle the case where the client has requested more historical
+        # messages in the room by increasing the timeline limit.
         from_bound = None
+        ignore_timeline_bound = False
         initial = True
         if from_token and not room_membership_for_user_at_to_token.newly_joined:
             room_status = await self.connection_store.have_sent_room(
@@ -1484,6 +1496,7 @@ class SlidingSyncHandler:
                 connection_token=from_token.connection_position,
                 room_id=room_id,
             )
+
             if room_status.status == HaveSentRoomFlag.LIVE:
                 from_bound = from_token.stream_token.room_key
                 initial = False
@@ -1497,9 +1510,24 @@ class SlidingSyncHandler:
             else:
                 assert_never(room_status.status)
 
+            if room_status.timeline_limit is not None and (
+                room_status.timeline_limit < room_sync_config.timeline_limit
+            ):
+                # If the timeline limit has been increased since previous
+                # requests then we treat it as request for more events. We do
+                # this by sending down a `limited` sync, ignoring the from
+                # bound.
+                ignore_timeline_bound = True
+
             log_kv({"sliding_sync.room_status": room_status})
 
-        log_kv({"sliding_sync.from_bound": from_bound, "sliding_sync.initial": initial})
+        log_kv(
+            {
+                "sliding_sync.from_bound": from_bound,
+                "sliding_sync.ignore_timeline_bound": ignore_timeline_bound,
+                "sliding_sync.initial": initial,
+            }
+        )
 
         # Assemble the list of timeline events
         #
@@ -1542,7 +1570,7 @@ class SlidingSyncHandler:
                 # (from newer to older events) starting at to_bound.
                 # This ensures we fill the `limit` with the newest events first,
                 from_key=to_bound,
-                to_key=from_bound,
+                to_key=from_bound if not ignore_timeline_bound else None,
                 direction=Direction.BACKWARDS,
                 # We add one so we can determine if there are enough events to saturate
                 # the limit or not (see `limited`)
diff --git a/tests/rest/client/test_sync.py b/tests/rest/client/test_sync.py
index 8d5fd30fdd..bb723927c4 100644
--- a/tests/rest/client/test_sync.py
+++ b/tests/rest/client/test_sync.py
@@ -4940,6 +4940,76 @@ class SlidingSyncTestCase(SlidingSyncBase):
         response_body, _ = self.do_sync(sync_body, tok=user1_tok)
         self.assertEqual(response_body["rooms"][room_id1]["initial"], True)
 
+    def test_increasing_timeline_range_sends_more_messages(self) -> None:
+        """
+        Test that increasing the timeline limit via room subscriptions sends the
+        room down with more messages in a limited sync.
+        """
+
+        user1_id = self.register_user("user1", "pass")
+        user1_tok = self.login(user1_id, "pass")
+
+        room_id1 = self.helper.create_room_as(user1_id, tok=user1_tok)
+
+        sync_body = {
+            "lists": {
+                "foo-list": {
+                    "ranges": [[0, 1]],
+                    "required_state": [[EventTypes.Create, ""]],
+                    "timeline_limit": 1,
+                }
+            }
+        }
+
+        message_events = []
+        for _ in range(10):
+            resp = self.helper.send(room_id1, "msg", tok=user1_tok)
+            message_events.append(resp["event_id"])
+
+        # Make the first Sliding Sync request
+        response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
+        room_response = response_body["rooms"][room_id1]
+
+        self.assertEqual(room_response["initial"], True)
+        self.assertEqual(room_response["limited"], True)
+
+        # We only expect the last message at first
+        self.assertEqual(
+            [event["event_id"] for event in room_response["timeline"]],
+            message_events[-1:],
+            room_response["timeline"],
+        )
+
+        # We also expect to get the create event state.
+        self.assertEqual(
+            [event["type"] for event in room_response["required_state"]],
+            [EventTypes.Create],
+        )
+
+        # Now do another request with a room subscription with an increased timeline limit
+        sync_body["room_subscriptions"] = {
+            room_id1: {
+                "required_state": [],
+                "timeline_limit": 10,
+            }
+        }
+
+        response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok)
+        room_response = response_body["rooms"][room_id1]
+
+        self.assertNotIn("initial", room_response)
+        self.assertEqual(room_response["limited"], True)
+
+        # Now we expect all the messages
+        self.assertEqual(
+            [event["event_id"] for event in room_response["timeline"]],
+            message_events,
+            room_response["timeline"],
+        )
+
+        # We don't expect to get the room create down, as nothing has changed.
+        self.assertNotIn("required_state", room_response)
+
 
 class SlidingSyncToDeviceExtensionTestCase(SlidingSyncBase):
     """Tests for the to-device sliding sync extension"""