Repository navigation
Conversation
…ing Sync New unstable endpoint POST /_matrix/client/unstable/org.matrix.paginated_sync/sync behind experimental.paginated_sync_enabled. The client sends page_size / limit / history + a single top-level required_state; the server returns the changed rooms (most recently active first, at most page_size of them, at most limit new events each with an explicit per-room gap beyond), plus pending (rooms that did not fit) and total_rooms. The implementation is deliberately a thin layer over sliding sync: PaginatedSyncHandler subclasses SlidingSyncHandler (room data fetch, extensions, connection store and notifier integration are shared), the servlet subclasses SlidingSyncRestServlet (room/extension serialisation shared), and the paging cursor is the existing NEVER/PREVIOUSLY/LIVE per-connection room tracking - candidates that don't fit the page are recorded unsent and picked up next request. Fairness: when more rooms are pending than fit, a quarter of the page is reserved for the rooms whose undelivered updates are oldest. The expanded-timeline hack is disabled for this endpoint (history is /messages' job).
Covers initial-sync paging + drain (pending/total_rooms, most-recent first, no duplication or loss), per-room gapping (limited + prev_batch, only the newest limit events), incremental backlog paging, history semantics for never-sent rooms, and top-level required_state.
- No M_UNKNOWN_POS: an unrecognised pos is treated as absent (nothing trusted from the token); the connection starts afresh and rooms come down as never-sent. Clients have no error path. - required_state is immutable per connection and always taken from the current request: room configs are no longer persisted or diffed (track_room_configs flag, off for paginated sync). - num_live dropped from the wire (derivable: previously-sent rooms only receive live events, initial rooms are all-historical). - Extension lists/rooms scoping ignored: an enabled extension applies to the rooms in the response (PaginatedSyncExtensionHandler). Tests: unknown-pos-starts-afresh, extensions-without-scoping, num_live absence; sliding sync suites unaffected (174 still green). Also gitignore localtest/, the local validation server's scratch dir.
Endpoint /_matrix/client/unstable/org.matrix.msc4525/sync, config flag experimental.msc4525_enabled, docs updated.
The override must not narrow the base method's parameter types.
The store only needs user/requester/conn_id from the sync config, and is shared between sliding sync and paginated sync (MSC4525), so type its methods with a Protocol both configs satisfy instead of SlidingSyncConfig (fixes the mypy failure on PaginatedSyncConfig at the get_and_clear_connection_positions call site, and drops the now-redundant ignore on record_new_state).
There was a problem hiding this comment.
Pull request overview
Adds experimental MSC4525 Paginated Sync using existing Sliding Sync infrastructure.
Changes:
- Adds the gated endpoint, request/response types, and handler.
- Implements room paging, backlog tracking, extensions, and timeline limits.
- Adds integration tests and a changelog entry.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
.gitignore |
Ignores local test artifacts. |
changelog.d/20073.feature |
Announces MSC4525 support. |
synapse/config/experimental.py |
Adds the feature flag. |
synapse/handlers/sliding_sync/__init__.py |
Makes shared behavior configurable. |
synapse/handlers/sliding_sync/paginated.py |
Implements paginated synchronization. |
synapse/handlers/sliding_sync/store.py |
Generalizes connection configuration typing. |
synapse/rest/__init__.py |
Registers the endpoint. |
synapse/rest/client/paginated_sync.py |
Implements request and response handling. |
synapse/server.py |
Exposes the new handler. |
synapse/types/handlers/paginated_sync.py |
Defines handler types. |
synapse/types/rest/client/__init__.py |
Defines the request model. |
tests/rest/client/sliding_sync/test_paginated_sync.py |
Tests primary endpoint behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| required_state: list[ | ||
| Annotated[tuple[StrictStr, StrictStr], Field(strict=False)] | ||
| ] = [] | ||
| extensions: SlidingSyncBody.Extensions | None = None |
With room-config tracking off, prev_room_sync_config was always None, so the lazy-member accounting in get_room_sync_data never ran and incremental responses were state-deltas-only: a timeline sender's membership that the connection had never seen was silently omitted. Since required_state is immutable per connection, treat the previous config as the current one, which reduces _required_state_changes to exactly the lazy-member bookkeeping.
Candidates were derived purely from the event stream, so a read receipt (or room account data change) in an otherwise quiet room was deferred until someone spoke in it. Rooms with undelivered receipt/account-data changes - tracked PREVIOUSLY on the per-connection stream maps, or with activity in the token range - now count as candidates when the relevant extension is enabled: the room wakes into the page (usually as an empty, filtered-out entry) and the extension delivers the data.
Add MSC4525 to the per-user ExperimentalFeature registry: the servlet is always registered but 404s unless the feature is enabled globally (msc4525_enabled) or for the requesting user via the admin experimental-features API.
…omit total_rooms when unknown, conn_id caveat, drop stray .gitignore hunk Also adds a test for the aging-lane fairness path.
Pure rename (plus the test module moving to test_msc4525_paginated_sync.py) to make the experiment easy to grep, remove or merge later.
…malformed pos, protect cold-start drain, fix total_rooms - /versions now advertises org.matrix.msc4525, honouring both the global flag and the per-user experimental feature (as Erik requested inline). - An unparsable pos no longer 400s: it is treated like an unrecognised one and the connection starts afresh (MSC4525 has no client error path). - The aging lane now also reserves page slots for never-sent rooms, so continuous traffic in already-delivered rooms can't stall the initial drain indefinitely. - total_rooms is counted before partial-state filtering so cold-start progress reporting isn't understated.
Removing M_UNKNOWN_POS is about well-formed positions the server no longer recognises; a malformed token is a plain client error like everywhere else. Reverts part of 20feb1e and keeps a test pinning the 400.
complement main has no tests/msc4429 (matrix-org/complement#849 is unmerged), so since 19556 merged, every complement CI run on develop build-fails on the missing package (obscured by a gotestfmt panic on the mid-stream module downloads). Drop the package here so this PR's CI is meaningful; restore the line once complement#849 lands.
|
Heads-up: develop's complement CI is currently broken for every PR - #19556 added To keep this PR's CI meaningful I've merged develop and added fe471df, an |
Implements MSC4525 (Paginated Sync): a dialect of Simplified Sliding Sync without lists, ranges, subscriptions, expanding timelines, connection expiry, or a client-visible error path. The client declares
page_size/limit/historyand the server pages it through whatever changed, most recently active rooms first, with bounded responses; rooms with updates that don't fit are reported inpendingand delivered on subsequent requests.New unstable endpoint
POST /_matrix/client/unstable/org.matrix.msc4525/syncbehindexperimental.msc4525_enabled(default off).The implementation is deliberately a thin layer over sliding sync:
PaginatedSyncHandlersubclassesSlidingSyncHandler: the per-room data fetch (get_room_sync_data), extensions, connection store and notifier integration are shared.postoken format, no schema changes._compute_interested_rooms_*with no lists/subscriptions to get the membership map, then sorts by recency and truncates to the page. Fairness: when more rooms are pending than fit, a quarter of the page is reserved for the rooms whose undelivered updates are oldest.expanded_timeline_on_limit_increase,track_room_configs) disable the expanded-timeline hack and per-room request-config persistence for this endpoint;required_stateis immutable per connection, so per-connection state reduces to the sent-rooms map.posis treated as absent (nothing trusted from the token) rather than erroring withM_UNKNOWN_POS;num_liveand the per-extensionlists/roomsscoping are dropped from the wire per the MSC.Tested by
tests/rest/client/sliding_sync/test_paginated_sync.py(initial paging + drain, per-room gapping, backlog redelivery,historysemantics,required_state, unknown-pos-starts-afresh, extensions without scoping); the sliding sync suites are unaffected. Also validated end-to-end against matrix-rust-sdk and Element X iOS branches of the same name (120-room account: cold start fully synced in 2 requests, overnight catch-up in one 18KB page with the connection surviving).Code drafted by Claude at Matthew's direction.
🤖 Generated with Claude Code
https://claude.ai/code/session_01C1H1UimuVrfVkU9Wf1phsd