diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index c0338bc22..1e8bcc1f6 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -25,7 +25,7 @@ The motivation for this MSC is: Updating call member events after the user disconnected by allowing to schedule/delay/timeout/expire events in a generic way. -It turnes out that there is a big overlap to other usecases in Matrix which can also be implemented using the proposed concept: +It turns out that there is a big overlap to other usecases in Matrix which can also be implemented using the proposed concept: - Updating call member events after the user disconnected. - Sending scheduled messages (send at a specific time). @@ -64,10 +64,10 @@ events the timed out version of the event would be an event where the content co the users has left the call. This proposal also includes a way to refresh the timeout. Allowing to delay the event multiple times. -A periodic ping of the refreshing can be used as a heardbeat mechanism. Once the refresh ping is not send -anymore the timeout condition is met and the homerver sends the event with the expired content information. +A periodic ping of the refreshing can be used as a heartbeat mechanism. Once the refresh ping is not send +anymore the timeout condition is met and the homeserver sends the event with the expired content information. -This translate to: _"only send the event when the client is not running the its program anymore (not sending the heartbeat anymore)"_ +This translate to: _"only send the event when the client is not running its program anymore (not sending the heartbeat anymore)"_ We call those delayed events `Futures`. New endpoints are introduced: @@ -79,18 +79,27 @@ New endpoints are introduced: Those behave like the normal `send`/`state` endpoints except that that they allow to define `future_timeout` and `future_group_id` in their query parameters. -- `future_timeout: number | "none"` is a required parameter that defines how long the homeserver will wait before sending - the event into the room. Since the timeout can be refreshed and sending the future can be triggered via an endpoint (see: [Proposal/Delegating futures](#delegating-futures)) this value is not enough to predict the time this event will arrive in the room. - - If set to `"none"` the future will never expire and can only be send by the [external delegation endpoint](#delegating-futures). +- `future_timeout: number` defines how long (in milliseconds) the homeserver will wait before sending + the event into the room. **Note**, since the timeout can be refreshed and sending the future can be triggered via an endpoint (see: [Proposal/Delegating futures](#delegating-futures)) this value is not enough to predict the time this event will arrive in the room. + - If this query parameter is not added the future will never expire and can only be send by the [external delegation endpoint](#delegating-futures). We call such a future **action future**. - - If set to a `number` we call the future **timeout future** + - If set to a `number` (ms) we call the future **timeout future** - `future_group_id: string` is optional if a `future_timeout` is a `number`. The purpose of this identifier is to group **multiple futures in one mutually exclusive group**. - Only one of the events in such a group can ever reach the DAG/will be distributed by the homeserver. All other futures will be discarded. - Every future group needs at least one timeout future to guarantee that all future expire eventually. - - If a timeout future is send without a `future_group_id` a unique identifier will be generated by the - homeserver and is part of the `send_future`response. + - If a timeout future is sent without a `future_group_id` a unique identifier will be generated by the + homeserver and is part of the `send_future` response. + +Both of the query parameters are optional but one of them has to be present. +This gives us the following options: + +``` +?future_timeout=10 - a timeout future in a new future group +?future_timeout=10&future_group_id="groupA" - a timeout future added to groupA +?future_group_id="groupA" - an action future added to groupA +``` Possible error responses are all error responses that can occur when using the `send` and `state` endpoint accordingly and: @@ -112,16 +121,16 @@ if the power level situation has changed at the time the future resolves.) ### Response -The response will include a `send_token` and a optional `refresh_token` but no `event_id` since the `event_id` depends on the `origin_server_ts` which is not yet determined. A timeout future will contain both, `send_token` and `refresh_token` but an action future will only have a `action_token` in its body. +The response will include a `send_token`, `cancel_token`, the associated `future_group_id` and an optional `refresh_token` but no `event_id` since the `event_id` depends on the `origin_server_ts` which is not yet determined. A timeout future will contain `refresh_token` but an action future will not. ```json { // always present - "send_token": "token", - // optional if there is a timeout + "send_token": "send_token", + "cancel_token": "cancel_token", "future_group_id": "group_id", - "refresh_token": "token", - "cancel_token": "token" + // optional, only present if its a a timeout future response + "refresh_token": "refresh_token" } ``` @@ -145,7 +154,7 @@ The homeserver does the following when receiving a Future: - It checks for the validity of the request (based on the `future_timeout` and the `future_group_id` query parameters) and returns a `409` or `400` if necessary. -- It **generates** a `send_token` and optionally a `future_group_id`, a `refresh_token` and a `cancel_token` and stores them alongside the time +- It **generates** a `send_token`, a `cancel_token` and if not provided in the request a `future_group_id` and a optionally `refresh_token` and stores them alongside the time of retrieval and the `timeout_duration`. - If `future_timeout` was present, it **Starts a timer** for the `refresh_token`. @@ -154,7 +163,10 @@ The homeserver does the following when receiving a Future: - If a `PUT /_matrix/client/v3/futures/{send_token}` is received, it **sends the associated action or timeout future** and deletes any stored futures with the `group_id` associated with that token. - If a `PUT /_matrix/client/v3/futures/{cancel_token}` is received, it **does NOT send any future** - and deletes any stored futures with the `group_id` associated with that token. + and deletes/invalidates the associated stored future. This can mean that a whole future group gets deleted (see below). + - If a `PUT /_matrix/client/v3/futures/{unknown_token}` is received the server responds with a `410` (Gone). + An `unknown_token` either means that the service is making something up or that the service is using a + token that is invalidated by now. - If a timer times out, **it sends the timeout future**. - If the homeserver receives a _new state event_ with the same state key as existing futures the **futures get invalidated and the associated timers are stopped**. @@ -171,7 +183,7 @@ The homeserver does the following when receiving a Future: So for each `future_group_id`, the homeserver will at most send one timeline event. - No timeline event will be send in case all of the timeout futures in a future group are cancelled via `/_matrix/client/v3/futures/{cancel_token}`. -- Otherwise one of the timeout or action future will be emitted. +- Otherwise one of the timeout or action futures will be send. Timed messages, tea timers, reminders or ephemeral events could be implemented using this where clients send room events with @@ -206,17 +218,17 @@ of the final event content with the associated tokens. ```json [ { - "url":"/_matrix/client/v3/rooms/{roomId}/send_future/{eventType}/{txnId}?timeout={timeout_duration}&future_group_id={group_id}", + "url":"/_matrix/client/v3/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}", "body":{ ...event_body }, "response":{ // always present - "send_token": "token", - // optional if there is a timeout + "send_token": "send_token", + "cancel_token": "cancel_token", "future_group_id": "group_id", + // optional if there is a timeout "refresh_token": "token", - "cancel_token": "token", } }, ] @@ -244,9 +256,9 @@ To properly display room tiles and header in the room list (or compute a list of - If there is a running session. - What type that session has. -- Who and how many perople are currently participating. +- Who and how many people are currently participating. -A particular delicat situation is that clients are not able to inform others if they loose connection. +A particular delicate situation is that clients are not able to inform others if they lose connection. There are numerous approaches to solve such a situation. They split into two categories: - Polling based @@ -254,25 +266,39 @@ There are numerous approaches to solve such a situation. They split into two cat - Ask an RTC backend (SFU) who is connected. - Timeout based - - Update the room state every x seconds. This allows clients to check how long an event has not been updated and ignore it if its expired. - - Use Future events with a 10s timeout to send the disconnected from call in less then 10s after the user is not anymore pingin the `/refresh` endpoint. (or delegate the disconnect action to a service attached to the SFU) - - Use the client sync loop as a special case timeout for call member events. (See [Alternatives/MSC4018 (use client sync loop))](#msc4018-use-client-sync-loop)) + - Update the room state every x seconds. + This allows clients to check how long an event has not been updated and ignore it if it's expired. + - Use Future events with a 10s timeout to send the disconnected from call + in less then 10s after the user is not anymore pinging the `/refresh` endpoint. + (or delegate the disconnect action to a service attached to the SFU) + - Use the client sync loop as a special case timeout for call member events. + (See [Alternatives/MSC4018 (use client sync loop))](#msc4018-use-client-sync-loop)) Polling based solution have a big overhead in complexity and network requests on the clients. Example: -> A room list with 100 rooms where there has been a call before in every room (or there is an ongoing call) would require the client to send a to-device message (or a request to the SFU) to every user that has an active state event to check if they are still online. Just to display the room tile properly. +> A room list with 100 rooms where there has been a call before in every room +> (or there is an ongoing call) would require the client to send a to-device message +> (or a request to the SFU) to every user that has an active state event to check if +> they are still online. Just to display the room tile properly. For displaying the room list timeout based approaches are much more reasonable because this allows computing MatrixRTC metadata for a room to be synchronous. -The current solution updates the room state every X minutes. This is not elegant since we basically resend room state with the same content. In large calls this could result in huge traffic/large DAGs (100 call members implies 100 state events every X minutes.) X cannot be a long duration because it is the duration after which we can consider the event as expired. Improper disconnects would result in the user being displayed as "still in the call" for X minutes (we want this to be as short as possible!) +The current solution updates the room state every X minutes. +This is not elegant since we basically resend room state with the same content. +In large calls this could result in huge traffic/large DAGs (100 call members +implies 100 state events every X minutes.) X cannot be a long duration because +it is the duration after which we can consider the event as expired. Improper +disconnects would result in the user being displayed as "still in the call" for +X minutes (we want this to be as short as possible!) Additionally this approach requires perfect server client time synchronization to compute the expiration. This is currently not possible over federation since `unsigned.age` is not available over federation. #### How this MSC would be used for MatrixRTC -With this proposal we can provide an elegant solution using actions and timeouts to only send one event for joining and one for leaving (reliably) +With this proposal we can provide an elegant solution using actions and timeouts +to only send one event for joining and one for leaving (reliably) - If the client takes care of its membership, we use a short timeout value (around 5-20 seconds) The client will have to ping the refresh endpoint approx every 2-19 seconds. @@ -287,7 +313,9 @@ With this proposal we can provide an elegant solution using actions and timeouts This MSC also allows to implement self-destructing messages: -First send (or generate the pdu when [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) is available): +First send (or generate the pdu when +[MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) +is available): `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{eventType}/{txnId}` ```json @@ -318,8 +346,8 @@ normal `send` and `state` endpoints it comes to mind, that one could reuse them query parameters `?future_timeout={timeout_duration}&future_group_id={group_id}` directly to the `send` and `state` endpoint. -This would be elegant but since those two endpoint are core to Matrix changes to them might -be controversion if their return value is altered. +This would be elegant but since those two endpoint are core to Matrix, changes to them might +be controversial if their return value is altered. Currently they always return @@ -355,17 +383,16 @@ dependent on the query parameters. ### Batch sending futures with custom endpoint -The proposed solution does not allow to send multiple events/futures that belong to each other with one +The proposed solution does not allow to send events together with futures that reference them with one HTTPS request. This is desired for self-destructing events and for MatrixRTC room state events, where we want the guarantee, that the event itself and the future removing the event both reach the homeserver -with one request. Otherwise there is a rist for the client to loose connecting or crash between sending the -event and the future which results in never expiring call memberhsip or never destructing self-destructing messages. +with one request. Otherwise there is a risk for the client to lose connection or crash between sending the +event and the future which results in never expiring call membership or never destructing self-destructing messages. This would be solved once [MSC4080](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) and the `/send_pdus` endpoint is implemented. (Then the `future_timeout` and `future_group_id` could be added to the `PDUInfo` instead of the query parameters and everything could be send at once.) -This would be the preferred solution since we currently don't have any other batch sending mechanism. -before +This would be the preferred solution since we currently don't have any other batch sending mechanism. It would however require lots of changes since a new widget action for futures would be needed. With the current main proposal it is enough to add a `future_timeout` and `future_group_id` parameter to the send message widget action. The widget driver would then take care of calling `send` or `send_future` based on the presence of those fields. @@ -378,7 +405,6 @@ It allows to send a list of event contents. The body looks as following: ```json { "timeout": 10, - "group_id": group_id, "send_on_timeout": { "type":type, @@ -520,6 +546,7 @@ even tell with which room or user it is interacting or what the token does (refr ## Unstable prefix -Use `io.element.` instead of `m.` as long as the MSC is not stable. +Use `io.element.msc3140.` instead of `m.` as long as the MSC is not stable. +For the endpoints introduced in this MSC use the prefix `/io.element.msc3140/` and set the paths version string to unstable, instead of v#. ## Dependencies