From 60f2cbfc82c81c27f4cb8948f9e35e63f65ec492 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Wed, 13 Nov 2024 19:11:17 +0000 Subject: [PATCH] Features spec: implement only emitting a leave if there was an existing member per https://github.com/ably/specification/issues/211 The actual behaviour change here is small (only whether non-sync leaves where there is no matching member currently present should emit a leave event), but I found the current structure of this spec section quite confusing (why are broadcast and members-map add/remove requirements defined separately, despite having the same requirements?), so ended up rewriting it. (Without deprecating the old spec items since this isn't actually a behaviour change except in that one case) --- meta.yaml | 2 +- textile/features.textile | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/meta.yaml b/meta.yaml index 623003d76..eeea3ebae 100644 --- a/meta.yaml +++ b/meta.yaml @@ -1,7 +1,7 @@ versions: # Must conform to Semantic Versioning (https://semver.org/). # Should never need to use a pre-release suffix. - specification: 3.0.0 + specification: 3.0.1 # Must be an Integer value. # Previously, prior to version 2 (i.e. versions 0.8, 1.0, 1.1 and 1.2) it was a Decimal value. diff --git a/textile/features.textile b/textile/features.textile index b24e81082..8316264cd 100644 --- a/textile/features.textile +++ b/textile/features.textile @@ -741,16 +741,22 @@ h3(#realtime-presence). RealtimePresence * @(RTP1)@ When a channel @ATTACHED@ @ProtocolMessage@ is received, the @ProtocolMessage@ may contain a @HAS_PRESENCE@ bit flag indicating that it will perform a presence sync, see "TR3":#TR3 . (Note that this does not imply that there are definitely members present, only that there may be; the sync may be empty). If the flag is 1, the server will shortly perform a @SYNC@ operation as described in "RTP18":#RTP18 . If that flag is 0 or there is no @flags@ field, the presence map should be considered in sync immediately with no members present on the channel * @(RTP2)@ A @PresenceMap@ should be used to maintain a list of members present on a channel. Broadly, this is is a map of "memberKeys":#TP3h to presence messages, all with @PRESENT@ actions (during a sync there may also be ones with an @ABSENT@ action, see "RTP2f":#RTP2f). -** @(RTP2a)@ All incoming presence messages must be compared for newness with the matching member already in the @PresenceMap@, if one exists, where "matching" means they share the same @memberKey@ (or equivalently, they share both @connectionId@ and @clientId@) +** @(RTP2a)@ An incoming presence message is defined to "satisfy the newness check" if either there is no matching message already present in the @PresenceMap@ (where "matching" means they share the same @memberKey@; equivalently, they share both @connectionId@ and @clientId@); or there is a matching message, but the incoming event is @RTP2b@-newer than the existing one ** @(RTP2b)@ To compare for newness: *** @(RTP2b1)@ If either presence message has a @connectionId@ which is not an initial substring of its @id@, compare them by @timestamp@ numerically. (This will be the case when one of them is a 'synthesized leave' event sent by realtime to indicate a connection disconnected unexpectedly 15s ago. Such messages will have an @id@ that does not correspond to its @connectionId@, as it wasn't actually published by that connection) **** @(RTP2b1a)@ If the timestamps compare equal, the newly-incoming message is considered newer than the existing one *** @(RTP2b2)@ Else split the @id@ of both presence messages (which will be of the form @connid:msgSerial:index@, e.g. @aaaaaa:0:0@) on the separator @:@, and parse the latter two as integers. Compare them first by @msgSerial@ numerically, then (if @msgSerial@ is equal) by @index@ numerically, larger being newer in both cases ** @(RTP2c)@ As there are no guarantees that during a @SYNC@ operation presence events will arrive in order, all presence messages from a @SYNC@ must also be compared for newness in the same way as they would from a @PRESENCE@ -** @(RTP2d)@ When a presence message with an action of @ENTER@, @UPDATE@, or @PRESENT@ arrives, it should be added to the presence map with the action set to @PRESENT@ -** @(RTP2e)@ If a @SYNC@ is not in progress, then when a presence message with an action of @LEAVE@ arrives, that @memberKey@ should be deleted from the presence map, if present -** @(RTP2f)@ If a @SYNC@ is in progress, then when a presence message with an action of @LEAVE@ arrives, it should be stored in the presence map with the action set to @ABSENT@. When the @SYNC@ completes, any @ABSENT@ members should be deleted from the presence map. (This is because in a @SYNC@, we might receive a @LEAVE@ before the corresponding @ENTER@). -** @(RTP2g)@ Any incoming presence message that passes the newness check should be emitted on the @RealtimePresence@ object, with an event name set to its original action. Note: this action may not be the same one that it will have when stored in the presence map. For example: an incoming presence message with an @ENTER@ action will be emitted as an @enter@ event, and the emitted presence message will have its action set to @ENTER@. However, it will be stored in the presence map with a @PRESENT@ action. +** @(RTP2d)@ When a presence message with an action of @ENTER@, @UPDATE@, or @PRESENT@ arrives, if and only if it satisfies the @RTP2a@ newness check: +*** @(RTP2d1)@ It must be emitted to @RealtimePresence@ subscribers (with its original presence action, and an event name set to the stringified form of that action) +*** @(RTP2d2)@ It must be added to the presence map with the action set to @PRESENT@ +** @(RTP2e)@ If a @SYNC@ is not in progress, then when a presence message with an action of @LEAVE@ arrives, if and only if there is a member with a matching @memberKey@ currently in the presence map: +*** @(RTP2e1)@ It must be emitted to @RealtimePresence@ subscribers (with an event name set to the stringified form of the @LEAVE@ action) +*** @(RTP2e2)@ That member must be deleted from the presence map +** @(RTP2f)@ If a @SYNC@ is in progress, then when a presence message with an action of @LEAVE@ arrives, if and only if there is a member with a matching @memberKey@ currently in the presence map and the incoming event satisfies the @RTP2b@ newness check against that member: +*** @(RTP2f1)@ It must be stored in the presence map with the action set to @ABSENT@ +*** @(RTP2f2)@ When the @SYNC@ completes, then all @ABSENT@ members in the presence map must be deleted. (No leave events should be emitted other than those required by @RTP19@) +** @(RTP2g)@ This clause has been replaced by @RTP2d1@, @RTP2d2@, @RTP2e1@, @RTP2e2@, @RTP2f1@, and @RTP2f2@. * @(RTP18)@ The realtime system reserves the right to initiate a sync of the presence members at any point once a channel is attached. A server initiated sync provides Ably with a means to send a complete list of members present on the channel at any point ** @(RTP18a)@ The client library determines that a new sync has started whenever a @SYNC@ @ProtocolMessage@ is received with a @channel@ attribute and a new sync sequence identifier in the @channelSerial@ attribute. The @channelSerial@ is used as the sync cursor and is a two-part identifier @:@. If a new sequence identifier is sent from Ably, then the client library must consider that to be the start of a new sync sequence and any previous in-flight sync should be discarded ** @(RTP18b)@ The sync operation for that sequence identifier has completed once the cursor is empty; that is, when the @channelSerial@ looks like @:@