From 9616816ca6b124063c2119ab816db8d85afe03f9 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Mon, 8 Jun 2026 11:39:41 +0100 Subject: [PATCH 1/3] Move resumability decisions from the client to the server --- build/data/versions.yaml | 2 +- specifications/api-docstrings.md | 6 +++--- specifications/features.md | 28 ++++++++++++++++------------ 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/build/data/versions.yaml b/build/data/versions.yaml index 5c43e9640..e102fd03a 100644 --- a/build/data/versions.yaml +++ b/build/data/versions.yaml @@ -1,2 +1,2 @@ -specification: "6.0.0" +specification: "6.1.0" protocol: 6 diff --git a/specifications/api-docstrings.md b/specifications/api-docstrings.md index f0d9594b1..11b66956e 100644 --- a/specifications/api-docstrings.md +++ b/specifications/api-docstrings.md @@ -474,7 +474,7 @@ Describes the possible states of a [`RestChannel`]{@link RestChannel} or [`Realt | ATTACHED || The attach has succeeded. In the `ATTACHED` state a client may publish and subscribe to messages, or be present on the channel. | | DETACHING || A detach has been initiated on an `ATTACHED` channel by sending a request to Ably. This is a transient state, followed either by a transition to `DETACHED` or `FAILED`. | | DETACHED || The channel, having previously been `ATTACHED`, has been detached by the user. | -| SUSPENDED || The channel, having previously been `ATTACHED`, has lost continuity, usually due to the client being disconnected from Ably for longer than two minutes. It will automatically attempt to reattach as soon as connectivity is restored. | +| SUSPENDED || The channel, having previously been `ATTACHED`, is waiting until the client to reconnect to Ably after an extended period of disconnection. It will automatically reattach once connectivity is restored. Message continuity may have been lost, but the authoritative source for this is the [`resumed`]{@link ChannelStateChange#resumed}) flag on the subsequent `attached` state change. | | FAILED || An indefinite failure condition. This state is entered if a channel error has been received from the Ably service, such as an attempt to attach without the necessary access rights. | ## enum ChannelEvent @@ -977,8 +977,8 @@ Describes the realtime [`Connection`]{@link Connection} object states. | INITIALIZED || A connection with this state has been initialized but no connection has yet been attempted. | | CONNECTING || A connection attempt has been initiated. The connecting state is entered as soon as the library has completed initialization, and is reentered each time connection is re-attempted following disconnection. | | CONNECTED || A connection exists and is active. | -| DISCONNECTED || A temporary failure condition. No current connection exists because there is no network connectivity or no host is available. The disconnected state is entered if an established connection is dropped, or if a connection attempt was unsuccessful. In the disconnected state the library will periodically attempt to open a new connection (approximately every 15 seconds), anticipating that the connection will be re-established soon and thus connection and channel continuity will be possible. In this state, developers can continue to publish messages as they are automatically placed in a local queue, to be sent as soon as a connection is reestablished. Messages published by other clients while this client is disconnected will be delivered to it upon reconnection, so long as the connection was resumed within 2 minutes. After 2 minutes have elapsed, recovery is no longer possible and the connection will move to the `SUSPENDED` state. | -| SUSPENDED || A long term failure condition. No current connection exists because there is no network connectivity or no host is available. The suspended state is entered after a failed connection attempt if there has then been no connection for a period of two minutes. In the suspended state, the library will periodically attempt to open a new connection every 30 seconds. Developers are unable to publish messages in this state. A new connection attempt can also be triggered by an explicit call to [`connect()`]{@link Connection#connect}. Once the connection has been re-established, channels will be automatically re-attached. The client has been disconnected for too long for them to resume from where they left off, so if it wants to catch up on messages published by other clients while it was disconnected, it needs to use the [History API](https://ably.com/docs/realtime/history). | +| DISCONNECTED || A temporary failure condition. The client has been disconnect from Ably, usually due to network connectivity. It will periodically attempt to open a new connection (about every 15 seconds). In this state, you can continue to publish messages, which will be queued to be sent once a connection is reestablished. Messages published by other clients while this client is disconnected will be delivered to it upon reconnection if the server is able to preserve continuity. | +| SUSPENDED || A long term failure condition. A client moves to this state once it has been in the `disconnected` state for over two minutes. Developers are unable to publish messages in this state, and any queued messages are discarded. The library will attempt to reconnect every 30 seconds; an attempt can also be triggered by calling [`connect()`]{@link Connection#connect}. | | CLOSING || An explicit request by the developer to close the connection has been sent to the Ably service. If a reply is not received from Ably within a short period of time, the connection is forcibly terminated and the connection state becomes `CLOSED`. | | CLOSED || The connection has been explicitly closed by the client. In the closed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. No connection state is preserved by the service or by the library. A new connection attempt can be triggered by an explicit call to [`connect()`]{@link Connection#connect}, which results in a new connection. | | FAILED || This state is entered if the client library encounters a failure condition that it cannot recover from. This may be a fatal connection error received from the Ably service, for example an attempt to connect with an incorrect API key, or a local terminal error, for example the token in use has expired and the library does not have any way to renew it. In the failed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. A new connection attempt can be triggered by an explicit call to [`connect()`]{@link Connection#connect}. | diff --git a/specifications/features.md b/specifications/features.md index c96fffb2a..847a4f44d 100644 --- a/specifications/features.md +++ b/specifications/features.md @@ -544,11 +544,13 @@ The threading and/or asynchronous model for each realtime library will vary by l - `(RTN8)` `Connection#id` attribute: - `(RTN8a)` Is unset until connected - `(RTN8b)` Is a unique string provided by Ably. You should have a test to ensure multiple connected clients have unique connection IDs - - `(RTN8c)` Is `Null` when the SDK is in the `CLOSED`, `CLOSING`, `FAILED`, or `SUSPENDED` states + - `(RTN8c)` This clause has been replaced by [`RTN8d`](#RTN8d) as of specification version 6.1.0. + - `(RTN8d)` Is `Null` when the SDK is in the `CLOSED`, `CLOSING`, or `FAILED` states. - `(RTN9)` `Connection#key` attribute: - `(RTN9a)` Is unset until connected - `(RTN9b)` Is a unique private connection key provided by Ably that is used to reconnect and retain connection state following an unexpected disconnection. You should have a test to ensure multiple connected clients have unique connection keys - - `(RTN9c)` Is `Null` when the SDK is in the `CLOSED`, `CLOSING`, `FAILED`, or `SUSPENDED` states + - `(RTN9c)` This clause has been replaced by [`RTN9d`](#RTN9d) as of specification version 6.1.0. + - `(RTN9d)` Is `Null` when the SDK is in the `CLOSED`, `CLOSING`, or `FAILED` states. - `(RTN10)` This clause has been deleted. It was valid up to and including specification version `1.2`. - `(RTN11)` `Connection#connect` function: - `(RTN11a)` This clause has been replaced by `"RTN11e"`:#RTN11e and `"RTN11f`":#RTN11f as of specification version 4.0.0. @@ -577,6 +579,7 @@ The threading and/or asynchronous model for each realtime library will vary by l - `(RTN14d)` If a connection attempt fails for any recoverable reason (i.e. a network failure, a timeout such as [RTN14c](#RTN14c), or a disconnected response, other than a token failure [RTN14b](#RTN14b)), the `Connection#state` will transition to `DISCONNECTED`, the `Connection#errorReason` will be updated, a `ConnectionStateChange` with the `reason` will be emitted, and new connection attempts will periodically be made until the maximum time in that state threshold is reached. The `retryIn` attribute of the `ConnectionStateChange` object will contain the time in milliseconds until the next connection attempt. `retryIn` should be calculated as described in [`RTB1`](#RTB1). Each time a new connection attempt is made the state will transition to `CONNECTING` and then to `CONNECTED` if successful, or `DISCONNECTED` if unsuccessful and the [default `connectionStateTtl`](#defaults) has not been exceeded. Fallback hosts are used for new connection attempts in accordance with [RTN17](#RTN17). - `(RTN14e)` Once the connection state has been in the `DISCONNECTED` state for more than the [default `connectionStateTtl`](#defaults), the state will change to `SUSPENDED` and be emitted with the `reason`, and the `Connection#errorReason` will be updated. In this state, a new connection attempt will be made periodically as specified within `suspendedRetryTimeout` of `ClientOptions` - `(RTN14f)` The connection will remain in the `SUSPENDED` state indefinitely, whilst periodically attempting to reestablish a connection + - `(RTN14g)` Reconnection attempts in this state should continue to attempt to resume, regardless of how long it has been since the client was last connected. - `(RTN15)` `Connection` failures once `CONNECTED`: - `(RTN15h)` If a `DISCONNECTED` message is received from Ably, then that transport will subsequently be closed by Ably - `(RTN15h1)` If the `DISCONNECTED` message contains a token error (`statusCode` value of 401 and error `code` value in the range `40140 <= code < 40150`) and the library does not have a means to renew the token, the connection will transition to the `FAILED` state and the `Connection#errorReason` will be set @@ -586,10 +589,10 @@ The threading and/or asynchronous model for each realtime library will vary by l - `(RTN15j)` If an `ERROR` `ProtocolMessage` with an empty `channel` attribute is received, this indicates a fatal error in the connection. The server will close the transport immediately after. The client should transition to the `FAILED` state triggering all attached channels to transition to the `FAILED` state as well. Additionally the `Connection#errorReason` should be set with the error received from Ably - `(RTN15i)` This clause has been replaced by [`RTN15j`](#RTN15j) as of specification version 4.0.0. - `(RTN15a)` If a transport is disconnected unexpectedly (without having received a `DISCONNECTED` or `ERROR` protocol message), it should respond as if it had received a non-token `DISCONNECTED` (following `RTN15h3`). - - `(RTN15g)` Connection state is only maintained server-side for a brief period, given by the `connectionStateTtl` in the `connectionDetails`, see [CD2f](#CD2f). If a client has been disconnected for longer than the `connectionStateTtl`, it should not attempt to resume. Instead, it should clear the local connection state, and any connection attempts should be made as for a fresh connection - - `(RTN15g1)` This check should be made before each connection attempt. It is generally not sufficient to merely clear the connection state when moving to `SUSPENDED` state (though that may be done too), since the device may have been sleeping / suspended, in which case it may have been many hours since it was last actually connected, even though, having been in the `CONNECTED` state when it was put to sleep, it has only moved out of that state very recently (after waking up and noticing it's no longer connected) - - `(RTN15g2)` Another consequence of that is that the measure of whether the client been disconnected for too long (for the purpose of this check) cannot just be whether the client left the `CONNECTED` state more than `connectionStateTtl` ago. Instead, it should be whether the difference between the current time and the last activity time is greater than the sum of the `connectionStateTtl` and the `maxIdleInterval`, where the last activity time is the time of the last known actual sign of activity from Ably per [RTN23a](#RTN23a) - - `(RTN15g3)` When a connection attempt succeeds after the connection state has been cleared in this way, channels that were previously `ATTACHED`, `ATTACHING`, or `SUSPENDED` must be automatically reattached, just as if the connection was a resume attempt which failed per [RTN15c7](#RTN15c7) + - `(RTN15g)` This clause has been replaced by [`RTN14g`](#RTN14g) as of specification version 6.1.0. + - `(RTN15g1)` This clause has been deleted as of specification version 6.1.0. + - `(RTN15g2)` This clause has been deleted as of specification version 6.1.0. + - `(RTN15g3)` This clause has been deleted as of specification version 6.1.0. - `(RTN15b)` In order for a connection to be resumed and connection state to be recovered, the client must have received a `CONNECTED` ProtocolMessage which will include a private connection key. To resume that connection, the library reconnects to the [websocket](https://ably.com/topic/websockets) endpoint with an additional querystring param: - `(RTN15b1)` `resume` is the `ProtocolMessage#connectionKey` from the most recent `CONNECTED` `ProtocolMessage` received - `(RTN15b2)` This clause has been deleted. It was valid up to and including specification version `1.2`. @@ -703,9 +706,9 @@ The threading and/or asynchronous model for each realtime library will vary by l - `(RTL4d)` A callback (or other language-idiomatic equivalent) can be provided that is called when the channel next moves to one of `ATTACHED`, `DETACHED`, `SUSPENDED`, or `FAILED` states. In the case of `ATTACHED` the callback is called with no argument. In all other cases it is called with an `ErrorInfo` corresponding to the `ChannelStateChange.reason` of the state change (or a fallback if there is no `reason`) to indicate that the attach has failed. (Note: when combined with RTL4f, this means that if the connection is `CONNECTED`, the callback is guaranteed to be called within `realtimeRequestTimeout` of the `attach()` call) - `(RTL4d1)` Optionally, upon success, the callback may be invoked with the `ChannelStateChange` object once the channel is attached. If the channel is already attached, it should be invoked with `null`. - `(RTL4e)` This clause has been deleted (redundant to [`RTL14`](#RTL14)). - - `(RTL4j)` If the attach is not a clean attach (defined in `RTL4j1`), for example an automatic reattach triggered by [`RTN15c3`](#RTN15c3) or [`RTL13a`](#RTL13a) (non-exhaustive), the library should set the [`ATTACH_RESUME`](#TR3f) flag in the `ATTACH` message - - `(RTL4j1)` A 'clean attach' is an attach attempt where the channel has either not previously been attached or has been explicitly detached since the last time it was attached. Note that this is not purely a function of the immediate previous channel state. An example implementation would be to set the flag from an `attachResume` private boolean variable on the channel, that starts out set to `false`, is set to `true` when the channel moves to the `ATTACHED` state, and set to `false` when the channel moves to the `DETACHING` or `FAILED` states. - - `(RTL4j2)` The client library can test that the flag is being correctly encoded (and that `RTL4k` channel params are correctly included) by publishing a message on a channel, then having another two clients attach to that channel both specifying a `rewind` channel param of `"1"`, one of which has the `ATTACH_RESUME` flag forcibly set, other doesn't. The client without the flag set should receive the previously-published message once the attach succeeds; the one with that flag set should not + - `(RTL4j)` This clause has been deleted as of specification version 6.1.0. (That means that SDKs need not set `ATTACH_RESUME` any more). + - `(RTL4j1)` This clause has been deleted as of specification version 6.1.0. + - `(RTL4j2)` This clause has been deleted as of specification version 6.1.0. - `(RTL4k)` If the user has specified a non-empty `params` object in the `ChannelOptions` ([`TB2c`](#TB2c)), it must be included in a `params` field of the `ATTACH` `ProtocolMessage` - `(RTL4k1)` If any channel parameters are requested (which may be through the `params` field of the `ATTACH` message or some other way opaque to the client library), the `ATTACHED` (and any subsequent `ATTACHED` s) will include a `params` property (also a `Dict`) containing the subset of those params that the server has recognised and validated. This should be exposed as a read-only `params` field of the `RealtimeChannel` (or a `getParams()` method where that is more idiomatic). An `ATTACHED` message with no `params` property must be treated as equivalent to a `params` of `{}` (that is, `RealtimeChannel.params` should be set to the empty dict) - `(RTL4l)` If the user has specified a `modes` array in the `ChannelOptions` ([`TB2d`](#TB2d)), it must be encoded as a bitfield per [`TR3`](#TR3) and set as the `flags` field of the `ATTACH` `ProtocolMessage`. (For the avoidance of doubt, when multiple different spec items require flags to be set in the `ATTACH`, the final `flags` field should be the bitwise OR of them all) @@ -779,7 +782,8 @@ The threading and/or asynchronous model for each realtime library will vary by l - `(RTL15)` `RealtimeChannel#properties` attribute is a `ChannelProperties` object representing properties of the channel state. `properties` is a publicly accessible member of the channel, but it is an experimental and unstable API. It has the following attributes: - `(RTL15a)` `attachSerial` is unset when the channel is instantiated, and is updated with the `channelSerial` from each `ATTACHED` `ProtocolMessage` received from Ably with a matching `channel` attribute. The `attachSerial` value is used for `untilAttach` queries, see [RTL10b](#RTL10b) - `(RTL15b)` `channelSerial` is updated whenever a `ProtocolMessage` with either `MESSAGE`, `PRESENCE`, `ANNOTATION`, `OBJECT`, or `ATTACHED` actions is received on a channel, and is set to the `TR4c` `channelSerial` of that `ProtocolMessage`, if and only if that field (`ProtocolMessage.channelSerial`) is populated. - - `(RTL15b1)` If the channel enters the `DETACHED`, `SUSPENDED`, or `FAILED` state, it must clear its `channelSerial`. + - `(RTL15b1)` This clause has been replaced by [`RTL15b2`](#RTL15b2) as of specification version 6.1.0. + - `(RTL15b2)` If the channel enters the `DETACHED` or `FAILED` state, it must clear its `channelSerial`. (Unlike previous spec versions, it must not clear it when entering the `SUSPENDED` state). - `(RTL13)` If the channel receives a server initiated `DETACHED` message when it is in the `ATTACHING`, `ATTACHED` or `SUSPENDED` state (i.e. the client has not explicitly requested a detach putting the channel into the `DETACHING` state), then the following applies: - `(RTL13a)` If the channel is in the `ATTACHED` or `SUSPENDED` states, an attempt to reattach the channel should be made immediately by sending a new `ATTACH` message and the channel should transition to the `ATTACHING` state with the error emitted in the `ChannelStateChange` event. - `(RTL13b)` If the attempt to re-attach fails, or if the channel was already in the `ATTACHING` state, the channel will transition to the `SUSPENDED` state and the error will be emitted in the `ChannelStateChange` event. An attempt to re-attach the channel automatically will then be made after the period defined by [`RTB1`](#RTB1). When re-attaching the channel, the channel will transition to the `ATTACHING` state. If that request to attach fails i.e. it times out or a `DETACHED` message is received, then the process described here in `RTL13b` will be repeated, indefinitely @@ -1576,7 +1580,7 @@ The core SDK provides an API for wrapper SDKs to supply Ably with analytics info - `(TR3b)` 1: `HAS_BACKLOG` - `(TR3c)` 2: `RESUMED` - `(TR3e)` 4: `TRANSIENT` - - `(TR3f)` 5: `ATTACH_RESUME` + - `(TR3f)` 5: `ATTACH_RESUME` (deprecated): As of specification version 6.1.0 the library need no longer set this flag. - `(TR3h)` 7: `HAS_OBJECTS` - `(TR3q)` 16: `PRESENCE` - `(TR3r)` 17: `PUBLISH` @@ -2058,7 +2062,7 @@ The core SDK provides an API for wrapper SDKs to supply Ably with analytics info The following default values are configured for the client library: - `(DF1)` Realtime defaults: - - `(DF1a)` `connectionStateTtl` integer - default 120s. The duration that Ably will persist the connection state when a Realtime client is abruptly disconnected. When the client is in the `DISCONNECTED` state, once this TTL has passed, the client should transition the state to the `SUSPENDED` state signifying that the state is now lost i.e. channels need to be re-attached manually. Note that this default is overriden by `connectionStateTtl`, if specified in the `ConnectionDetails` of the `CONNECTED` `ProtocolMessage` + - `(DF1a)` `connectionStateTtl` integer - default 120s. When the client is in the `DISCONNECTED` state, once this TTL has passed, the client should transition the state to the `SUSPENDED` state (see [`RTN14e`](#RTN14e)). Note that this default is overriden by `connectionStateTtl`, if specified in the `ConnectionDetails` of the `CONNECTED` `ProtocolMessage`. (The client no longer uses this value to decide whether to attempt a resume: it always attempts a resume on reconnecting and lets the server decide whether continuity can be preserved). - `(DF1b)` This clause has been replaced by [TO3l11](#TO3l11). ## Interface Definition {#idl} From 915c3f336440ed63484c5a4aa6052e86d4d3d24c Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Fri, 19 Jun 2026 17:08:34 +0100 Subject: [PATCH 2/3] Update specifications/api-docstrings.md Co-authored-by: Lewis Marshall --- specifications/api-docstrings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifications/api-docstrings.md b/specifications/api-docstrings.md index 11b66956e..5af931823 100644 --- a/specifications/api-docstrings.md +++ b/specifications/api-docstrings.md @@ -474,7 +474,7 @@ Describes the possible states of a [`RestChannel`]{@link RestChannel} or [`Realt | ATTACHED || The attach has succeeded. In the `ATTACHED` state a client may publish and subscribe to messages, or be present on the channel. | | DETACHING || A detach has been initiated on an `ATTACHED` channel by sending a request to Ably. This is a transient state, followed either by a transition to `DETACHED` or `FAILED`. | | DETACHED || The channel, having previously been `ATTACHED`, has been detached by the user. | -| SUSPENDED || The channel, having previously been `ATTACHED`, is waiting until the client to reconnect to Ably after an extended period of disconnection. It will automatically reattach once connectivity is restored. Message continuity may have been lost, but the authoritative source for this is the [`resumed`]{@link ChannelStateChange#resumed}) flag on the subsequent `attached` state change. | +| SUSPENDED || The channel, having previously been `ATTACHED`, is waiting for the client to reconnect to Ably after an extended period of disconnection. It will automatically reattach once connectivity is restored. Message continuity may have been lost, but the authoritative source for this is the [`resumed`]{@link ChannelStateChange#resumed}) flag on the subsequent `attached` state change. | | FAILED || An indefinite failure condition. This state is entered if a channel error has been received from the Ably service, such as an attempt to attach without the necessary access rights. | ## enum ChannelEvent From 3e3e3a73d525e82b901bd151046fc44ce8a7a0e5 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Fri, 19 Jun 2026 17:08:41 +0100 Subject: [PATCH 3/3] Update specifications/api-docstrings.md Co-authored-by: Lewis Marshall --- specifications/api-docstrings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifications/api-docstrings.md b/specifications/api-docstrings.md index 5af931823..e173bb7b0 100644 --- a/specifications/api-docstrings.md +++ b/specifications/api-docstrings.md @@ -977,7 +977,7 @@ Describes the realtime [`Connection`]{@link Connection} object states. | INITIALIZED || A connection with this state has been initialized but no connection has yet been attempted. | | CONNECTING || A connection attempt has been initiated. The connecting state is entered as soon as the library has completed initialization, and is reentered each time connection is re-attempted following disconnection. | | CONNECTED || A connection exists and is active. | -| DISCONNECTED || A temporary failure condition. The client has been disconnect from Ably, usually due to network connectivity. It will periodically attempt to open a new connection (about every 15 seconds). In this state, you can continue to publish messages, which will be queued to be sent once a connection is reestablished. Messages published by other clients while this client is disconnected will be delivered to it upon reconnection if the server is able to preserve continuity. | +| DISCONNECTED || A temporary failure condition. The client has been disconnected from Ably, usually due to network connectivity. It will periodically attempt to open a new connection (about every 15 seconds). In this state, you can continue to publish messages, which will be queued to be sent once a connection is reestablished. Messages published by other clients while this client is disconnected will be delivered to it upon reconnection if the server is able to preserve continuity. | | SUSPENDED || A long term failure condition. A client moves to this state once it has been in the `disconnected` state for over two minutes. Developers are unable to publish messages in this state, and any queued messages are discarded. The library will attempt to reconnect every 30 seconds; an attempt can also be triggered by calling [`connect()`]{@link Connection#connect}. | | CLOSING || An explicit request by the developer to close the connection has been sent to the Ably service. If a reply is not received from Ably within a short period of time, the connection is forcibly terminated and the connection state becomes `CLOSED`. | | CLOSED || The connection has been explicitly closed by the client. In the closed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. No connection state is preserved by the service or by the library. A new connection attempt can be triggered by an explicit call to [`connect()`]{@link Connection#connect}, which results in a new connection. |