-
Notifications
You must be signed in to change notification settings - Fork 412
MSC4308: Thread Subscriptions extension to Sliding Sync #4308
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
c56b003
to
41e8a2c
Compare
41e8a2c
to
5f22967
Compare
``` | ||
|
||
If two changes occur to the same subscription, only the latter change ever needs | ||
to be sent to the client. \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these on purpose? (applies elsewhere)
to be sent to the client. \ | |
to be sent to the client. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the most sane of the two ways of adding a line break in the more standard variants of Markdown.
(GFM is I think the only notable dialect that doesn't need this)
// A token that can be used to backpaginate other thread subscription | ||
// changes that occurred since the last sync, but that were not | ||
// included in this response. | ||
// | ||
// Only present when some thread subscriptions have been missed out | ||
// from the response because there are too many of them. | ||
"gap": "OPAQUE_TOKEN" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on prior art, feels like prev_batch
/next_batch
is the pattern to align with.
Ex.
prev_batch
from/sync
responses are supposed to be used with/messages?from=xxx&to=xxx
next_batch
from/sync
responses are supposed to be used with/sync?since=xxx
and/messages?from=xxx&to=xxx
pos
in Sliding sync responses are supposed to be used with Sliding Sync/sync?since=xxx
start
/end
from/messages
responses are supposed to be used with/messages?from=xxx&to=xxx
next_batch
from/threads
responses is supposed to be used with/threads?from=xxx
next_token
from/notifications
responses is supposed to be used with/notifications?from=xxx
next_batch
from/hierarchy
responses is supposed to be used with/hierarchy?from=xxx
Overall, I find our API surface a bit confusing on how we've named fields.
We probably do want to include pagination tokens for both sides of the response (prev
/next
) so clients can fill in the gap from both directions as desired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, what's the use case for a next
token? i.e., in which cases is one interested in getting a thread subscriptions and the ones after it? I feel that we're not going to need this in the short term, and, if we need it, we could also add it when it's actually justified by a use case. So, as a wish to keep the API as minimal as it can be, I'd be in favor of not adding support for next
as well as a dir
ection parameter, unless it's strongly motivated by a real-world use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also rather not add a 'forward' direction. Maybe we can add the dir=b
parameter now and require it to always point backwards.
I am struggling to line up the semantics I want here with the existing API patterns, which is namely tracking and filling a gap (i.e. there are bounds on both sides).
The clients should not really care about the direction of filling, because the time of a subscription is not really important, just the latest state of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed the /thread_subscriptions
API to use from=xxx&to=xxx&dir=b
, but wondering how we should pass back the pair of tokens from Sliding Sync? I've added a new proposal for that but I'm not sure it's the most satisfying (though couldn't see endpoints that do this already for double-ended bounds)
### Companion endpoint for backpaginating thread subscription changes | ||
|
||
``` | ||
GET /_matrix/v1/thread_subscriptions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love the bulk endpoint 👍 but is there also a way to get thread_subscriptions
for a single room?
I can definitely picture some UI which shows the subscribed threads in a room and it would suck to have to wait for the whole room list to paginate in before you could see the list for the single room you're staring at.
The dumb-simple solution is a filter parameter for this endpoint instead of a separate endpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There isn't, but we expect clients to be needing to cache this information locally anyway, so it'd cover a very tiny surface (i.e. during the time we haven't managed to fully sync yet) on a UI I'm not actually sure anyone would implement (rather than 'list of my subscriptions in this room in time order of the subscription', wouldn't you instead be interested in 'list of most recent threads in this room, filtered by whether I'm subscribed').
So I kinda felt it was not worth adding extra API surface here given I don't have a use case in mind for it. If someone disagrees this could easily be rectified in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking on this, this also means servers need to add indices for this and optimise a bit for it. It's not really for free. And if we don't think of a valid use for it, I guess that's actually harmful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I come back to my desktop after a week of working on my phone on a holiday, my desktop client is going to need to catch up on the threads I'm subscribed to. If I click into a room and open a threads panel, I will be waiting until my clients catches up on everything whereas it could use the dedicated room endpoint to get everything directly ahead of the entire list on my account.
"filtered by whether I'm subscribed" is the key here. I need to know whether some threads are subscribed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I see what you'd mean, I'd kinda like a client dev who is implementing this to provide some kind of opinion though on what would work for them.
An ugly but maybe-valid workaround in that case could be to backpaginate the threads in general and check the subscriptions one-by-one manually. Given HTTP pipelining and multiplexing, there are probably worse crimes.
I think more effective would be to inline the subscription directly in the APIs where that makes sense. e.g. filtering the threads to only subscribed ones, directly in /threads
, or at least returning the state there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I come back to my desktop after a week of working on my phone on a holiday, my desktop client is going to need to catch up on the threads I'm subscribed to. If I click into a room and open a threads panel, I will be waiting until my clients catches up on everything whereas it could use the dedicated room endpoint to get everything directly ahead of the entire list on my account.
Remember how thread subscriptions are entirely controlled by clients, and never created by the server? If you came back after a holiday and didn't open any client for a week, then you won't have any new subscriptions, by definition. Then, your client would catch up on the threads (not the thread subscriptions), using either automatic back-pagination for all the threads (bleh) or a new threads catchup mechanism (yay), and then only would it recompute new subscriptions for the threads that have been active during your holiday.
If other devices have sync'd and did subscribe you to threads: you'd receive the updates from the sliding sync extension, and then if there were too many of them, you'd use the global endpoints.
Having a room-specific endpoint might likely make the second case above slightly faster (if the other device subscribed to many other threads on many other rooms). But if this endpoint's processing speed is Good Enough™ (viz. not blocked on network/federation), I imagine that it would be sufficient to have only the global feed, for the time being. Does it make sense?
An ugly but maybe-valid workaround in that case could be to backpaginate the threads in general and check the subscriptions one-by-one manually. Given HTTP pipelining and multiplexing, there are probably worse crimes.
For what it's worth, I did experiment with backpaginating all of a room's threads, when implementing a dummy activity center in the SDK, and this was way too slow for a room like MatrixHQ (of course this is the extreme edge case).
I think more effective would be to inline the subscription directly in the APIs where that makes sense. e.g. filtering the threads to only subscribed ones, directly in /threads, or at least returning the state there.
Maybe this could be part of the bundled thread summary, for thread roots, next to the current_user_participated
which really thread subscriptions are a generalization of?
Also: note that filtering to only subscribed ones wouldn't give you the information that you may have unsubscribed from another device in the meanwhile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps the bulk endpoint should work like this:
We could implement a bulk
/messages
endpoint, where the client would specify multiple rooms andprev_batch
tokens.-- MSC4186
And this also gets around the problem where you can't get to the thread subscriptions that you care about without paginating through the bulk. This way, you can specify the rooms you want to get more thread subscriptions in.
Not sure how fetching thread subscriptions across all rooms would look like yet (many options).
Also being explored in element-hq/synapse#19005 (comment)
GET /_matrix/v1/thread_subscriptions | ||
``` | ||
|
||
URL parameters: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per my other comment,
We probably do want to include pagination tokens for both sides of the response (
prev
/next
) so clients can fill in the gap from both directions as desired.
This would also require a dir
direction parameter. I'm not sure it's necessary to refactor to a from
/to
pattern like /messages
but maybe more obvious if they behave the same.
We should also clarify the token bounds. Synapse reference for other places:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I like this updated version a lot!
// Returns a limited window of changes to thread subscriptions | ||
// Only the latest changes are returned in this window. | ||
"thread_subscriptions": { | ||
"changed": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, do you have ideas for other sibling fields at this level? Or is this for symmetry with other response subparts?
(The one idea that comes to mind is that the request/response parts could be a subset of a new threads
field, something like:
"threads": {
"subscriptions": {
"!roomId1:example.org": { ... }
}
}
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than pos
which now exists (although might be renamed, c.f. the other thread), the idea was to not block off extensibility; i.e. if we just flattened this struct then we would have no avenue to extend this Sliding Sync extension.
threads -> subscriptions
might work, but by the same token, the extension is called threads_subscriptions
, so it is weird for the answer to not match the extension name (and I think the Sliding Sync MSC actually says they should match). It would still have the same extensibility problem :/
- `to` (string, optional): a token used to limit the backpagination \ | ||
The token can be acquired from a Sliding Sync response. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, the wording is confusing here as it's talking about the sliding sync response. We might want to precise here again that it's the pos
value from the latest request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried (and maybe failed) to improve with 4d88858
// Optional. Only present when some thread subscriptions have been | ||
// missed out from the response because there are too many of them. | ||
"prev_batch": "OPAQUE_TOKEN" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a client behavior
section defining that ideally the thread subscriptions would all be persisted on the client's side, to avoid having to call into the GET
endpoint for individual thread subscriptions when computing notifications locally (notably for e2ee rooms)? The "ideally" in my sentence suggests this could be worded as a "MAY".
Then I guess, to implement this optimization correctly, a client would have to remember if it's synchronized all threads subscriptions yet, with the following assumptions:
- when a SSS connection has been reset (via a new
pos
), a client should assume it doesn't know about all the thread subscriptions yet (set an hypothetical booleanknown_thread_subscriptions
to false) - after receiving a response and paginating all the previous unknown thread subscriptions, the client should assume it does know with certainty about all the thread subscriptions (set
known_thread_subscriptions
to true) - when getting a thread subscription: if
!known_thread_subscriptions
, a GET request should be sent to figure whether a thread has a subscription or not; otherwise, the state synchronized locally can be used.
The `bump_stamp`s within each thread subscription can be used for determining which | ||
state is latest, for example when a concurrent `/thread_subscriptions` backpagination request | ||
and `/sync` request both return information about the same thread subscription. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For what it's worth, my implementation will likely now store all the bumpstamps in the database (so as to be able to order the latest sub vs unsub, notably). This creates a small discrepancy, when using the endpoints introduced from MSC4306:
- when we use the PUT or DELETE endpoints, we don't receive a bump_stamp. In this case, it's expected that the bumpstamp will come with the remote echo from the next sync. If we want to implement "local echo" behvaior when using the
PUT
orDELETE
endpoints, then we'd need to store the latest status (subscribed/unsubscribed) in the database, and we wouldn't have a new bumpstamp. I think that in this case, it might be sufficient to not update the bumpstamp. - When using the GET endpoint, we don't receive a bumpstamp, but that's fine: it's supposed to be the latest version anyways. Ideally, we'd cache the result in database too, here. And maybe we can perform the same hack as indicated above (keep the previous bumpstamp stored in DB, and only update the subscription status, if it's changed).
TL;DR: adding bump_stamp
to the MSC4306 endpoints might be better for API consistency, but not sure it's achieving anything useful, considering the hack described above.
// in the response. | ||
// Defaults to 100. | ||
// Servers may impose a smaller limit than what is requested here. | ||
"limit": 100, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- it'd be nice to add the
enabled
: boolean here too, for consistency with other extensions (and because the synapse impl requires it, and I was confused as to why my request wasn't taken into account xD) - also i see in the impl that
limit
is required, but I wonder if it should? I didn't put it first, assuming the server would give me a nice default value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the enabled flag, sorry for forgetting it!
The limit
is now opened as a bug in Synapse.
…to Sliding Sync when MSC4306 and MSC4186 are enabled. (#18695) Closes: #18436 Implements: matrix-org/matrix-spec-proposals#4308 Follows: #18674 Adds an extension to Sliding Sync and a companion endpoint needed for backpaginating missed thread subscription changes, as described in MSC4308 --------- Signed-off-by: Olivier 'reivilibre <[email protected]> Co-authored-by: Andrew Morgan <[email protected]>
# Synapse 1.139.0 (2025-09-30) ## Features - Add experimental support for [MSC4308: Thread Subscriptions extension to Sliding Sync](matrix-org/matrix-spec-proposals#4308) when [MSC4306: Thread Subscriptions](matrix-org/matrix-spec-proposals#4306) and [MSC4186: Simplified Sliding Sync](matrix-org/matrix-spec-proposals#4186) are enabled. ([\#18695](element-hq/synapse#18695)) - Update push rules for experimental [MSC4306: Thread Subscriptions](matrix-org/matrix-spec-proposals#4306) to follow a newer draft. ([\#18846](element-hq/synapse#18846)) - Add `get_media_upload_limits_for_user` and `on_media_upload_limit_exceeded` module API callbacks to the media repository. ([\#18848](element-hq/synapse#18848)) - Support [MSC4169](matrix-org/matrix-spec-proposals#4169) for backwards-compatible redaction sending using the `/send` endpoint. Contributed by @SpiritCroc @ Beeper. ([\#18898](element-hq/synapse#18898)) - Add an in-memory cache to `_get_e2e_cross_signing_signatures_for_devices` to reduce DB load. ([\#18899](element-hq/synapse#18899)) - Update [MSC4190](matrix-org/matrix-spec-proposals#4190) support to return correct errors and allow appservices to reset cross-signing keys without user-interactive authentication. Contributed by @tulir @ Beeper. ([\#18946](element-hq/synapse#18946)) ## Deprecations and Removals - Remove obsolete and experimental `/sync/e2ee` endpoint. ([\#18583](element-hq/synapse#18583)) # Synapse 1.138.0 (2025-09-09) ## Features - Support for the stable endpoint and scopes of [MSC3861](matrix-org/matrix-spec-proposals#3861) & co. ([\#18549](element-hq/synapse#18549))
…to Sliding Sync when MSC4306 and MSC4186 are enabled. (#18695) Closes: #18436 Implements: matrix-org/matrix-spec-proposals#4308 Follows: #18674 Adds an extension to Sliding Sync and a companion endpoint needed for backpaginating missed thread subscription changes, as described in MSC4308 --------- Signed-off-by: Olivier 'reivilibre <[email protected]> Co-authored-by: Andrew Morgan <[email protected]>
Depends-on: #4186, #4306
Rendered