Skip to content

Commit 2691d0b

Browse files
bnjbvrerikjohnston
andauthored
Send a SSS response immediately if the config has changed and there are new results to sync (#19714)
This fixes the bug described in #19713 (and double-checked against the SDK integration test, which now passes with this change). A sync response must be returned immediately if a room subscription configuration change caused a new non-empty response (checked with `if response` in the code) to be produced. Fixes #19713. Fixes #18844. --------- Co-authored-by: Erik Johnston <erik@matrix.org>
1 parent 213b5a0 commit 2691d0b

4 files changed

Lines changed: 156 additions & 28 deletions

File tree

changelog.d/19714.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Have SSS return a new response immediately if a room subscription have changed and produced a new response.

synapse/handlers/sliding_sync/__init__.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -167,34 +167,38 @@ async def wait_for_sync_for_user(
167167
timeout_ms -= after_wait_ts - before_wait_ts
168168
timeout_ms = max(timeout_ms, 0)
169169

170-
# We're going to respond immediately if the timeout is 0 or if this is an
171-
# initial sync (without a `from_token`) so we can avoid calling
172-
# `notifier.wait_for_events()`.
173-
if timeout_ms == 0 or from_token is None:
174-
now_token = self.event_sources.get_current_token()
175-
result = await self.current_sync_for_user(
170+
# Compute a response immediately. We always need to do this before
171+
# waiting for new data (unlike in /v3/sync), as the request config might
172+
# have changed (e.g. new room subscriptions, etc).
173+
now_token = self.event_sources.get_current_token()
174+
result = await self.current_sync_for_user(
175+
sync_config,
176+
from_token=from_token,
177+
to_token=now_token,
178+
)
179+
180+
# Return immediately if we have a result, the timeout is 0, or this is
181+
# an initial sync.
182+
if result or timeout_ms == 0 or from_token is None:
183+
return result, did_wait
184+
185+
# Otherwise, we wait for something to happen and report it to the user.
186+
async def current_sync_callback(
187+
before_token: StreamToken, after_token: StreamToken
188+
) -> SlidingSyncResult:
189+
return await self.current_sync_for_user(
176190
sync_config,
177191
from_token=from_token,
178-
to_token=now_token,
192+
to_token=after_token,
179193
)
180-
else:
181-
# Otherwise, we wait for something to happen and report it to the user.
182-
async def current_sync_callback(
183-
before_token: StreamToken, after_token: StreamToken
184-
) -> SlidingSyncResult:
185-
return await self.current_sync_for_user(
186-
sync_config,
187-
from_token=from_token,
188-
to_token=after_token,
189-
)
190194

191-
result = await self.notifier.wait_for_events(
192-
sync_config.user.to_string(),
193-
timeout_ms,
194-
current_sync_callback,
195-
from_token=from_token.stream_token,
196-
)
197-
did_wait = True
195+
result = await self.notifier.wait_for_events(
196+
sync_config.user.to_string(),
197+
timeout_ms,
198+
current_sync_callback,
199+
from_token=now_token,
200+
)
201+
did_wait = True
198202

199203
return result, did_wait
200204

synapse/handlers/sliding_sync/room_lists.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -852,11 +852,15 @@ async def _filter_relevant_rooms_to_send(
852852
previous_connection_state.room_configs.get(room_id)
853853
)
854854
if prev_room_sync_config is not None:
855-
# Always include rooms whose timeline limit has increased.
856-
# (see the "XXX: Odd behavior" described below)
855+
# Always include rooms whose effective config has
856+
# expanded. This covers timeline-limit increases and
857+
# required-state additions introduced by room
858+
# subscriptions overriding list-derived params.
857859
if (
858-
prev_room_sync_config.timeline_limit
859-
< room_config.timeline_limit
860+
prev_room_sync_config.combine_room_sync_config(
861+
room_config
862+
)
863+
!= prev_room_sync_config
860864
):
861865
rooms_should_send.add(room_id)
862866
continue

tests/rest/client/sliding_sync/test_room_subscriptions.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from synapse.api.constants import EventTypes, HistoryVisibility
2323
from synapse.rest.client import login, room, sync
2424
from synapse.server import HomeServer
25+
from synapse.types import JsonDict
2526
from synapse.util.clock import Clock
2627

2728
from tests.rest.client.sliding_sync.test_sliding_sync import SlidingSyncBase
@@ -126,6 +127,124 @@ def test_room_subscriptions_with_join_membership(self) -> None:
126127
response_body["rooms"][room_id1],
127128
)
128129

130+
def test_room_subscription_required_state_expansion_returns_immediately(
131+
self,
132+
) -> None:
133+
"""
134+
Test that adding a room subscription with stronger params than the list causes an
135+
incremental long-poll to return immediately, even without new stream activity.
136+
"""
137+
user1_id = self.register_user("user1", "pass")
138+
user1_tok = self.login(user1_id, "pass")
139+
140+
room_id1 = self.helper.create_room_as(user1_id, tok=user1_tok)
141+
142+
sync_body: JsonDict = {
143+
"lists": {
144+
"foo-list": {
145+
"ranges": [[0, 0]],
146+
"required_state": [],
147+
"timeline_limit": 0,
148+
}
149+
},
150+
"conn_id": "conn_id",
151+
}
152+
_, from_token = self.do_sync(sync_body, tok=user1_tok)
153+
154+
sync_body["room_subscriptions"] = {
155+
room_id1: {
156+
"required_state": [
157+
[EventTypes.Create, ""],
158+
],
159+
"timeline_limit": 0,
160+
}
161+
}
162+
163+
channel = self.make_request(
164+
"POST",
165+
self.sync_endpoint + f"?timeout=10000&pos={from_token}",
166+
content=sync_body,
167+
access_token=user1_tok,
168+
await_result=False,
169+
)
170+
channel.await_result(timeout_ms=3000)
171+
self.assertEqual(channel.code, 200, channel.json_body)
172+
173+
state_map = self.get_success(
174+
self.storage_controllers.state.get_current_state(room_id1)
175+
)
176+
177+
room_response = channel.json_body["rooms"][room_id1]
178+
self.assertNotIn("initial", room_response)
179+
self._assertRequiredStateIncludes(
180+
room_response["required_state"],
181+
{
182+
state_map[(EventTypes.Create, "")],
183+
},
184+
exact=True,
185+
)
186+
187+
def test_room_subscription_required_state_change_returns_immediately(self) -> None:
188+
"""
189+
Test that expanding an existing room subscription's required state causes an
190+
incremental long-poll to return immediately, even without new stream activity.
191+
"""
192+
user1_id = self.register_user("user1", "pass")
193+
user1_tok = self.login(user1_id, "pass")
194+
195+
room_id1 = self.helper.create_room_as(
196+
user1_id, tok=user1_tok, extra_content={"name": "Foo"}
197+
)
198+
199+
sync_body: JsonDict = {
200+
"room_subscriptions": {
201+
room_id1: {
202+
"required_state": [
203+
[EventTypes.Create, ""],
204+
],
205+
"timeline_limit": 0,
206+
}
207+
},
208+
"conn_id": "conn_id",
209+
}
210+
response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
211+
212+
state_map = self.get_success(
213+
self.storage_controllers.state.get_current_state(room_id1)
214+
)
215+
self._assertRequiredStateIncludes(
216+
response_body["rooms"][room_id1]["required_state"],
217+
{
218+
state_map[(EventTypes.Create, "")],
219+
},
220+
exact=True,
221+
)
222+
223+
sync_body["room_subscriptions"][room_id1]["required_state"] = [
224+
[EventTypes.Create, ""],
225+
[EventTypes.Name, ""],
226+
]
227+
228+
channel = self.make_request(
229+
"POST",
230+
self.sync_endpoint + f"?timeout=10000&pos={from_token}",
231+
content=sync_body,
232+
access_token=user1_tok,
233+
await_result=False,
234+
)
235+
channel.await_result(timeout_ms=3000)
236+
self.assertEqual(channel.code, 200, channel.json_body)
237+
238+
room_response = channel.json_body["rooms"][room_id1]
239+
self.assertNotIn("initial", room_response)
240+
self._assertRequiredStateIncludes(
241+
room_response["required_state"],
242+
{
243+
state_map[(EventTypes.Name, "")],
244+
},
245+
exact=True,
246+
)
247+
129248
def test_room_subscriptions_with_leave_membership(self) -> None:
130249
"""
131250
Test `room_subscriptions` with a leave room should give us timeline and state

0 commit comments

Comments
 (0)