From 86022f7be97a5c4e24cc347fcf3b48e5340f4a98 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 26 Jul 2023 23:48:59 +0530 Subject: [PATCH 001/178] Created recovery context struct, added members to save and load data --- ably/recovery_context.go | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 ably/recovery_context.go diff --git a/ably/recovery_context.go b/ably/recovery_context.go new file mode 100644 index 000000000..f59a28b4a --- /dev/null +++ b/ably/recovery_context.go @@ -0,0 +1,8 @@ +package ably + +// RecoveryKeyContext contains the properties required to recover existing connection. +var RecoveryContext struct { + ConnectionKey string + MsgSerial int64 + ChannelSerials map[string]string +} From 8e8a8d8aa03bcef481c3bdf45902e01a8d89277d Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 26 Jul 2023 23:56:57 +0530 Subject: [PATCH 002/178] Added encode method to recoveryKeyContext --- ably/recovery_context.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ably/recovery_context.go b/ably/recovery_context.go index f59a28b4a..87253f929 100644 --- a/ably/recovery_context.go +++ b/ably/recovery_context.go @@ -1,8 +1,12 @@ package ably // RecoveryKeyContext contains the properties required to recover existing connection. -var RecoveryContext struct { +type recoveryKeyContext struct { ConnectionKey string MsgSerial int64 ChannelSerials map[string]string } + +func (r *recoveryKeyContext) Encode() string { + return "" +} From 6d8e8869afca4ff84a07d0927641f3c340d3a873 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 26 Jul 2023 23:58:56 +0530 Subject: [PATCH 003/178] Added json serialization annotations to recoveryContext --- ably/recovery_context.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ably/recovery_context.go b/ably/recovery_context.go index 87253f929..0ae986771 100644 --- a/ably/recovery_context.go +++ b/ably/recovery_context.go @@ -2,9 +2,9 @@ package ably // RecoveryKeyContext contains the properties required to recover existing connection. type recoveryKeyContext struct { - ConnectionKey string - MsgSerial int64 - ChannelSerials map[string]string + ConnectionKey string `json:"connectionKey,omitempty" codec:"connectionKey,omitempty"` + MsgSerial int64 `json:"msgSerial,omitempty" codec:"msgSerial,omitempty"` + ChannelSerials map[string]string `json:"channelSerials,omitempty" codec:"channelSerials,omitempty"` } func (r *recoveryKeyContext) Encode() string { From b76504885a169c16b0e3534e0de2f902edc8b984 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 27 Jul 2023 00:52:49 +0530 Subject: [PATCH 004/178] Added implementation for encode method in RecoveryContext struct --- ably/recovery_context.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ably/recovery_context.go b/ably/recovery_context.go index 0ae986771..4fa5063e0 100644 --- a/ably/recovery_context.go +++ b/ably/recovery_context.go @@ -1,12 +1,15 @@ package ably -// RecoveryKeyContext contains the properties required to recover existing connection. +import "encoding/json" + +// recoveryKeyContext contains the properties required to recover existing connection. type recoveryKeyContext struct { ConnectionKey string `json:"connectionKey,omitempty" codec:"connectionKey,omitempty"` MsgSerial int64 `json:"msgSerial,omitempty" codec:"msgSerial,omitempty"` ChannelSerials map[string]string `json:"channelSerials,omitempty" codec:"channelSerials,omitempty"` } -func (r *recoveryKeyContext) Encode() string { - return "" +func (r *recoveryKeyContext) Encode() (string, error) { + serializedRecoveryKey, err := json.Marshal(r) + return string(serializedRecoveryKey), err } From 736dfe1776d01408559f65862c247fd4d0b7998f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 27 Jul 2023 01:18:26 +0530 Subject: [PATCH 005/178] Added decode method to recoveryContext --- ably/recovery_context.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ably/recovery_context.go b/ably/recovery_context.go index 4fa5063e0..3b8e266f5 100644 --- a/ably/recovery_context.go +++ b/ably/recovery_context.go @@ -4,12 +4,18 @@ import "encoding/json" // recoveryKeyContext contains the properties required to recover existing connection. type recoveryKeyContext struct { - ConnectionKey string `json:"connectionKey,omitempty" codec:"connectionKey,omitempty"` - MsgSerial int64 `json:"msgSerial,omitempty" codec:"msgSerial,omitempty"` - ChannelSerials map[string]string `json:"channelSerials,omitempty" codec:"channelSerials,omitempty"` + ConnectionKey string `json:"connectionKey" codec:"connectionKey"` + MsgSerial int64 `json:"msgSerial" codec:"msgSerial"` + ChannelSerials map[string]string `json:"channelSerials" codec:"channelSerials"` } -func (r *recoveryKeyContext) Encode() (string, error) { - serializedRecoveryKey, err := json.Marshal(r) - return string(serializedRecoveryKey), err +func (r *recoveryKeyContext) Encode() (serializedRecoveryKey string, err error) { + result, err := json.Marshal(r) + serializedRecoveryKey = string(result) + return +} + +func Decode(recoveryKey string) (rCtx *recoveryKeyContext, err error) { + err = json.Unmarshal([]byte(recoveryKey), &rCtx) + return } From bcc04206aa3748b1d376c280710d4f1d5b8b3325 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 27 Jul 2023 23:29:54 +0530 Subject: [PATCH 006/178] Added testcases forn recovery context --- ably/recovery_context_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 ably/recovery_context_test.go diff --git a/ably/recovery_context_test.go b/ably/recovery_context_test.go new file mode 100644 index 000000000..8d0bf79d3 --- /dev/null +++ b/ably/recovery_context_test.go @@ -0,0 +1,20 @@ +//go:build !unit +// +build !unit + +package ably_test + +import ( + "testing" +) + +func Test_ShouldEncodeRecoveryKeyContextObject(t *testing.T) { + +} + +func Test_ShouldDecodeRecoveryKeyToRecoveryKeyContextObject(t *testing.T) { + +} + +func Test_ShouldReturnNullRecoveryContextWhileDecodingFaultyRecoveryKey(t *testing.T) { + +} From f52c9a036a8b1bf90f0a68b9b65a3555f4c3c777 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 27 Jul 2023 23:59:01 +0530 Subject: [PATCH 007/178] Added a test to serialize recoveryKey and check the same --- ably/recovery_context.go | 8 ++++---- ably/recovery_context_test.go | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/ably/recovery_context.go b/ably/recovery_context.go index 3b8e266f5..8fe881989 100644 --- a/ably/recovery_context.go +++ b/ably/recovery_context.go @@ -2,20 +2,20 @@ package ably import "encoding/json" -// recoveryKeyContext contains the properties required to recover existing connection. -type recoveryKeyContext struct { +// RecoveryKeyContext contains the properties required to recover existing connection. +type RecoveryKeyContext struct { ConnectionKey string `json:"connectionKey" codec:"connectionKey"` MsgSerial int64 `json:"msgSerial" codec:"msgSerial"` ChannelSerials map[string]string `json:"channelSerials" codec:"channelSerials"` } -func (r *recoveryKeyContext) Encode() (serializedRecoveryKey string, err error) { +func (r *RecoveryKeyContext) Encode() (serializedRecoveryKey string, err error) { result, err := json.Marshal(r) serializedRecoveryKey = string(result) return } -func Decode(recoveryKey string) (rCtx *recoveryKeyContext, err error) { +func Decode(recoveryKey string) (rCtx *RecoveryKeyContext, err error) { err = json.Unmarshal([]byte(recoveryKey), &rCtx) return } diff --git a/ably/recovery_context_test.go b/ably/recovery_context_test.go index 8d0bf79d3..78d05441f 100644 --- a/ably/recovery_context_test.go +++ b/ably/recovery_context_test.go @@ -5,10 +5,25 @@ package ably_test import ( "testing" + + "github.com/ably/ably-go/ably" + "github.com/stretchr/testify/assert" ) func Test_ShouldEncodeRecoveryKeyContextObject(t *testing.T) { - + var expectedRecoveryKey = "{\"connectionKey\":\"uniqueKey\",\"msgSerial\":1,\"channelSerials\":{\"channel1\":\"1\",\"channel2\":\"2\",\"channel3\":\"3\"}}" + var recoveryKey = &ably.RecoveryKeyContext{ + ConnectionKey: "uniqueKey", + MsgSerial: 1, + ChannelSerials: map[string]string{ + "channel1": "1", + "channel2": "2", + "channel3": "3", + }, + } + key, err := recoveryKey.Encode() + assert.Nil(t, err) + assert.Equal(t, expectedRecoveryKey, key) } func Test_ShouldDecodeRecoveryKeyToRecoveryKeyContextObject(t *testing.T) { From 6c98ef2adc134ceb6340afd16c54404fee4f3c5f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 28 Jul 2023 21:28:39 +0530 Subject: [PATCH 008/178] Added tests for recoveryContext encode and decode --- ably/recovery_context.go | 3 +++ ably/recovery_context_test.go | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ably/recovery_context.go b/ably/recovery_context.go index 8fe881989..2489897c6 100644 --- a/ably/recovery_context.go +++ b/ably/recovery_context.go @@ -17,5 +17,8 @@ func (r *RecoveryKeyContext) Encode() (serializedRecoveryKey string, err error) func Decode(recoveryKey string) (rCtx *RecoveryKeyContext, err error) { err = json.Unmarshal([]byte(recoveryKey), &rCtx) + if err != nil { + rCtx = nil + } return } diff --git a/ably/recovery_context_test.go b/ably/recovery_context_test.go index 78d05441f..605d268c0 100644 --- a/ably/recovery_context_test.go +++ b/ably/recovery_context_test.go @@ -27,9 +27,19 @@ func Test_ShouldEncodeRecoveryKeyContextObject(t *testing.T) { } func Test_ShouldDecodeRecoveryKeyToRecoveryKeyContextObject(t *testing.T) { - + var expectedRecoveryKey = "{\"connectionKey\":\"uniqueKey\",\"msgSerial\":1,\"channelSerials\":{\"channel1\":\"1\",\"channel2\":\"2\",\"channel3\":\"3\"}}" + keyContext, err := ably.Decode(expectedRecoveryKey) + assert.Nil(t, err) + assert.Equal(t, int64(1), keyContext.MsgSerial) + assert.Equal(t, "uniqueKey", keyContext.ConnectionKey) + assert.Equal(t, "1", keyContext.ChannelSerials["channel1"]) + assert.Equal(t, "2", keyContext.ChannelSerials["channel2"]) + assert.Equal(t, "3", keyContext.ChannelSerials["channel3"]) } func Test_ShouldReturnNullRecoveryContextWhileDecodingFaultyRecoveryKey(t *testing.T) { - + var expectedRecoveryKey = "{\"connectionKey\":\"uniqueKey\",\"msgSerial\":\"incorrectStringSerial\",\"channelSerials\":{\"channel1\":\"1\",\"channel2\":\"2\",\"channel3\":\"3\"}}" + keyContext, err := ably.Decode(expectedRecoveryKey) + assert.NotNil(t, err) + assert.Nil(t, keyContext) } From 56a49200ec3da219a560ebdae62a065b692f6ab7 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 28 Jul 2023 21:35:28 +0530 Subject: [PATCH 009/178] Refactored decode to DecodeRecoveryKey in recoveryContext --- ably/recovery_context.go | 2 +- ably/recovery_context_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ably/recovery_context.go b/ably/recovery_context.go index 2489897c6..11d2b7156 100644 --- a/ably/recovery_context.go +++ b/ably/recovery_context.go @@ -15,7 +15,7 @@ func (r *RecoveryKeyContext) Encode() (serializedRecoveryKey string, err error) return } -func Decode(recoveryKey string) (rCtx *RecoveryKeyContext, err error) { +func DecodeRecoveryKey(recoveryKey string) (rCtx *RecoveryKeyContext, err error) { err = json.Unmarshal([]byte(recoveryKey), &rCtx) if err != nil { rCtx = nil diff --git a/ably/recovery_context_test.go b/ably/recovery_context_test.go index 605d268c0..8a42db775 100644 --- a/ably/recovery_context_test.go +++ b/ably/recovery_context_test.go @@ -28,7 +28,7 @@ func Test_ShouldEncodeRecoveryKeyContextObject(t *testing.T) { func Test_ShouldDecodeRecoveryKeyToRecoveryKeyContextObject(t *testing.T) { var expectedRecoveryKey = "{\"connectionKey\":\"uniqueKey\",\"msgSerial\":1,\"channelSerials\":{\"channel1\":\"1\",\"channel2\":\"2\",\"channel3\":\"3\"}}" - keyContext, err := ably.Decode(expectedRecoveryKey) + keyContext, err := ably.DecodeRecoveryKey(expectedRecoveryKey) assert.Nil(t, err) assert.Equal(t, int64(1), keyContext.MsgSerial) assert.Equal(t, "uniqueKey", keyContext.ConnectionKey) @@ -39,7 +39,7 @@ func Test_ShouldDecodeRecoveryKeyToRecoveryKeyContextObject(t *testing.T) { func Test_ShouldReturnNullRecoveryContextWhileDecodingFaultyRecoveryKey(t *testing.T) { var expectedRecoveryKey = "{\"connectionKey\":\"uniqueKey\",\"msgSerial\":\"incorrectStringSerial\",\"channelSerials\":{\"channel1\":\"1\",\"channel2\":\"2\",\"channel3\":\"3\"}}" - keyContext, err := ably.Decode(expectedRecoveryKey) + keyContext, err := ably.DecodeRecoveryKey(expectedRecoveryKey) assert.NotNil(t, err) assert.Nil(t, keyContext) } From edd4d2437c0608183ec46f2be03d67af6e31b378 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 28 Jul 2023 21:46:38 +0530 Subject: [PATCH 010/178] idempotent rest publishing default set to true as per spec --- ably/options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ably/options.go b/ably/options.go index c5bc29d1b..8770eae48 100644 --- a/ably/options.go +++ b/ably/options.go @@ -43,7 +43,7 @@ var defaultOptions = clientOptions{ HTTPOpenTimeout: 4 * time.Second, //TO3l3 ChannelRetryTimeout: 15 * time.Second, // TO3l7 FallbackRetryTimeout: 10 * time.Minute, - IdempotentRESTPublishing: false, + IdempotentRESTPublishing: true, // TO3n Port: Port, TLSPort: TLSPort, Now: time.Now, From 0257156fab35157280651d46ddf81cabb8521a40 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 28 Jul 2023 21:57:44 +0530 Subject: [PATCH 011/178] Refactored clientOption defaults, annotated with right spec --- ably/export_test.go | 6 +++--- ably/proto_http.go | 22 +++++++++++----------- ably/realtime_conn.go | 2 +- ably/rest_client.go | 6 +++--- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index 4bdd5e23a..828daf31f 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -205,9 +205,9 @@ const ( DefaultCipherAlgorithm = defaultCipherAlgorithm DefaultCipherMode = defaultCipherMode - AblyVersionHeader = ablyVersionHeader - AblyVersion = ablyVersion - LibraryVersion = libraryVersion + AblyVersionHeader = ablyProtocolVersionHeader + AblyVersion = ablyProtocolVersion + LibraryVersion = clientLibraryVersion AblyAgentHeader = ablyAgentHeader AblySDKIdentifier = ablySDKIdentifier diff --git a/ably/proto_http.go b/ably/proto_http.go index cb426bfa8..886abfb4a 100644 --- a/ably/proto_http.go +++ b/ably/proto_http.go @@ -8,20 +8,20 @@ import ( // constants for rsc7 const ( - ablyVersionHeader = "X-Ably-Version" - ablyErrorCodeHeader = "X-Ably-Errorcode" - ablyErrorMessageHeader = "X-Ably-Errormessage" - libraryVersion = "1.2.12" - libraryName = "go" - ablyVersion = "1.2" - ablyClientIDHeader = "X-Ably-ClientId" - hostHeader = "Host" - ablyAgentHeader = "Ably-Agent" // RSC7d - ablySDKIdentifier = "ably-go/" + libraryVersion // RSC7d1 + ablyProtocolVersionHeader = "X-Ably-Version" + ablyErrorCodeHeader = "X-Ably-Errorcode" + ablyErrorMessageHeader = "X-Ably-Errormessage" + clientLibraryVersion = "1.2.12" + clientRuntimeName = "go" + ablyProtocolVersion = "1.2" + ablyClientIDHeader = "X-Ably-ClientId" + hostHeader = "Host" + ablyAgentHeader = "Ably-Agent" // RSC7d + ablySDKIdentifier = "ably-go/" + clientLibraryVersion // RSC7d1 ) var goRuntimeIdentifier = func() string { - return fmt.Sprintf("%s/%s", libraryName, runtime.Version()[2:]) + return fmt.Sprintf("%s/%s", clientRuntimeName, runtime.Version()[2:]) }() func ablyAgentIdentifier(agents map[string]string) string { diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 2b7ba8c12..ef2880487 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -251,7 +251,7 @@ func (c *Connection) params(mode connectionMode) (url.Values, error) { "timestamp": []string{strconv.FormatInt(unixMilli(c.opts.Now()), 10)}, "echo": []string{"true"}, "format": []string{"msgpack"}, - "v": []string{ablyVersion}, + "v": []string{ablyProtocolVersion}, } if c.opts.NoEcho { query.Set("echo", "false") diff --git a/ably/rest_client.go b/ably/rest_client.go index e9f5e89c3..b25a88b99 100644 --- a/ably/rest_client.go +++ b/ably/rest_client.go @@ -824,9 +824,9 @@ func (c *REST) newHTTPRequest(ctx context.Context, r *request) (*http.Request, e if r.header != nil { copyHeader(req.Header, r.header) } - req.Header.Set("Accept", protocol) //spec RSC19c - req.Header.Set(ablyVersionHeader, ablyVersion) - req.Header.Set(ablyAgentHeader, ablyAgentIdentifier(c.opts.Agents)) + req.Header.Set("Accept", protocol) // RSC19c + req.Header.Set(ablyProtocolVersionHeader, ablyProtocolVersion) // RSC7a + req.Header.Set(ablyAgentHeader, ablyAgentIdentifier(c.opts.Agents)) // RSC7d if c.opts.ClientID != "" && c.Auth.method == authBasic { // References RSA7e2 h := base64.StdEncoding.EncodeToString([]byte(c.opts.ClientID)) From 5781fc7ceee6aa42462d5a20498dbea7d4e58646 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 28 Jul 2023 22:56:25 +0530 Subject: [PATCH 012/178] Created a separate structure type for channelSerial and AttachSerial --- ably/proto_channel_propeties.go | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 ably/proto_channel_propeties.go diff --git a/ably/proto_channel_propeties.go b/ably/proto_channel_propeties.go new file mode 100644 index 000000000..3ca9d406f --- /dev/null +++ b/ably/proto_channel_propeties.go @@ -0,0 +1,9 @@ +package ably + +type ChannelProperties struct { + // AttachSerial contains the channelSerial from latest ATTACHED ProtocolMessage received on the channel, see CP2a, RTL15a + AttachSerial string + + // ChannelSerial contains the channelSerial from latest ProtocolMessage of action type Message/PresenceMessage received on the channel, see CP2b, RTL15b. + ChannelSerial string +} From 8544c2b342f1e841cc812cb092ab5172b2d0a90f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 28 Jul 2023 23:06:11 +0530 Subject: [PATCH 013/178] Marked recoverykey as deprecated, use CreateRecoveryKey instead --- ably/realtime_conn.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index ef2880487..749bffd1c 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -483,7 +483,13 @@ func (c *Connection) ErrorReason() *ErrorInfo { return c.errorReason } +// Deprecated: this property is deprecated, use CreateRecoveryKey method instead. func (c *Connection) RecoveryKey() string { + return c.CreateRecoveryKey() +} + +// CreateRecoveryKey is an attribute composed of the connectionKey, messageSerial and channelSerials (RTN16g, RTN16g1, RTN16h). +func (c *Connection) CreateRecoveryKey() string { c.mtx.Lock() defer c.mtx.Unlock() if c.key == "" { From 2456acb486a1bbf53c2f5acceea1a4a25e59e15e Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 28 Jul 2023 23:21:34 +0530 Subject: [PATCH 014/178] Added properties option to realtimechannel, set attach key to latest msgserial --- ably/realtime_channel.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index a23049685..e2bceb122 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -226,6 +226,8 @@ type RealtimeChannel struct { //attachResume is True when the channel moves to the ChannelStateAttached state, and False //when the channel moves to the ChannelStateDetaching or ChannelStateFailed states. attachResume bool + + properties ChannelProperties } func newRealtimeChannel(name string, client *Realtime, chOptions *channelOptions) *RealtimeChannel { @@ -239,6 +241,7 @@ func newRealtimeChannel(name string, client *Realtime, chOptions *channelOptions client: client, messageEmitter: newEventEmitter(client.log()), options: chOptions, + properties: ChannelProperties{}, } c.Presence = newRealtimePresence(c) c.queue = newMsgQueue(client.Connection) @@ -726,6 +729,7 @@ func (c *RealtimeChannel) ErrorReason() *ErrorInfo { func (c *RealtimeChannel) notify(msg *protocolMessage) { switch msg.Action { case actionAttached: + c.properties.AttachSerial = msg.ChannelSerial if c.State() == ChannelStateDetaching { // RTL5K c.sendDetachMsg() return From 3233e66863d1923f430612832c405af352e5d8d9 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 28 Jul 2023 23:45:06 +0530 Subject: [PATCH 015/178] Added extra method for historyUntilAttach --- ably/realtime_channel.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index e2bceb122..a34ca7b46 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -656,6 +656,31 @@ func (c *RealtimeChannel) History(o ...HistoryOption) HistoryRequest { return c.client.rest.Channels.Get(c.Name).History(o...) } +// HistoryUntilAttach retrieves a [ably.HistoryRequest] object, containing an array of historical +// [ably.Message] objects for the channel. If the channel is configured to persist messages, +// then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be +// retrieved from history for up to two minutes in the past. +// +// This function will only retrieve messages prior to the moment that the channel was attached or emitted an UPDATE +// indicating loss of continuity. This bound is specified by passing the querystring param fromSerial with the RealtimeChannel#properties.attachSerial +// assigned to the channel in the ATTACHED ProtocolMessage (see RTL15a). +// If the untilAttach param is specified when the channel is not attached, it results in an error. +// +// See package-level documentation => [ably] Pagination for details about history pagination. +func (c *RealtimeChannel) HistoryUntilAttach(o ...HistoryOption) (*HistoryRequest, error) { + if c.state != ChannelStateAttached { + return nil, errors.New("channel is not attached, cannot use attachSerial value in fromSerial param") + } + + untilAttachParam := func(o *historyOptions) { + o.params.Set("fromSerial", c.properties.AttachSerial) + } + o = append(o, untilAttachParam) + + historyRequest := c.client.rest.Channels.Get(c.Name).History(o...) + return &historyRequest, nil +} + func (c *RealtimeChannel) send(msg *protocolMessage, onAck func(err error)) error { if enqueued := c.maybeEnqueue(msg, onAck); enqueued { return nil From b38c7cc98554616333dbb445a5b180209bfb693b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 28 Jul 2023 23:55:27 +0530 Subject: [PATCH 016/178] Refactored test exports for agent headers --- ably/export_test.go | 10 +++++----- ably/realtime_conn_spec_integration_test.go | 2 +- ably/rest_client_integration_test.go | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index 828daf31f..d8d095a4f 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -205,11 +205,11 @@ const ( DefaultCipherAlgorithm = defaultCipherAlgorithm DefaultCipherMode = defaultCipherMode - AblyVersionHeader = ablyProtocolVersionHeader - AblyVersion = ablyProtocolVersion - LibraryVersion = clientLibraryVersion - AblyAgentHeader = ablyAgentHeader - AblySDKIdentifier = ablySDKIdentifier + AblyProtocolVersionHeader = ablyProtocolVersionHeader + AblyProtocolVersion = ablyProtocolVersion + ClientLibraryVersion = clientLibraryVersion + AblyAgentHeader = ablyAgentHeader + AblySDKIdentifier = ablySDKIdentifier EncUTF8 = encUTF8 EncJSON = encJSON diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 56f8894fd..1522cdfe4 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -97,7 +97,7 @@ func Test_RTN2_WebsocketQueryParams(t *testing.T) { t.Run("RTN2f: api version v should be the API version", func(t *testing.T) { requestParams := setup() libVersion := requestParams["v"] - assert.Equal(t, []string{ably.AblyVersion}, libVersion) + assert.Equal(t, []string{ably.AblyProtocolVersion}, libVersion) }) } diff --git a/ably/rest_client_integration_test.go b/ably/rest_client_integration_test.go index d1eb11e39..597c231f5 100644 --- a/ably/rest_client_integration_test.go +++ b/ably/rest_client_integration_test.go @@ -231,9 +231,9 @@ func TestRSC7(t *testing.T) { ablytest.Instantly.Recv(t, &req, requests, t.Fatalf) t.Run("must set version header", func(t *testing.T) { - h := req.Header.Get(ably.AblyVersionHeader) - assert.Equal(t, ably.AblyVersion, h, - "expected %s got %s", ably.AblyVersion, h) + h := req.Header.Get(ably.AblyProtocolVersionHeader) + assert.Equal(t, ably.AblyProtocolVersion, h, + "expected %s got %s", ably.AblyProtocolVersion, h) }) } From 9c3f677a7c4b93cc6fb90c74ff46689eece112a2 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 29 Jul 2023 17:57:38 +0530 Subject: [PATCH 017/178] Added method to realtimechannels to set channelSerials from recover option --- ably/realtime_channel.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index a34ca7b46..cace6d339 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -47,6 +47,15 @@ func newChannels(client *Realtime) *RealtimeChannels { } } +func (channels *RealtimeChannels) SetChannelSerialsFromRecoverOption(serials map[string]string) { + channels.mtx.Lock() + defer channels.mtx.Unlock() + for channelName, channelSerial := range serials { + channel := channels.chans[channelName] + channel.properties.ChannelSerial = channelSerial + } +} + // ChannelOption configures a channel. type ChannelOption func(*channelOptions) From 8f8be85b36290a971b58ccc5f0a1271805916817 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 29 Jul 2023 17:59:02 +0530 Subject: [PATCH 018/178] Added realtimeChannels method to generate channel serials --- ably/realtime_channel.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index cace6d339..cbf94f377 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -56,6 +56,16 @@ func (channels *RealtimeChannels) SetChannelSerialsFromRecoverOption(serials map } } +func (channels *RealtimeChannels) GetChannelSerials() map[string]string { + channels.mtx.Lock() + defer channels.mtx.Unlock() + channelSerials := make(map[string]string) + for channelName, realtimeChannel := range channels.chans { + channelSerials[channelName] = realtimeChannel.properties.ChannelSerial + } + return channelSerials +} + // ChannelOption configures a channel. type ChannelOption func(*channelOptions) From 9592e361c74eff6b92541b653bdcdb47bbb19102 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 29 Jul 2023 18:35:09 +0530 Subject: [PATCH 019/178] Updated createRecoveryKey implementation with channelSerials --- ably/realtime_client.go | 2 +- ably/realtime_conn.go | 20 +++++++++++++++++--- ably/recovery_context.go | 6 +++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ably/realtime_client.go b/ably/realtime_client.go index 9dd612386..e5795db21 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -32,7 +32,7 @@ func NewRealtime(options ...ClientOption) (*Realtime, error) { c.onChannelMsg, c.onReconnected, c.onReconnectionFailed, - }) + }, c) conn.internalEmitter.OnAll(func(change ConnectionStateChange) { c.Channels.broadcastConnStateChange(change) }) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 749bffd1c..d8671313f 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -83,6 +83,7 @@ type Connection struct { // after a reauthorization, to avoid re-reauthorizing. reauthorizing bool arg connArgs + client *Realtime } type connCallbacks struct { @@ -97,7 +98,7 @@ type connCallbacks struct { onReconnectionFailed func(*errorInfo) } -func newConn(opts *clientOptions, auth *Auth, callbacks connCallbacks) *Connection { +func newConn(opts *clientOptions, auth *Auth, callbacks connCallbacks, client *Realtime) *Connection { c := &Connection{ ConnectionEventEmitter: ConnectionEventEmitter{newEventEmitter(auth.log())}, state: ConnectionStateInitialized, @@ -107,6 +108,7 @@ func newConn(opts *clientOptions, auth *Auth, callbacks connCallbacks) *Connecti pending: newPendingEmitter(auth.log()), auth: auth, callbacks: callbacks, + client: client, } auth.onExplicitAuthorize = c.onClientAuthorize c.queue = newMsgQueue(c) @@ -492,10 +494,22 @@ func (c *Connection) RecoveryKey() string { func (c *Connection) CreateRecoveryKey() string { c.mtx.Lock() defer c.mtx.Unlock() - if c.key == "" { + if empty(c.key) || c.state == ConnectionStateClosing || + c.state == ConnectionStateClosed || + c.state == ConnectionStateFailed || + c.state == ConnectionStateSuspended { return "" } - return strings.Join([]string{c.key, fmt.Sprint(*c.serial), fmt.Sprint(c.msgSerial)}, ":") + recoveryContext := RecoveryKeyContext{ + ConnectionKey: c.key, + MsgSerial: c.msgSerial, + ChannelSerials: c.client.Channels.GetChannelSerials(), + } + recoveryKey, err := recoveryContext.Encode() + if err != nil { + c.log().Errorf("Error while encoding recoveryKey %v", err) + } + return recoveryKey } // Serial gives serial number of a message received most recently. diff --git a/ably/recovery_context.go b/ably/recovery_context.go index 11d2b7156..608be450d 100644 --- a/ably/recovery_context.go +++ b/ably/recovery_context.go @@ -11,7 +11,11 @@ type RecoveryKeyContext struct { func (r *RecoveryKeyContext) Encode() (serializedRecoveryKey string, err error) { result, err := json.Marshal(r) - serializedRecoveryKey = string(result) + if err != nil { + serializedRecoveryKey = "" + } else { + serializedRecoveryKey = string(result) + } return } From cb22c7e26620f5dfd8215ba27dee5f9a79627cf5 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 29 Jul 2023 19:29:40 +0530 Subject: [PATCH 020/178] Set channelSerial from msg channelSerial, updated impl for RTL12 --- ably/realtime_channel.go | 21 ++++++++++++++++----- ably/recovery_context_test.go | 8 ++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index cbf94f377..3b9c85377 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -771,10 +771,18 @@ func (c *RealtimeChannel) ErrorReason() *ErrorInfo { } func (c *RealtimeChannel) notify(msg *protocolMessage) { + // RTL15b + if !empty(msg.ChannelSerial) && msg.Action == actionMessage || + msg.Action == actionPresence || msg.Action == actionAttached { + c.log().Debugf("Setting channel serial for channelName - %v, previous - %v, current - %v", + c.Name, c.properties.ChannelSerial, msg.ChannelSerial) + c.properties.ChannelSerial = msg.ChannelSerial + } + switch msg.Action { case actionAttached: - c.properties.AttachSerial = msg.ChannelSerial - if c.State() == ChannelStateDetaching { // RTL5K + c.properties.AttachSerial = msg.ChannelSerial // RTL15a + if c.State() == ChannelStateDetaching { // RTL5K c.sendDetachMsg() return } @@ -785,8 +793,11 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { c.setModes(channelModeFromFlag(msg.Flags)) } c.Presence.onAttach(msg) - // RTL12 - c.setState(ChannelStateAttached, newErrorFromProto(msg.Error), msg.Flags.Has(flagResumed)) + + isAttachResumed := msg.Flags.Has(flagResumed) + if c.state != ChannelStateAttached || !isAttachResumed { //RTL12 + c.setState(ChannelStateAttached, newErrorFromProto(msg.Error), isAttachResumed) + } c.queue.Flush() case actionDetached: c.mtx.Lock() @@ -960,7 +971,7 @@ func (c *RealtimeChannel) lockSetState(state ChannelState, err error, resumed bo Reason: c.errorReason, Resumed: resumed, } - // RTL2g + // RTL2g, RTL12 if !changed { change.Event = ChannelEventUpdate } else { diff --git a/ably/recovery_context_test.go b/ably/recovery_context_test.go index 8a42db775..6c7416c96 100644 --- a/ably/recovery_context_test.go +++ b/ably/recovery_context_test.go @@ -27,8 +27,8 @@ func Test_ShouldEncodeRecoveryKeyContextObject(t *testing.T) { } func Test_ShouldDecodeRecoveryKeyToRecoveryKeyContextObject(t *testing.T) { - var expectedRecoveryKey = "{\"connectionKey\":\"uniqueKey\",\"msgSerial\":1,\"channelSerials\":{\"channel1\":\"1\",\"channel2\":\"2\",\"channel3\":\"3\"}}" - keyContext, err := ably.DecodeRecoveryKey(expectedRecoveryKey) + var recoveryKey = "{\"connectionKey\":\"uniqueKey\",\"msgSerial\":1,\"channelSerials\":{\"channel1\":\"1\",\"channel2\":\"2\",\"channel3\":\"3\"}}" + keyContext, err := ably.DecodeRecoveryKey(recoveryKey) assert.Nil(t, err) assert.Equal(t, int64(1), keyContext.MsgSerial) assert.Equal(t, "uniqueKey", keyContext.ConnectionKey) @@ -38,8 +38,8 @@ func Test_ShouldDecodeRecoveryKeyToRecoveryKeyContextObject(t *testing.T) { } func Test_ShouldReturnNullRecoveryContextWhileDecodingFaultyRecoveryKey(t *testing.T) { - var expectedRecoveryKey = "{\"connectionKey\":\"uniqueKey\",\"msgSerial\":\"incorrectStringSerial\",\"channelSerials\":{\"channel1\":\"1\",\"channel2\":\"2\",\"channel3\":\"3\"}}" - keyContext, err := ably.DecodeRecoveryKey(expectedRecoveryKey) + var recoveryKey = "{\"connectionKey\":\"uniqueKey\",\"msgSerial\":\"incorrectStringSerial\",\"channelSerials\":{\"channel1\":\"1\",\"channel2\":\"2\",\"channel3\":\"3\"}}" + keyContext, err := ably.DecodeRecoveryKey(recoveryKey) assert.NotNil(t, err) assert.Nil(t, keyContext) } From 59984893b83422ca3017bcbce36656f3a826ae31 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 29 Jul 2023 19:34:12 +0530 Subject: [PATCH 021/178] Added spec annotation to channelProperties --- ably/proto_channel_propeties.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ably/proto_channel_propeties.go b/ably/proto_channel_propeties.go index 3ca9d406f..c6475a2cf 100644 --- a/ably/proto_channel_propeties.go +++ b/ably/proto_channel_propeties.go @@ -1,5 +1,6 @@ package ably +// CP2 type ChannelProperties struct { // AttachSerial contains the channelSerial from latest ATTACHED ProtocolMessage received on the channel, see CP2a, RTL15a AttachSerial string From 6df40beaab3f5856b71c3c53c7fc6daca4f07c43 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 29 Jul 2023 22:17:51 +0530 Subject: [PATCH 022/178] Setting msg channelSerial field while sending attach message --- ably/realtime_channel.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 3b9c85377..5ad94269c 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -352,6 +352,7 @@ func (c *RealtimeChannel) lockAttach(err error) (result, error) { Action: actionAttach, Channel: c.Name, } + msg.ChannelSerial = c.properties.ChannelSerial // RTL4c1 if len(c.channelOpts().Params) > 0 { msg.Params = c.channelOpts().Params } From ad8393da9501d1198c4ad24b7b1918a77623d788 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 29 Jul 2023 23:55:46 +0530 Subject: [PATCH 023/178] Set channelSerial to empty as per state change --- ably/realtime_channel.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 5ad94269c..67a0658b4 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -945,6 +945,12 @@ func (c *RealtimeChannel) log() logger { func (c *RealtimeChannel) setState(state ChannelState, err error, resumed bool) error { c.mtx.Lock() defer c.mtx.Unlock() + + // RTP5a1 + if state == ChannelStateDetached || state == ChannelStateSuspended || state == ChannelStateFailed { + c.properties.ChannelSerial = "" + } + return c.lockSetState(state, err, resumed) } From bd04c817d65170686a764e2db6047bbf7f877232 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 2 Aug 2023 19:29:47 +0530 Subject: [PATCH 024/178] removed all references of connectionserial from code and tests --- ably/proto_protocol_message.go | 1 - ably/proto_protocol_message_test.go | 7 +- ably/realtime_conn.go | 13 -- ably/realtime_conn_spec_integration_test.go | 149 -------------------- 4 files changed, 3 insertions(+), 167 deletions(-) diff --git a/ably/proto_protocol_message.go b/ably/proto_protocol_message.go index 74eeb9ade..ecbb476e3 100644 --- a/ably/proto_protocol_message.go +++ b/ably/proto_protocol_message.go @@ -125,7 +125,6 @@ type protocolMessage struct { ConnectionDetails *connectionDetails `json:"connectionDetails,omitempty" codec:"connectionDetails,omitempty"` Error *errorInfo `json:"error,omitempty" codec:"error,omitempty"` MsgSerial int64 `json:"msgSerial" codec:"msgSerial"` - ConnectionSerial int64 `json:"connectionSerial" codec:"connectionSerial"` Timestamp int64 `json:"timestamp,omitempty" codec:"timestamp,omitempty"` Count int `json:"count,omitempty" codec:"count,omitempty"` Action protoAction `json:"action,omitempty" codec:"action,omitempty"` diff --git a/ably/proto_protocol_message_test.go b/ably/proto_protocol_message_test.go index 8783c4c87..c93f3f0e8 100644 --- a/ably/proto_protocol_message_test.go +++ b/ably/proto_protocol_message_test.go @@ -18,14 +18,13 @@ import ( // explicitly encoded into msgpack (as required by the realtime API) func TestProtocolMessageEncodeZeroSerials(t *testing.T) { msg := ably.ProtocolMessage{ - ID: "test", - MsgSerial: 0, - ConnectionSerial: 0, + ID: "test", + MsgSerial: 0, } encoded, err := ablyutil.MarshalMsgpack(msg) assert.NoError(t, err) // expect a 3-element map with both the serial fields set to zero - expected := []byte("\x83\xB0connectionSerial\x00\xA2id\xA4test\xA9msgSerial\x00") + expected := []byte("\x83\xB0id\xA4test\xA9msgSerial\x00") assert.True(t, bytes.Equal(encoded, expected), "unexpected msgpack encoding\nexpected: %x\nactual: %x", expected, encoded) } diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 3ccd7f999..a5af65455 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -287,16 +287,12 @@ func (c *Connection) params(mode connectionMode) (url.Values, error) { switch mode { case resumeMode: query.Set("resume", c.key) - if c.serial != nil { - query.Set("connectionSerial", fmt.Sprint(*c.serial)) - } case recoveryMode: m := strings.Split(c.opts.Recover, ":") if len(m) != 3 { return nil, errors.New("conn: Invalid recovery key") } query.Set("recover", m[0]) - query.Set("connectionSerial", m[1]) } return query, nil } @@ -720,10 +716,6 @@ func (c *Connection) log() logger { return c.auth.log() } -func (c *Connection) setSerial(serial *int64) { - c.serial = serial -} - func (c *Connection) resendPending() { c.mtx.Lock() cx := c.pending.Dismiss() @@ -770,11 +762,6 @@ func (c *Connection) eventloop() { } lastActivityAt = c.opts.Now() msg.updateInnerMessagesEmptyFields() // TM2a, TM2c, TM2f - if msg.ConnectionSerial != 0 { - c.mtx.Lock() - c.setSerial(&msg.ConnectionSerial) - c.mtx.Unlock() - } switch msg.Action { case actionHeartbeat: case actionAck: diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 1522cdfe4..408cbb2e7 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -316,133 +316,6 @@ func (c connectionStateChanges) Receive(change ably.ConnectionStateChange) { c <- change } -func TestRealtimeConn_RTN10_ConnectionSerial(t *testing.T) { - t.Run("RTN10a: Should be unset until connected, should set after connected", func(t *testing.T) { - connDetails := ably.ConnectionDetails{ - ConnectionKey: "foo", - ConnectionStateTTL: ably.DurationFromMsecs(time.Minute * 20), - MaxIdleInterval: ably.DurationFromMsecs(time.Minute * 5), - } - - in := make(chan *ably.ProtocolMessage, 1) - out := make(chan *ably.ProtocolMessage, 16) - - c, _ := ably.NewRealtime( - ably.WithAutoConnect(false), - ably.WithToken("fake:token"), - ably.WithDial(MessagePipe(in, out))) - - stateChange := make(connectionStateChanges, 2) - c.Connection.OnAll(stateChange.Receive) - - assert.Equal(t, ably.ConnectionStateInitialized, c.Connection.State(), - "expected %v; got %v", ably.ConnectionStateInitialized, c.Connection.State()) - - serial := c.Connection.Serial() - assert.Nil(t, serial, - "Connection serial should be nil when initialized/not connected") - c.Connect() - - var change ably.ConnectionStateChange - - ablytest.Soon.Recv(t, &change, stateChange, t.Fatalf) - assert.Equal(t, ably.ConnectionStateConnecting, change.Current, - "expected %v; got %v", ably.ConnectionStateConnecting, change.Current) - - serial = c.Connection.Serial() - assert.Nil(t, serial, - "Connection serial should be nil when connecting/not connected") - - in <- &ably.ProtocolMessage{ - Action: ably.ActionConnected, - ConnectionID: "connection", - ConnectionSerial: 2, - ConnectionDetails: &connDetails, - } - - ablytest.Soon.Recv(t, &change, stateChange, t.Fatalf) - assert.Equal(t, ably.ConnectionStateConnected, change.Current, - "expected %v; got %v", ably.ConnectionStateConnected, change.Current) - - err := ablytest.Wait(ablytest.AssertionWaiter(func() bool { - return *c.Connection.Serial() == 2 - }), nil) - - assert.NoError(t, err, - "Expected 2, Received %v", *c.Connection.Serial()) - }) - - t.Run("RTN10b: Should be set everytime message with connection-serial is received", func(t *testing.T) { - connDetails := ably.ConnectionDetails{ - ConnectionKey: "foo", - ConnectionStateTTL: ably.DurationFromMsecs(time.Minute * 20), - MaxIdleInterval: ably.DurationFromMsecs(time.Minute * 5), - } - - in := make(chan *ably.ProtocolMessage, 1) - out := make(chan *ably.ProtocolMessage, 16) - - in <- &ably.ProtocolMessage{ - Action: ably.ActionConnected, - ConnectionID: "connection", - ConnectionSerial: 2, - ConnectionDetails: &connDetails, - } - - c, _ := ably.NewRealtime( - ably.WithAutoConnect(false), - ably.WithToken("fake:token"), - ably.WithDial(MessagePipe(in, out))) - - err := ablytest.Wait(ablytest.ConnWaiter(c, c.Connect, ably.ConnectionEventConnected), nil) - assert.NoError(t, err) - assert.Equal(t, int64(2), *c.Connection.Serial(), - "Connection serial should be set to 2") - - in <- &ably.ProtocolMessage{ - Action: ably.ActionAttached, - ConnectionID: "connection", - ConnectionSerial: 4, - ConnectionDetails: &connDetails, - } - - err = ablytest.Wait(ablytest.AssertionWaiter(func() bool { - return *c.Connection.Serial() == 4 - }), nil) - - assert.NoError(t, err, - "Expected 4, Received %v", *c.Connection.Serial()) - - in <- &ably.ProtocolMessage{ - Action: ably.ActionMessage, - ConnectionID: "connection", - ConnectionSerial: 5, - ConnectionDetails: &connDetails, - } - - err = ablytest.Wait(ablytest.AssertionWaiter(func() bool { - return *c.Connection.Serial() == 5 - }), nil) - - assert.NoError(t, err, - "Expected 5, Received %v", *c.Connection.Serial()) - - in <- &ably.ProtocolMessage{ - Action: ably.ActionHeartbeat, - ConnectionID: "connection", - ConnectionSerial: 6, - ConnectionDetails: &connDetails, - } - - err = ablytest.Wait(ablytest.AssertionWaiter(func() bool { - return *c.Connection.Serial() == 6 - }), nil) - - assert.NoError(t, err, - "Expected 6, Received %v", *c.Connection.Serial()) - }) -} - func TestRealtimeConn_RTN12_Connection_Close(t *testing.T) { setUpWithEOF := func() (app *ablytest.Sandbox, client *ably.Realtime, doEOF chan struct{}) { @@ -1047,16 +920,6 @@ func TestRealtimeConn_RTN15b(t *testing.T) { assert.Equal(t, connKey, resume, "resume: expected %q got %q", connKey, resume) } - - { //(RTN15b2) - u := metaList[1].dial - serial := u.Query().Get("connectionSerial") - connSerial := fmt.Sprint(metaList[0].Messages()[0].ConnectionSerial) - assert.NotEqual(t, "", serial, - "expected connectionSerial query param to be set") - assert.Equal(t, connSerial, serial, - "connectionSerial: expected %q got %q", connSerial, serial) - } } func recent(msgs []*ably.ProtocolMessage, action ably.ProtoAction) *ably.ProtocolMessage { @@ -2164,18 +2027,6 @@ func TestRealtimeConn_RTN16(t *testing.T) { if err == nil { t.Fatal("expected reason to be set") } - { // (RTN16a) - recoverValue := query.Get("recover") - assert.NotEqual(t, "", recoverValue, - "expected recover query param to be set") - assert.Equal(t, "_____!ablygo_test_fake-key____", recoverValue, - "expected \"_____!ablygo_test_fake-key____\" got %q", recoverValue) - serial := query.Get("connectionSerial") - assert.NotEqual(t, "", serial, - "expected connectionSerial query param to be set") - assert.Equal(t, "5", serial, - "connectionSerial: expected \"5\" got %q", serial) - } { //(RTN16e) info := err.(*ably.ErrorInfo) assert.Equal(t, 80008, int(info.Code), From 391dc8c3de1f6c757414289f0dab17a61eafd3ce Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 2 Aug 2023 19:57:08 +0530 Subject: [PATCH 025/178] Updated ablyprotocolversion to 2 --- ably/options.go | 2 +- ably/proto_http.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ably/options.go b/ably/options.go index 5bf7077b6..98f92e472 100644 --- a/ably/options.go +++ b/ably/options.go @@ -282,7 +282,7 @@ type clientOptions struct { // A recovery key string can be explicitly provided, or alternatively if a callback function is provided, // the client library will automatically persist the recovery key between page reloads and call the callback // when the connection is recoverable. The callback is then responsible for confirming whether the connection - // should be recovered or not. See connection state recovery for further information (RTC1c, TO3i). + // should be recovered or not. See connection state recovery for further information (RTC1c, TO3i, RTN16i). Recover string // TransportParams is a set of key-value pairs that can be used to pass in arbitrary connection parameters, diff --git a/ably/proto_http.go b/ably/proto_http.go index 886abfb4a..cf4e1fad0 100644 --- a/ably/proto_http.go +++ b/ably/proto_http.go @@ -13,7 +13,7 @@ const ( ablyErrorMessageHeader = "X-Ably-Errormessage" clientLibraryVersion = "1.2.12" clientRuntimeName = "go" - ablyProtocolVersion = "1.2" + ablyProtocolVersion = "2" // CSV2 ablyClientIDHeader = "X-Ably-ClientId" hostHeader = "Host" ablyAgentHeader = "Ably-Agent" // RSC7d From c97a1a5e2f583f670d2030ec203c44f68e27f7b7 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 2 Aug 2023 23:00:28 +0530 Subject: [PATCH 026/178] Updated connection key and id in accordance with spec --- ably/realtime_conn.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index a5af65455..e4844118d 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -997,8 +997,9 @@ func (c *Connection) setState(state ConnectionState, err error, retryIn time.Dur } func (c *Connection) lockSetState(state ConnectionState, err error, retryIn time.Duration) error { - if state == ConnectionStateClosed { - c.key, c.id = "", "" //(RTN16c) + if state == ConnectionStateClosing || state == ConnectionStateClosed || + state == ConnectionStateSuspended || state == ConnectionStateFailed { + c.key, c.id = "", "" //(RTN8c, RTN9c) } previous := c.state From efcf81944be1ab4fb5802ed325db20fb0a4a5ed1 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 3 Aug 2023 10:33:48 +0530 Subject: [PATCH 027/178] Added mutexes for setting read limit on connection --- ably/realtime_conn.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index e4844118d..0c4f8c0fa 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -187,6 +187,8 @@ func (c *Connection) Connect() { // By default, the connection has a message read limit of [ably.maxMessageSize] or 65536 bytes. // When the limit is hit, the connection will be closed with StatusMessageTooBig. func (c *Connection) SetReadLimit(readLimit int64) { + c.mtx.Lock() + defer c.mtx.Unlock() c.readLimit = readLimit c.isReadLimitSetExternally = true } From 3392d55b60287a6d3082ee7b830fcd2dc7187ba1 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 6 Aug 2023 23:58:23 +0530 Subject: [PATCH 028/178] Added internal members property as a part of spec --- ably/realtime_presence.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 8eef09637..ea61fe251 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -19,24 +19,26 @@ const ( // It allows entering, leaving and updating presence state for the current client or on behalf of other client. // It enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel. type RealtimePresence struct { - mtx sync.Mutex - data interface{} - serial string - messageEmitter *eventEmitter - channel *RealtimeChannel - members map[string]*PresenceMessage - stale map[string]struct{} - state PresenceAction - syncMtx sync.Mutex - syncState syncState + mtx sync.Mutex + data interface{} + serial string + messageEmitter *eventEmitter + channel *RealtimeChannel + members map[string]*PresenceMessage + internalMembers map[string]*PresenceMessage // RTP17 + stale map[string]struct{} + state PresenceAction + syncMtx sync.Mutex + syncState syncState } func newRealtimePresence(channel *RealtimeChannel) *RealtimePresence { pres := &RealtimePresence{ - messageEmitter: newEventEmitter(channel.log()), - channel: channel, - members: make(map[string]*PresenceMessage), - syncState: syncInitial, + messageEmitter: newEventEmitter(channel.log()), + channel: channel, + members: make(map[string]*PresenceMessage), + internalMembers: make(map[string]*PresenceMessage), + syncState: syncInitial, } // Lock syncMtx to make all callers to Get(true) wait until the presence // is in initial sync state. This is to not make them early return From c950d2610599b7d6c16f1c32f755ca5a0ca4302b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 14 Aug 2023 22:55:24 +0530 Subject: [PATCH 029/178] Refactored realtime presence for loop, added spec annotation --- ably/realtime_presence.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 8eef09637..332183a21 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -173,26 +173,28 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS // Filter out old messages by their timestamp. messages := make([]*PresenceMessage, 0, len(msg.Presence)) // Update presence map / channel's member state. - for _, member := range msg.Presence { - memberKey := member.ConnectionID + member.ClientID - if oldMember, ok := pres.members[memberKey]; ok { - if member.Timestamp <= oldMember.Timestamp { - continue // do not process old message + for _, presenceMember := range msg.Presence { + // RTP2 + memberKey := presenceMember.ConnectionID + presenceMember.ClientID + // RTP2b1 + if oldPresenceMember, ok := pres.members[memberKey]; ok { + if oldPresenceMember.Timestamp >= presenceMember.Timestamp { + continue // do not process message with older timestamp } } - switch member.Action { + switch presenceMember.Action { case PresenceActionEnter: - pres.members[memberKey] = member + pres.members[memberKey] = presenceMember case PresenceActionUpdate: - member.Action = PresenceActionPresent + presenceMember.Action = PresenceActionPresent fallthrough case PresenceActionPresent: delete(pres.stale, memberKey) - pres.members[memberKey] = member + pres.members[memberKey] = presenceMember case PresenceActionLeave: delete(pres.members, memberKey) } - messages = append(messages, member) + messages = append(messages, presenceMember) } if syncSerial == "" { pres.syncEnd() From 60974f8f13763f217300c884fa133f397fb632b6 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 14 Aug 2023 23:51:07 +0530 Subject: [PATCH 030/178] Added internal members property to realtime presence struct --- ably/realtime_presence.go | 63 +++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 332183a21..62ebb8abf 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -19,24 +19,26 @@ const ( // It allows entering, leaving and updating presence state for the current client or on behalf of other client. // It enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel. type RealtimePresence struct { - mtx sync.Mutex - data interface{} - serial string - messageEmitter *eventEmitter - channel *RealtimeChannel - members map[string]*PresenceMessage - stale map[string]struct{} - state PresenceAction - syncMtx sync.Mutex - syncState syncState + mtx sync.Mutex + data interface{} + serial string + messageEmitter *eventEmitter + channel *RealtimeChannel + members map[string]*PresenceMessage + internalMembers map[string]*PresenceMessage // RTP17 + stale map[string]struct{} + state PresenceAction + syncMtx sync.Mutex + syncState syncState } func newRealtimePresence(channel *RealtimeChannel) *RealtimePresence { pres := &RealtimePresence{ - messageEmitter: newEventEmitter(channel.log()), - channel: channel, - members: make(map[string]*PresenceMessage), - syncState: syncInitial, + messageEmitter: newEventEmitter(channel.log()), + channel: channel, + members: make(map[string]*PresenceMessage), + internalMembers: make(map[string]*PresenceMessage), + syncState: syncInitial, } // Lock syncMtx to make all callers to Get(true) wait until the presence // is in initial sync state. This is to not make them early return @@ -170,8 +172,30 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS if syncSerial != "" { pres.syncStart(syncSerial) } - // Filter out old messages by their timestamp. - messages := make([]*PresenceMessage, 0, len(msg.Presence)) + + // RTP17 + for _, presenceMember := range msg.Presence { + memberKey := presenceMember.ClientID + if pres.channel.client.Connection.id != presenceMember.ConnectionID { + continue + } + switch presenceMember.Action { + case PresenceActionEnter: + pres.members[memberKey] = presenceMember + case PresenceActionUpdate: + presenceMember.Action = PresenceActionPresent + fallthrough + case PresenceActionPresent: + delete(pres.stale, memberKey) + pres.members[memberKey] = presenceMember + case PresenceActionLeave: + // todo : check for synthesized leave + delete(pres.members, memberKey) + } + } + + // Filter out old presenceMessages by their timestamp. + newPresenceMessages := make([]*PresenceMessage, 0, len(msg.Presence)) // Update presence map / channel's member state. for _, presenceMember := range msg.Presence { // RTP2 @@ -194,14 +218,15 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS case PresenceActionLeave: delete(pres.members, memberKey) } - messages = append(messages, presenceMember) + newPresenceMessages = append(newPresenceMessages, presenceMember) } + if syncSerial == "" { pres.syncEnd() } pres.mtx.Unlock() - msg.Count = len(messages) - msg.Presence = messages + msg.Count = len(newPresenceMessages) + msg.Presence = newPresenceMessages for _, msg := range msg.Presence { pres.messageEmitter.Emit(msg.Action, (*subscriptionPresenceMessage)(msg)) } From 6aa28738c0176bd564559fd06794026f2d2f4daf Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 15 Aug 2023 00:00:15 +0530 Subject: [PATCH 031/178] Added synthesized leave check --- ably/realtime_presence.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 62ebb8abf..077a37a90 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -173,6 +173,10 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS pres.syncStart(syncSerial) } + isSynthesizedLeave := func(oldMsg *PresenceMessage, newMsg *PresenceMessage) bool { + return false + } + // RTP17 for _, presenceMember := range msg.Presence { memberKey := presenceMember.ClientID @@ -200,10 +204,11 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS for _, presenceMember := range msg.Presence { // RTP2 memberKey := presenceMember.ConnectionID + presenceMember.ClientID - // RTP2b1 - if oldPresenceMember, ok := pres.members[memberKey]; ok { - if oldPresenceMember.Timestamp >= presenceMember.Timestamp { - continue // do not process message with older timestamp + + if oldPresenceMember, ok := pres.members[memberKey]; ok { // RTP2a + if isSynthesizedLeave(oldPresenceMember, presenceMember) && // // RTP2b1 + oldPresenceMember.Timestamp >= presenceMember.Timestamp { + continue // do not process message with older timestamp // RTP2b1a } } switch presenceMember.Action { From a2e2d383844c93c075e465cfb37f512b7808125d Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 15 Aug 2023 23:21:28 +0530 Subject: [PATCH 032/178] Added a new method to proto presence message to check for newer message --- ably/proto_presence_message.go | 14 ++++++++++++++ ably/realtime_presence.go | 7 +------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ably/proto_presence_message.go b/ably/proto_presence_message.go index bc19f7b41..2b0ea3d92 100644 --- a/ably/proto_presence_message.go +++ b/ably/proto_presence_message.go @@ -2,6 +2,7 @@ package ably import ( "fmt" + "strings" ) // PresenceAction describes the possible actions members in the presence set can emit (TP2). @@ -58,3 +59,16 @@ func (m PresenceMessage) String() string { "update", }[m.Action], m.ClientID, m.Data) } + +// RTP2b1 +func (msg *PresenceMessage) isServerSynthesizedPresenceMessage() bool { + return strings.HasPrefix(msg.ID, msg.ConnectionID) +} + +func (oldMessage *PresenceMessage) IsNewerThan(incomingMessage *PresenceMessage) bool { + if oldMessage.isServerSynthesizedPresenceMessage() || + incomingMessage.isServerSynthesizedPresenceMessage() { + return oldMessage.Timestamp > incomingMessage.Timestamp + } + return false +} diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 077a37a90..14e8698e3 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -173,10 +173,6 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS pres.syncStart(syncSerial) } - isSynthesizedLeave := func(oldMsg *PresenceMessage, newMsg *PresenceMessage) bool { - return false - } - // RTP17 for _, presenceMember := range msg.Presence { memberKey := presenceMember.ClientID @@ -206,8 +202,7 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS memberKey := presenceMember.ConnectionID + presenceMember.ClientID if oldPresenceMember, ok := pres.members[memberKey]; ok { // RTP2a - if isSynthesizedLeave(oldPresenceMember, presenceMember) && // // RTP2b1 - oldPresenceMember.Timestamp >= presenceMember.Timestamp { + if oldPresenceMember.IsNewerThan(presenceMember) { // RTP2b1 continue // do not process message with older timestamp // RTP2b1a } } From 95b5ef8b9e6ff852c99e5541f87815d06dfc9058 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 15 Aug 2023 23:56:38 +0530 Subject: [PATCH 033/178] Refactored code to handle synthesized and old message checks --- ably/proto_presence_message.go | 31 ++++++++++++++++++++++++++++--- ably/realtime_presence.go | 5 ++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/ably/proto_presence_message.go b/ably/proto_presence_message.go index 2b0ea3d92..f30956ede 100644 --- a/ably/proto_presence_message.go +++ b/ably/proto_presence_message.go @@ -2,6 +2,7 @@ package ably import ( "fmt" + "strconv" "strings" ) @@ -65,10 +66,34 @@ func (msg *PresenceMessage) isServerSynthesizedPresenceMessage() bool { return strings.HasPrefix(msg.ID, msg.ConnectionID) } -func (oldMessage *PresenceMessage) IsNewerThan(incomingMessage *PresenceMessage) bool { +func (incomingMessage *PresenceMessage) IsOlderThan(oldMessage *PresenceMessage) (bool, error) { if oldMessage.isServerSynthesizedPresenceMessage() || incomingMessage.isServerSynthesizedPresenceMessage() { - return oldMessage.Timestamp > incomingMessage.Timestamp + return oldMessage.Timestamp > incomingMessage.Timestamp, nil } - return false + + presenceIdErr := func(presenceMsgId string) error { + return fmt.Errorf("parsing error, the presence message has invalid id %v", presenceMsgId) + } + + oldMessageIds := strings.Split(oldMessage.ID, ":") + incomingMessageIds := strings.Split(incomingMessage.ID, ":") + + oldMessageSerial, err := strconv.ParseInt(oldMessageIds[1], 10, 64) + oldMessageIndex, err := strconv.ParseInt(oldMessageIds[2], 10, 64) + if len(oldMessageIds) != 3 || err != nil { + return false, presenceIdErr(oldMessage.ID) + } + + incomingMessageSerial, err := strconv.ParseInt(incomingMessageIds[1], 10, 64) + incomingMessageIndex, err := strconv.ParseInt(incomingMessageIds[2], 10, 64) + if len(incomingMessageIds) != 3 || err != nil { + return true, presenceIdErr(incomingMessage.ID) + } + + if oldMessageSerial == incomingMessageSerial { + return oldMessageIndex > incomingMessageIndex, nil + } + + return oldMessageSerial > incomingMessageSerial, nil } diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 14e8698e3..0c634b43e 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -202,7 +202,10 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS memberKey := presenceMember.ConnectionID + presenceMember.ClientID if oldPresenceMember, ok := pres.members[memberKey]; ok { // RTP2a - if oldPresenceMember.IsNewerThan(presenceMember) { // RTP2b1 + isIncomingMessageOld, err := presenceMember.IsOlderThan(oldPresenceMember) + pres.log().Error(err) + // TODO - publish channel error event here without state change + if isIncomingMessageOld { // RTP2b1 continue // do not process message with older timestamp // RTP2b1a } } From ba8e1fcd5bafa1f288801e6c108147027c24491d Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 16 Aug 2023 01:17:10 +0530 Subject: [PATCH 034/178] Refactored realtime presence to make it easy to understand --- ably/proto_presence_message.go | 55 ++++++++++++++++++---------------- ably/realtime_presence.go | 19 ++++++------ 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/ably/proto_presence_message.go b/ably/proto_presence_message.go index f30956ede..a49af105f 100644 --- a/ably/proto_presence_message.go +++ b/ably/proto_presence_message.go @@ -62,38 +62,41 @@ func (m PresenceMessage) String() string { } // RTP2b1 -func (msg *PresenceMessage) isServerSynthesizedPresenceMessage() bool { - return strings.HasPrefix(msg.ID, msg.ConnectionID) +func (msg *PresenceMessage) isServerSynthesized() bool { + return !strings.HasPrefix(msg.ID, msg.ConnectionID) } -func (incomingMessage *PresenceMessage) IsOlderThan(oldMessage *PresenceMessage) (bool, error) { - if oldMessage.isServerSynthesizedPresenceMessage() || - incomingMessage.isServerSynthesizedPresenceMessage() { - return oldMessage.Timestamp > incomingMessage.Timestamp, nil +// RTP2b2 +func (msg *PresenceMessage) getMsgSerialAndIndex() (int64, int64, error) { + msgIds := strings.Split(msg.ID, ":") + if len(msgIds) != 3 { + return 0, 0, fmt.Errorf("parsing error, the presence message has invalid id %v", msg.ID) } - - presenceIdErr := func(presenceMsgId string) error { - return fmt.Errorf("parsing error, the presence message has invalid id %v", presenceMsgId) + msgSerial, err := strconv.ParseInt(msgIds[1], 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("parsing error, the presence message has invalid msgSerial, for msgId %v", msg.ID) } - - oldMessageIds := strings.Split(oldMessage.ID, ":") - incomingMessageIds := strings.Split(incomingMessage.ID, ":") - - oldMessageSerial, err := strconv.ParseInt(oldMessageIds[1], 10, 64) - oldMessageIndex, err := strconv.ParseInt(oldMessageIds[2], 10, 64) - if len(oldMessageIds) != 3 || err != nil { - return false, presenceIdErr(oldMessage.ID) + msgIndex, err := strconv.ParseInt(msgIds[2], 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("parsing error, the presence message has invalid msgIndex, for msgId %v", msg.ID) } + return msgSerial, msgIndex, nil +} - incomingMessageSerial, err := strconv.ParseInt(incomingMessageIds[1], 10, 64) - incomingMessageIndex, err := strconv.ParseInt(incomingMessageIds[2], 10, 64) - if len(incomingMessageIds) != 3 || err != nil { - return true, presenceIdErr(incomingMessage.ID) +func (msg1 *PresenceMessage) IsNewerThan(msg2 *PresenceMessage) (bool, error) { + if msg1.isServerSynthesized() || msg2.isServerSynthesized() { + return msg1.Timestamp > msg2.Timestamp, nil } - - if oldMessageSerial == incomingMessageSerial { - return oldMessageIndex > incomingMessageIndex, nil + msg1Serial, msg1Index, err := msg1.getMsgSerialAndIndex() + if err != nil { + return false, err } - - return oldMessageSerial > incomingMessageSerial, nil + msg2Serial, msg2Index, err := msg2.getMsgSerialAndIndex() + if err != nil { + return true, err + } + if msg1Serial == msg2Serial { + return msg1Index > msg2Index, nil + } + return msg1Serial > msg2Serial, nil } diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 0c634b43e..5dfb5f2f3 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -194,19 +194,18 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS } } - // Filter out old presenceMessages by their timestamp. - newPresenceMessages := make([]*PresenceMessage, 0, len(msg.Presence)) // Update presence map / channel's member state. + newPresenceMessages := make([]*PresenceMessage, 0, len(msg.Presence)) for _, presenceMember := range msg.Presence { - // RTP2 memberKey := presenceMember.ConnectionID + presenceMember.ClientID - - if oldPresenceMember, ok := pres.members[memberKey]; ok { // RTP2a - isIncomingMessageOld, err := presenceMember.IsOlderThan(oldPresenceMember) - pres.log().Error(err) - // TODO - publish channel error event here without state change - if isIncomingMessageOld { // RTP2b1 - continue // do not process message with older timestamp // RTP2b1a + if existingMember, ok := pres.members[memberKey]; ok { // RTP2a + isMemberNew, err := presenceMember.IsNewerThan(existingMember) // RTP2b + if err != nil { + pres.log().Error(err) + // TODO - publish channel error event here without state change + } + if !isMemberNew { + continue // do not accept if incoming member is old } } switch presenceMember.Action { From d0694a4e4effccd9c7410b13d1bfa922f1ccc4e1 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 16 Aug 2023 14:40:28 +0530 Subject: [PATCH 035/178] Added spec annotation to presenceMessage newerThan method --- ably/proto_presence_message.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ably/proto_presence_message.go b/ably/proto_presence_message.go index a49af105f..63140637f 100644 --- a/ably/proto_presence_message.go +++ b/ably/proto_presence_message.go @@ -61,12 +61,10 @@ func (m PresenceMessage) String() string { }[m.Action], m.ClientID, m.Data) } -// RTP2b1 func (msg *PresenceMessage) isServerSynthesized() bool { return !strings.HasPrefix(msg.ID, msg.ConnectionID) } -// RTP2b2 func (msg *PresenceMessage) getMsgSerialAndIndex() (int64, int64, error) { msgIds := strings.Split(msg.ID, ":") if len(msgIds) != 3 { @@ -84,9 +82,12 @@ func (msg *PresenceMessage) getMsgSerialAndIndex() (int64, int64, error) { } func (msg1 *PresenceMessage) IsNewerThan(msg2 *PresenceMessage) (bool, error) { + // RTP2b1 if msg1.isServerSynthesized() || msg2.isServerSynthesized() { return msg1.Timestamp > msg2.Timestamp, nil } + + // RTP2b2 msg1Serial, msg1Index, err := msg1.getMsgSerialAndIndex() if err != nil { return false, err From 2b849260e33000d5ee719690b31f6ae5cde3a282 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 16 Aug 2023 17:28:49 +0530 Subject: [PATCH 036/178] Added test for proto protocol message --- ably/proto_protocol_message_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/ably/proto_protocol_message_test.go b/ably/proto_protocol_message_test.go index c93f3f0e8..08f171775 100644 --- a/ably/proto_protocol_message_test.go +++ b/ably/proto_protocol_message_test.go @@ -137,3 +137,25 @@ func TestIfHasFlg(t *testing.T) { assert.False(t, flags.Has(ably.FlagHasBacklog), "Shouldn't contain flag %v", ably.FlagHasBacklog) } + +func TestPresenceMessagesShouldReturnErrorFor(t *testing.T) { + oldPresenceMsg := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:12:0", + Timestamp: 123, + ConnectionID: "123", + }, + } + newPresenceMessage := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:12:1", + Timestamp: 124, + ConnectionID: "123", + }, + } + isNewMsg, err := oldPresenceMsg.IsNewerThan(newPresenceMessage) + assert.Nil(t, err) + assert.False(t, isNewMsg) +} From c4122f4d3b8891f75ded35c3252e640576770a3d Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 16 Aug 2023 19:05:42 +0530 Subject: [PATCH 037/178] commented out unused query variable, added tests for presence newness --- ably/proto_protocol_message_test.go | 85 ++++++++++++++++++++- ably/realtime_conn_spec_integration_test.go | 4 +- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/ably/proto_protocol_message_test.go b/ably/proto_protocol_message_test.go index 08f171775..763a731d7 100644 --- a/ably/proto_protocol_message_test.go +++ b/ably/proto_protocol_message_test.go @@ -5,6 +5,7 @@ package ably_test import ( "bytes" + "fmt" "strconv" "testing" @@ -138,7 +139,33 @@ func TestIfHasFlg(t *testing.T) { "Shouldn't contain flag %v", ably.FlagHasBacklog) } -func TestPresenceMessagesShouldReturnErrorFor(t *testing.T) { +func TestPresenceCheckForNewNessByTimestampIfSynthesized_RTP2b1(t *testing.T) { + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:12:1", + Timestamp: 125, + ConnectionID: "987", + }, + } + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:12:2", + Timestamp: 123, + ConnectionID: "784", + }, + } + isNewMsg, err := presenceMsg1.IsNewerThan(presenceMsg2) + assert.Nil(t, err) + assert.True(t, isNewMsg) + + isNewMsg, err = presenceMsg2.IsNewerThan(presenceMsg1) + assert.Nil(t, err) + assert.False(t, isNewMsg) +} + +func TestPresenceCheckForNewNessBySerialIfNotSynthesized__RTP2b2(t *testing.T) { oldPresenceMsg := &ably.PresenceMessage{ Action: ably.PresenceActionPresent, Message: ably.Message{ @@ -151,11 +178,65 @@ func TestPresenceMessagesShouldReturnErrorFor(t *testing.T) { Action: ably.PresenceActionPresent, Message: ably.Message{ ID: "123:12:1", - Timestamp: 124, + Timestamp: 123, ConnectionID: "123", }, } isNewMsg, err := oldPresenceMsg.IsNewerThan(newPresenceMessage) assert.Nil(t, err) assert.False(t, isNewMsg) + + isNewMsg, err = newPresenceMessage.IsNewerThan(oldPresenceMsg) + assert.Nil(t, err) + assert.True(t, isNewMsg) +} + +func TestPresenceMessagesShouldReturnErrorForWrongMessageSerials__RTP2b2(t *testing.T) { + // Both has invalid msgserial + msg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:1a:0", + Timestamp: 123, + ConnectionID: "123", + }, + } + + msg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:1b:1", + Timestamp: 124, + ConnectionID: "123", + }, + } + isNewMsg, err := msg1.IsNewerThan(msg2) + assert.NotNil(t, err) + assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1a:0") + assert.False(t, isNewMsg) + + isNewMsg, err = msg2.IsNewerThan(msg1) + assert.NotNil(t, err) + assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1b:1") + assert.False(t, isNewMsg) + + // msg2 has valid messageSerial + msg2 = &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:10:0", + Timestamp: 124, + ConnectionID: "123", + }, + } + + isNewMsg, err = msg1.IsNewerThan(msg2) + assert.NotNil(t, err) + assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1a:0") + assert.False(t, isNewMsg) + + isNewMsg, err = msg2.IsNewerThan(msg1) + assert.NotNil(t, err) + assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1a:0") + assert.True(t, isNewMsg) } diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 408cbb2e7..d00994ea5 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -2014,11 +2014,11 @@ func TestRealtimeConn_RTN16(t *testing.T) { { //(RTN16e) // This test was adopted from the ably-js project // https://github.com/ably/ably-js/blob/340e5ce31dc9d7434a06ae4e1eec32bdacc9c6c5/spec/realtime/connection.test.js#L119 - var query url.Values + // var query url.Values client2 := app.NewRealtime( ably.WithRecover("_____!ablygo_test_fake-key____:5:3"), ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { - query = u.Query() + // query = u.Query() return ably.DialWebsocket(protocol, u, timeout) })) defer safeclose(t, ablytest.FullRealtimeCloser(client2)) From b30a9f6b7b45f0a1fcd62e3d1506be18db1a988a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 16 Aug 2023 23:01:57 +0530 Subject: [PATCH 038/178] Added separate method to register error handler and published the same --- ably/error.go | 4 ++++ ably/realtime_channel.go | 12 ++++++++++++ ably/realtime_presence.go | 3 ++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ably/error.go b/ably/error.go index d88ff9021..92bf81832 100644 --- a/ably/error.go +++ b/ably/error.go @@ -58,6 +58,10 @@ type ErrorInfo struct { err error } +type errorMessage ErrorInfo + +func (*errorMessage) isEmitterData() {} + // Error implements the builtin error interface. func (e ErrorInfo) Error() string { errorHref := e.HRef diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 67a0658b4..5a3fecb42 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -233,6 +233,7 @@ type RealtimeChannel struct { client *Realtime messageEmitter *eventEmitter + errorEmitter *eventEmitter queue *msgQueue options *channelOptions @@ -547,6 +548,17 @@ func (c *RealtimeChannel) SubscribeAll(ctx context.Context, handle func(*Message return unsubscribe, nil } +// SubscribeAll registers an event listener for error messages on this channel. +// +// See package-level documentation => [ably] Event Emitters for details about messages dispatch. +func (c *RealtimeChannel) OnError(handle func(*ErrorInfo)) (func(), error) { + // unsubscribe deregisters all listeners to error messages on this channel. + unsubscribe := c.errorEmitter.OnAll(func(err emitterData) { + handle((*ErrorInfo)(err.(*errorMessage))) + }) + return unsubscribe, nil +} + type channelStateChanges chan ChannelStateChange func (c channelStateChanges) Receive(change ChannelStateChange) { diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 5dfb5f2f3..e7733aa45 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -202,7 +202,8 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS isMemberNew, err := presenceMember.IsNewerThan(existingMember) // RTP2b if err != nil { pres.log().Error(err) - // TODO - publish channel error event here without state change + errorInfo := newError(0, err) + pres.channel.errorEmitter.Emit(subscriptionName("error"), (*errorMessage)(errorInfo)) } if !isMemberNew { continue // do not accept if incoming member is old From 2b151c1728217a1fe6cc2fbc9a0dcc71a1b8cc63 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 20 Aug 2023 19:39:08 +0530 Subject: [PATCH 039/178] Updated presence member action cases for enter, update and present --- ably/realtime_presence.go | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index e7733aa45..a852cfc73 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -174,23 +174,20 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS } // RTP17 + // TODO - Add newness check for incoming message for _, presenceMember := range msg.Presence { memberKey := presenceMember.ClientID if pres.channel.client.Connection.id != presenceMember.ConnectionID { continue } switch presenceMember.Action { - case PresenceActionEnter: - pres.members[memberKey] = presenceMember - case PresenceActionUpdate: + case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: presenceMember.Action = PresenceActionPresent - fallthrough - case PresenceActionPresent: - delete(pres.stale, memberKey) pres.members[memberKey] = presenceMember case PresenceActionLeave: - // todo : check for synthesized leave - delete(pres.members, memberKey) + if !presenceMember.isServerSynthesized() { + delete(pres.members, memberKey) + } } } @@ -210,12 +207,8 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS } } switch presenceMember.Action { - case PresenceActionEnter: - pres.members[memberKey] = presenceMember - case PresenceActionUpdate: + case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: presenceMember.Action = PresenceActionPresent - fallthrough - case PresenceActionPresent: delete(pres.stale, memberKey) pres.members[memberKey] = presenceMember case PresenceActionLeave: From 78d0a1aacba043d141b40aa28845737cf2d4afe4 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 20 Aug 2023 21:17:51 +0530 Subject: [PATCH 040/178] Added method to realtime presence to add and remove presence member from the map --- ably/realtime_presence.go | 76 +++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index a852cfc73..45f45c228 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -162,6 +162,40 @@ func (pres *RealtimePresence) syncEnd() { pres.syncMtx.Unlock() } +func (pres *RealtimePresence) addPresenceMember(memberMap map[string]*PresenceMessage, memberKey string, presenceMember *PresenceMessage) bool { + if existingMember, ok := memberMap[memberKey]; ok { // RTP2a + isMemberNew, err := presenceMember.IsNewerThan(existingMember) // RTP2b + if err != nil { + pres.log().Error(err) + errorInfo := newError(0, err) + pres.channel.errorEmitter.Emit(subscriptionName("error"), (*errorMessage)(errorInfo)) + } + if isMemberNew { + memberMap[memberKey] = presenceMember + return true + } + return false + } + memberMap[memberKey] = presenceMember + return true +} + +func (pres *RealtimePresence) removePresenceMember(memberMap map[string]*PresenceMessage, memberKey string, presenceMember *PresenceMessage) bool { + if existingMember, ok := memberMap[memberKey]; ok { // RTP2a + isMemberNew, err := presenceMember.IsNewerThan(existingMember) // RTP2b + if err != nil { + pres.log().Error(err) + errorInfo := newError(0, err) + pres.channel.errorEmitter.Emit(subscriptionName("error"), (*errorMessage)(errorInfo)) + } + if isMemberNew { + delete(memberMap, memberKey) + return true + } + } + return false +} + func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncSerial string) { for _, presmsg := range msg.Presence { if presmsg.Timestamp == 0 { @@ -173,48 +207,36 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS pres.syncStart(syncSerial) } - // RTP17 - // TODO - Add newness check for incoming message + // Update presence map / channel's member state. + newPresenceMessages := make([]*PresenceMessage, 0, len(msg.Presence)) for _, presenceMember := range msg.Presence { - memberKey := presenceMember.ClientID - if pres.channel.client.Connection.id != presenceMember.ConnectionID { - continue - } + memberKey := presenceMember.ConnectionID + presenceMember.ClientID switch presenceMember.Action { case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: presenceMember.Action = PresenceActionPresent - pres.members[memberKey] = presenceMember + delete(pres.stale, memberKey) + pres.addPresenceMember(pres.members, memberKey, presenceMember) case PresenceActionLeave: - if !presenceMember.isServerSynthesized() { - delete(pres.members, memberKey) - } + pres.removePresenceMember(pres.members, memberKey, presenceMember) } + newPresenceMessages = append(newPresenceMessages, presenceMember) } - // Update presence map / channel's member state. - newPresenceMessages := make([]*PresenceMessage, 0, len(msg.Presence)) + // RTP17 - Update internal presence map for _, presenceMember := range msg.Presence { - memberKey := presenceMember.ConnectionID + presenceMember.ClientID - if existingMember, ok := pres.members[memberKey]; ok { // RTP2a - isMemberNew, err := presenceMember.IsNewerThan(existingMember) // RTP2b - if err != nil { - pres.log().Error(err) - errorInfo := newError(0, err) - pres.channel.errorEmitter.Emit(subscriptionName("error"), (*errorMessage)(errorInfo)) - } - if !isMemberNew { - continue // do not accept if incoming member is old - } + memberKey := presenceMember.ClientID + if pres.channel.client.Connection.id != presenceMember.ConnectionID { + continue } switch presenceMember.Action { case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: presenceMember.Action = PresenceActionPresent - delete(pres.stale, memberKey) - pres.members[memberKey] = presenceMember + pres.addPresenceMember(pres.internalMembers, memberKey, presenceMember) case PresenceActionLeave: - delete(pres.members, memberKey) + if !presenceMember.isServerSynthesized() { + pres.removePresenceMember(pres.internalMembers, memberKey, presenceMember) + } } - newPresenceMessages = append(newPresenceMessages, presenceMember) } if syncSerial == "" { From f06f09e0989d9af6cfb1004f47ace13b476a14d6 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 20 Aug 2023 21:44:07 +0530 Subject: [PATCH 041/178] Created shallow copy of presence messages to avoid presence action mutation --- ably/realtime_presence.go | 43 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 45f45c228..6260ad056 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -207,21 +207,6 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS pres.syncStart(syncSerial) } - // Update presence map / channel's member state. - newPresenceMessages := make([]*PresenceMessage, 0, len(msg.Presence)) - for _, presenceMember := range msg.Presence { - memberKey := presenceMember.ConnectionID + presenceMember.ClientID - switch presenceMember.Action { - case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: - presenceMember.Action = PresenceActionPresent - delete(pres.stale, memberKey) - pres.addPresenceMember(pres.members, memberKey, presenceMember) - case PresenceActionLeave: - pres.removePresenceMember(pres.members, memberKey, presenceMember) - } - newPresenceMessages = append(newPresenceMessages, presenceMember) - } - // RTP17 - Update internal presence map for _, presenceMember := range msg.Presence { memberKey := presenceMember.ClientID @@ -230,8 +215,9 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS } switch presenceMember.Action { case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: - presenceMember.Action = PresenceActionPresent - pres.addPresenceMember(pres.internalMembers, memberKey, presenceMember) + presenceMemberShallowCopy := presenceMember + presenceMemberShallowCopy.Action = PresenceActionPresent + pres.addPresenceMember(pres.internalMembers, memberKey, presenceMemberShallowCopy) case PresenceActionLeave: if !presenceMember.isServerSynthesized() { pres.removePresenceMember(pres.internalMembers, memberKey, presenceMember) @@ -239,12 +225,31 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS } } + // Update presence map / channel's member state. + updatedPresenceMessages := make([]*PresenceMessage, 0, len(msg.Presence)) + for _, presenceMember := range msg.Presence { + memberKey := presenceMember.ConnectionID + presenceMember.ClientID + memberUpdated := false + switch presenceMember.Action { + case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: + delete(pres.stale, memberKey) + presenceMemberShallowCopy := presenceMember + presenceMemberShallowCopy.Action = PresenceActionPresent + memberUpdated = pres.addPresenceMember(pres.members, memberKey, presenceMemberShallowCopy) + case PresenceActionLeave: + memberUpdated = pres.removePresenceMember(pres.members, memberKey, presenceMember) + } + if memberUpdated { + updatedPresenceMessages = append(updatedPresenceMessages, presenceMember) + } + } + if syncSerial == "" { pres.syncEnd() } pres.mtx.Unlock() - msg.Count = len(newPresenceMessages) - msg.Presence = newPresenceMessages + msg.Count = len(updatedPresenceMessages) + msg.Presence = updatedPresenceMessages for _, msg := range msg.Presence { pres.messageEmitter.Emit(msg.Action, (*subscriptionPresenceMessage)(msg)) } From d40c5f63ea012336ffcf3dabf660a6af55b949a5 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 20 Aug 2023 21:52:32 +0530 Subject: [PATCH 042/178] For removed member, return false if existing member is absent --- ably/realtime_presence.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 6260ad056..bcc8fd10f 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -190,7 +190,7 @@ func (pres *RealtimePresence) removePresenceMember(memberMap map[string]*Presenc } if isMemberNew { delete(memberMap, memberKey) - return true + return existingMember.Action != PresenceActionAbsent } } return false From 3cec497a6439d2ec69615fcc268e24b7702c27ed Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 20 Aug 2023 23:11:13 +0530 Subject: [PATCH 043/178] Annotated realtime presence code with spec ids --- ably/proto_presence_message.go | 1 + ably/realtime_presence.go | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ably/proto_presence_message.go b/ably/proto_presence_message.go index 63140637f..b8d1ab99b 100644 --- a/ably/proto_presence_message.go +++ b/ably/proto_presence_message.go @@ -81,6 +81,7 @@ func (msg *PresenceMessage) getMsgSerialAndIndex() (int64, int64, error) { return msgSerial, msgIndex, nil } +// RTP2b, RTP2c func (msg1 *PresenceMessage) IsNewerThan(msg2 *PresenceMessage) (bool, error) { // RTP2b1 if msg1.isServerSynthesized() || msg2.isServerSynthesized() { diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index bcc8fd10f..3694a5e86 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -100,7 +100,7 @@ func (pres *RealtimePresence) syncWait() { pres.syncMtx.Unlock() } -func syncSerial(msg *protocolMessage) string { +func syncSerial(msg *protocolMessage) string { // RTP18 if i := strings.IndexRune(msg.ChannelSerial, ':'); i != -1 { return msg.ChannelSerial[i+1:] } @@ -203,22 +203,22 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS } } pres.mtx.Lock() - if syncSerial != "" { + if syncSerial != "" { //RTP2g pres.syncStart(syncSerial) } // RTP17 - Update internal presence map for _, presenceMember := range msg.Presence { - memberKey := presenceMember.ClientID + memberKey := presenceMember.ClientID // RTP17h if pres.channel.client.Connection.id != presenceMember.ConnectionID { continue } switch presenceMember.Action { - case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: - presenceMemberShallowCopy := presenceMember + case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: // RTP2d, RTP17b + presenceMemberShallowCopy := presenceMember // RTP2g shouldn't mutate action for next loop presenceMemberShallowCopy.Action = PresenceActionPresent pres.addPresenceMember(pres.internalMembers, memberKey, presenceMemberShallowCopy) - case PresenceActionLeave: + case PresenceActionLeave: // RTP17b, RTP2e if !presenceMember.isServerSynthesized() { pres.removePresenceMember(pres.internalMembers, memberKey, presenceMember) } @@ -231,12 +231,12 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS memberKey := presenceMember.ConnectionID + presenceMember.ClientID memberUpdated := false switch presenceMember.Action { - case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: + case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: // RTP2d delete(pres.stale, memberKey) presenceMemberShallowCopy := presenceMember - presenceMemberShallowCopy.Action = PresenceActionPresent + presenceMemberShallowCopy.Action = PresenceActionPresent // RTP2g memberUpdated = pres.addPresenceMember(pres.members, memberKey, presenceMemberShallowCopy) - case PresenceActionLeave: + case PresenceActionLeave: // RTP2e memberUpdated = pres.removePresenceMember(pres.members, memberKey, presenceMember) } if memberUpdated { @@ -251,7 +251,7 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS msg.Count = len(updatedPresenceMessages) msg.Presence = updatedPresenceMessages for _, msg := range msg.Presence { - pres.messageEmitter.Emit(msg.Action, (*subscriptionPresenceMessage)(msg)) + pres.messageEmitter.Emit(msg.Action, (*subscriptionPresenceMessage)(msg)) // RTP2g } } From 78abf8f15d216af8eb716af1689e04f0412b56c9 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 21 Aug 2023 21:44:17 +0530 Subject: [PATCH 044/178] Implemented spec RTP19 for leaving residual presence members --- ably/realtime_presence.go | 65 ++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 3694a5e86..1c6e23343 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" "sync" + "time" ) type syncState uint8 @@ -19,17 +20,17 @@ const ( // It allows entering, leaving and updating presence state for the current client or on behalf of other client. // It enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel. type RealtimePresence struct { - mtx sync.Mutex - data interface{} - serial string - messageEmitter *eventEmitter - channel *RealtimeChannel - members map[string]*PresenceMessage - internalMembers map[string]*PresenceMessage // RTP17 - stale map[string]struct{} - state PresenceAction - syncMtx sync.Mutex - syncState syncState + mtx sync.Mutex + data interface{} + serial string + messageEmitter *eventEmitter + channel *RealtimeChannel + members map[string]*PresenceMessage + internalMembers map[string]*PresenceMessage // RTP17 + syncResidualMembers map[string]*PresenceMessage + state PresenceAction + syncMtx sync.Mutex + syncState syncState } func newRealtimePresence(channel *RealtimeChannel) *RealtimePresence { @@ -111,12 +112,14 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage) { serial := syncSerial(msg) pres.mtx.Lock() defer pres.mtx.Unlock() - switch { - case msg.Flags.Has(flagHasPresence): + if msg.Flags.Has(flagHasPresence) { pres.syncStart(serial) - case pres.syncState == syncInitial: - pres.syncState = syncComplete - pres.syncMtx.Unlock() + } else { + pres.leaveMembers(pres.members) // RTP19a + if pres.syncState == syncInitial { + pres.syncState = syncComplete + pres.syncMtx.Unlock() + } } } @@ -137,9 +140,22 @@ func (pres *RealtimePresence) syncStart(serial string) { } pres.serial = serial pres.syncState = syncInProgress - pres.stale = make(map[string]struct{}, len(pres.members)) - for memberKey := range pres.members { - pres.stale[memberKey] = struct{}{} + pres.syncResidualMembers = make(map[string]*PresenceMessage, len(pres.members)) // RTP19 + for memberKey, member := range pres.members { + pres.syncResidualMembers[memberKey] = member + } +} + +// RTP19, RTP19a +func (pres *RealtimePresence) leaveMembers(members map[string]*PresenceMessage) { + for memberKey := range members { // RTP19 + delete(pres.members, memberKey) + } + for _, msg := range members { + msg.Action = PresenceActionLeave + msg.ID = "" + msg.Timestamp = time.Now().UnixMilli() + pres.messageEmitter.Emit(msg.Action, (*subscriptionPresenceMessage)(msg)) // RTP2g } } @@ -147,15 +163,14 @@ func (pres *RealtimePresence) syncEnd() { if pres.syncState != syncInProgress { return } - for memberKey := range pres.stale { - delete(pres.members, memberKey) - } - for memberKey, presence := range pres.members { + pres.leaveMembers(pres.syncResidualMembers) // RTP19 + + for memberKey, presence := range pres.members { // RTP2f if presence.Action == PresenceActionAbsent { delete(pres.members, memberKey) } } - pres.stale = nil + pres.syncResidualMembers = nil pres.syncState = syncComplete // Sync has completed, unblock all callers to Get(true) waiting // for the sync. @@ -232,7 +247,7 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS memberUpdated := false switch presenceMember.Action { case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: // RTP2d - delete(pres.stale, memberKey) + delete(pres.syncResidualMembers, memberKey) presenceMemberShallowCopy := presenceMember presenceMemberShallowCopy.Action = PresenceActionPresent // RTP2g memberUpdated = pres.addPresenceMember(pres.members, memberKey, presenceMemberShallowCopy) From 9bd5373a1d8ce306ad7a815f2da2a9e1c6f38480 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 21 Aug 2023 23:56:19 +0530 Subject: [PATCH 045/178] Added code to emit enter event from internal members --- ably/realtime_channel.go | 6 ++++-- ably/realtime_presence.go | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 5a3fecb42..cce6ace49 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -805,10 +805,12 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { if msg.Flags != 0 { c.setModes(channelModeFromFlag(msg.Flags)) } - c.Presence.onAttach(msg) + + isNewAttach := c.state != ChannelStateAttached + c.Presence.onAttach(msg, isNewAttach) isAttachResumed := msg.Flags.Has(flagResumed) - if c.state != ChannelStateAttached || !isAttachResumed { //RTL12 + if isNewAttach || !isAttachResumed { //RTL12 c.setState(ChannelStateAttached, newErrorFromProto(msg.Error), isAttachResumed) } c.queue.Flush() diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 1c6e23343..e87d72a1e 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -108,10 +108,15 @@ func syncSerial(msg *protocolMessage) string { // RTP18 return "" } -func (pres *RealtimePresence) onAttach(msg *protocolMessage) { +func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { serial := syncSerial(msg) pres.mtx.Lock() defer pres.mtx.Unlock() + if isNewAttach { + for _, member := range pres.internalMembers { + pres.EnterClient(context.Background(), member.ClientID, member.Data) + } + } if msg.Flags.Has(flagHasPresence) { pres.syncStart(serial) } else { From 4412548101dc461156e890ee26fea844e2e76f41 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 22 Aug 2023 23:42:16 +0530 Subject: [PATCH 046/178] Refactored enter client presence for internal members, handled error gracefully as per spec --- ably/realtime_presence.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index e87d72a1e..37abd5fcb 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -112,9 +112,21 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { serial := syncSerial(msg) pres.mtx.Lock() defer pres.mtx.Unlock() - if isNewAttach { + if isNewAttach { // RTP17f for _, member := range pres.internalMembers { - pres.EnterClient(context.Background(), member.ClientID, member.Data) + err := pres.enterClient(context.Background(), member.ClientID, member.Data, member.ID) // RTP17g + // RTP17e + if err != nil { + change := ChannelStateChange{ + Current: pres.channel.state, + Previous: pres.channel.state, + Reason: newError(91004, err), + Resumed: true, + Event: ChannelEventUpdate, + } + pres.channel.emitter.Emit(change.Event, change) // + } + pres.log().Errorf("Error for internal member presence enter with id %v, clientId %v, err %v", member.ID, member.ClientID, err) } } if msg.Flags.Has(flagHasPresence) { @@ -464,6 +476,10 @@ func (pres *RealtimePresence) Leave(ctx context.Context, data interface{}) error // If the context is cancelled before the operation finishes, the call returns with an error, // but the operation carries on in the background and presence state may eventually be updated anyway. func (pres *RealtimePresence) EnterClient(ctx context.Context, clientID string, data interface{}) error { + return pres.enterClient(ctx, clientID, data, "") +} + +func (pres *RealtimePresence) enterClient(ctx context.Context, clientID string, data interface{}, msgId string) error { pres.mtx.Lock() pres.data = data pres.state = PresenceActionEnter @@ -473,6 +489,7 @@ func (pres *RealtimePresence) EnterClient(ctx context.Context, clientID string, } msg.Data = data msg.ClientID = clientID + msg.ID = msgId res, err := pres.send(&msg) if err != nil { return err From 16afe49d6bb4f827e4ecfc116bf1803138232130 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 22 Aug 2023 23:50:58 +0530 Subject: [PATCH 047/178] Updated err message for internal member presence enter error --- ably/realtime_presence.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 37abd5fcb..8cb957285 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -117,6 +117,7 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { err := pres.enterClient(context.Background(), member.ClientID, member.Data, member.ID) // RTP17g // RTP17e if err != nil { + pres.log().Errorf("Error for internal member presence enter with id %v, clientId %v, err %v", member.ID, member.ClientID, err) change := ChannelStateChange{ Current: pres.channel.state, Previous: pres.channel.state, @@ -124,9 +125,8 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { Resumed: true, Event: ChannelEventUpdate, } - pres.channel.emitter.Emit(change.Event, change) // + pres.channel.emitter.Emit(change.Event, change) } - pres.log().Errorf("Error for internal member presence enter with id %v, clientId %v, err %v", member.ID, member.ClientID, err) } } if msg.Flags.Has(flagHasPresence) { From 8495354d58efc955db5f005374dd0db409cf91ca Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 26 Aug 2023 17:43:23 +0530 Subject: [PATCH 048/178] Added spec implementation for realtime presence RTP5a/RTP5f --- ably/realtime_presence.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 8cb957285..442f1c2ba 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -57,6 +57,22 @@ func (pres *RealtimePresence) verifyChanState() error { } } +// RTP5a +func (pres *RealtimePresence) onChannelDetachOrFailed(err ErrorInfo) { + for k := range pres.members { + delete(pres.members, k) + } + for k := range pres.internalMembers { + delete(pres.internalMembers, k) + } + // TODO - fail all queued messages +} + +// RTP5f +func (pres *RealtimePresence) onChannelSuspended(err ErrorInfo) { + // TODO - fail all queued messages +} + func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { attached, err := pres.channel.attach() if err != nil { From e94d526c5edfdafa21b087ebc3c98dd9b040b786 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 26 Aug 2023 19:50:02 +0530 Subject: [PATCH 049/178] Updated queuedMessages fail method with onlyPresenceMsg filter --- ably/realtime_channel.go | 4 ++-- ably/realtime_conn.go | 2 +- ably/realtime_presence.go | 4 ++-- ably/state.go | 27 ++++++++++++++++----------- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index cce6ace49..f5d29464a 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -274,7 +274,7 @@ func (c *RealtimeChannel) onConnStateChange(change ConnectionStateChange) { c.queue.Flush() case ConnectionStateFailed: c.setState(ChannelStateFailed, change.Reason, false) - c.queue.Fail(change.Reason) + c.queue.Fail(change.Reason, false) } } @@ -858,7 +858,7 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { c.Presence.processIncomingMessage(msg, "") case actionError: c.setState(ChannelStateFailed, newErrorFromProto(msg.Error), false) - c.queue.Fail(newErrorFromProto(msg.Error)) + c.queue.Fail(newErrorFromProto(msg.Error), false) case actionMessage: if c.State() == ChannelStateAttached { for _, msg := range msg.Messages { diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 0c4f8c0fa..6e6b1490c 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -914,7 +914,7 @@ func (c *Connection) failedConnSideEffects(err *errorInfo) { } c.lockSetState(ConnectionStateFailed, newErrorFromProto(err), 0) c.mtx.Unlock() - c.queue.Fail(newErrorFromProto(err)) + c.queue.Fail(newErrorFromProto(err), false) if c.conn != nil { c.conn.Close() } diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 442f1c2ba..d423bb284 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -65,12 +65,12 @@ func (pres *RealtimePresence) onChannelDetachOrFailed(err ErrorInfo) { for k := range pres.internalMembers { delete(pres.internalMembers, k) } - // TODO - fail all queued messages + pres.channel.queue.Fail(err, true) } // RTP5f func (pres *RealtimePresence) onChannelSuspended(err ErrorInfo) { - // TODO - fail all queued messages + pres.channel.queue.Fail(err, true) } func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { diff --git a/ably/state.go b/ably/state.go index 053bbee59..a12c251c5 100644 --- a/ably/state.go +++ b/ably/state.go @@ -190,14 +190,14 @@ func (q *pendingEmitter) Ack(msg *protocolMessage, errInfo *ErrorInfo) { } } -type msgch struct { +type msgWithAck struct { msg *protocolMessage onAck func(err error) } type msgQueue struct { mtx sync.Mutex - queue []msgch + queue []msgWithAck conn *Connection } @@ -210,28 +210,33 @@ func newMsgQueue(conn *Connection) *msgQueue { func (q *msgQueue) Enqueue(msg *protocolMessage, onAck func(err error)) { q.mtx.Lock() // TODO(rjeczalik): reorder the queue so Presence / Messages can be merged - q.queue = append(q.queue, msgch{msg, onAck}) + q.queue = append(q.queue, msgWithAck{msg, onAck}) q.mtx.Unlock() } func (q *msgQueue) Flush() { q.mtx.Lock() - for _, msgch := range q.queue { - q.conn.send(msgch.msg, msgch.onAck) + for _, msgWithAck := range q.queue { + q.conn.send(msgWithAck.msg, msgWithAck.onAck) } q.queue = nil q.mtx.Unlock() } -func (q *msgQueue) Fail(err error) { +func (q *msgQueue) Fail(err error, onlyPresenceMessages bool) { q.mtx.Lock() - for _, msgch := range q.queue { - q.log().Errorf("failure sending message (serial=%d): %v", msgch.msg.MsgSerial, err) - if msgch.onAck != nil { - msgch.onAck(newError(90000, err)) + var nonFailedQueueMessages []msgWithAck + for _, msgWithAck := range q.queue { + if onlyPresenceMessages && msgWithAck.msg.Action != actionPresence { + nonFailedQueueMessages = append(nonFailedQueueMessages, msgWithAck) + continue + } + q.log().Errorf("failure sending message (serial=%d): %v", msgWithAck.msg.MsgSerial, err) + if msgWithAck.onAck != nil { + msgWithAck.onAck(newError(90000, err)) } } - q.queue = nil + q.queue = nonFailedQueueMessages q.mtx.Unlock() } From 993a7ff8bf69bfec7160bf444a70dc86a6605a01 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 26 Aug 2023 23:10:37 +0530 Subject: [PATCH 050/178] Added spec implementation to handle presence msg queue on channel state changes --- ably/realtime_channel.go | 16 ++++++++++++++-- ably/realtime_client.go | 2 +- ably/realtime_conn.go | 2 +- ably/realtime_presence.go | 5 +++-- ably/state.go | 9 +++++++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index f5d29464a..3e271dec7 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -271,7 +271,7 @@ func newRealtimeChannel(name string, client *Realtime, chOptions *channelOptions func (c *RealtimeChannel) onConnStateChange(change ConnectionStateChange) { switch change.Current { case ConnectionStateConnected: - c.queue.Flush() + c.queue.Flush(false) case ConnectionStateFailed: c.setState(ChannelStateFailed, change.Reason, false) c.queue.Fail(change.Reason, false) @@ -813,7 +813,7 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { if isNewAttach || !isAttachResumed { //RTL12 c.setState(ChannelStateAttached, newErrorFromProto(msg.Error), isAttachResumed) } - c.queue.Flush() + c.queue.Flush(false) case actionDetached: c.mtx.Lock() err := error(newErrorFromProto(msg.Error)) @@ -960,10 +960,22 @@ func (c *RealtimeChannel) setState(state ChannelState, err error, resumed bool) c.mtx.Lock() defer c.mtx.Unlock() + // RT5Pa + if state == ChannelStateDetached || state == ChannelStateFailed { + c.Presence.onChannelDetachedOrFailed(channelStateError(state, err)) + } // RTP5a1 if state == ChannelStateDetached || state == ChannelStateSuspended || state == ChannelStateFailed { c.properties.ChannelSerial = "" } + // RTP5b + if state == ChannelStateAttached { + c.queue.Flush(true) + } + // RTP5f + if state == ChannelStateSuspended { + c.Presence.onChannelSuspended(channelStateError(state, err)) + } return c.lockSetState(state, err, resumed) } diff --git a/ably/realtime_client.go b/ably/realtime_client.go index e5795db21..6e1325580 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -78,7 +78,7 @@ func (c *Realtime) onReconnected(isNewID bool) { // No need to reattach: state is preserved. We just need to flush the // queue of pending messages. for _, ch := range c.Channels.Iterate() { - ch.queue.Flush() + ch.queue.Flush(false) } //RTN19a c.Connection.resendPending() diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 6e6b1490c..ef7d8b4f7 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -871,7 +871,7 @@ func (c *Connection) eventloop() { c.lockSetState(ConnectionStateConnected, newErrorFromProto(msg.Error), 0) c.mtx.Unlock() } - c.queue.Flush() + c.queue.Flush(false) case actionDisconnected: if !isTokenError(msg.Error) { // The spec doesn't say what to do in this case, so do nothing. diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index d423bb284..3770fd1ac 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -58,7 +58,7 @@ func (pres *RealtimePresence) verifyChanState() error { } // RTP5a -func (pres *RealtimePresence) onChannelDetachOrFailed(err ErrorInfo) { +func (pres *RealtimePresence) onChannelDetachedOrFailed(err error) { for k := range pres.members { delete(pres.members, k) } @@ -69,7 +69,7 @@ func (pres *RealtimePresence) onChannelDetachOrFailed(err ErrorInfo) { } // RTP5f -func (pres *RealtimePresence) onChannelSuspended(err ErrorInfo) { +func (pres *RealtimePresence) onChannelSuspended(err error) { pres.channel.queue.Fail(err, true) } @@ -128,6 +128,7 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { serial := syncSerial(msg) pres.mtx.Lock() defer pres.mtx.Unlock() + // TODO - need to move this logic only when channel enters attached state if isNewAttach { // RTP17f for _, member := range pres.internalMembers { err := pres.enterClient(context.Background(), member.ClientID, member.Data, member.ID) // RTP17g diff --git a/ably/state.go b/ably/state.go index a12c251c5..0fb412336 100644 --- a/ably/state.go +++ b/ably/state.go @@ -214,12 +214,17 @@ func (q *msgQueue) Enqueue(msg *protocolMessage, onAck func(err error)) { q.mtx.Unlock() } -func (q *msgQueue) Flush() { +func (q *msgQueue) Flush(onlyPresenceMessages bool) { q.mtx.Lock() + var pendingQueuedMessages []msgWithAck for _, msgWithAck := range q.queue { + if onlyPresenceMessages && msgWithAck.msg.Action != actionPresence { + pendingQueuedMessages = append(pendingQueuedMessages, msgWithAck) + continue + } q.conn.send(msgWithAck.msg, msgWithAck.onAck) } - q.queue = nil + q.queue = pendingQueuedMessages q.mtx.Unlock() } From 4e4a238836ec3bcd0436f21f198d6596170fb48a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 27 Aug 2023 22:26:44 +0530 Subject: [PATCH 051/178] Checking channel states before processing presence messages --- ably/realtime_conn.go | 1 + ably/realtime_presence.go | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index ef7d8b4f7..c7d101016 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -606,6 +606,7 @@ func (c *Connection) advanceSerial() { func (c *Connection) send(msg *protocolMessage, onAck func(err error)) { hasMsgSerial := msg.Action == actionMessage || msg.Action == actionPresence c.mtx.Lock() + // RTP16a - in case of presence msg send, check for connection status and send accordingly switch state := c.state; state { default: c.mtx.Unlock() diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 3770fd1ac..42c8e8c9d 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -48,9 +48,10 @@ func newRealtimePresence(channel *RealtimeChannel) *RealtimePresence { return pres } +// RTP16c func (pres *RealtimePresence) verifyChanState() error { switch state := pres.channel.State(); state { - case ChannelStateDetached, ChannelStateDetaching, ChannelStateFailed: + case ChannelStateDetaching, ChannelStateDetached, ChannelStateFailed, ChannelStateSuspended: return newError(91001, fmt.Errorf("unable to enter presence channel (invalid channel state: %s)", state.String())) default: return nil @@ -74,13 +75,17 @@ func (pres *RealtimePresence) onChannelSuspended(err error) { } func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { + // No need to do RTP16b since below action will make sure it goes + // into attached or failed states attached, err := pres.channel.attach() if err != nil { return nil, err } + // RTP16c if err := pres.verifyChanState(); err != nil { return nil, err } + // RTP16 - state is attached at this stage protomsg := &protocolMessage{ Action: actionPresence, Channel: pres.channel.Name, From 288ffb6eda2733238c40d055c3a354e67173cfa2 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 28 Aug 2023 23:48:37 +0530 Subject: [PATCH 052/178] Updated send method for realtimepresence --- ably/realtime_presence.go | 42 ++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 42c8e8c9d..53f65a1a6 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -75,12 +75,6 @@ func (pres *RealtimePresence) onChannelSuspended(err error) { } func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { - // No need to do RTP16b since below action will make sure it goes - // into attached or failed states - attached, err := pres.channel.attach() - if err != nil { - return nil, err - } // RTP16c if err := pres.verifyChanState(); err != nil { return nil, err @@ -91,27 +85,25 @@ func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { Channel: pres.channel.Name, Presence: []*PresenceMessage{msg}, } - return resultFunc(func(ctx context.Context) error { - err := attached.Wait(ctx) - if err != nil { - return err - } + if pres.channel.state == ChannelStateAttached { + return resultFunc(func(ctx context.Context) error { - listen := make(chan error, 1) - onAck := func(err error) { - listen <- err - } - if err := pres.channel.send(protomsg, onAck); err != nil { - return err - } + listen := make(chan error, 1) + onAck := func(err error) { + listen <- err + } + if err := pres.channel.send(protomsg, onAck); err != nil { + return err + } - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-listen: - return err - } - }), nil + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-listen: + return err + } + }), nil + } } func (pres *RealtimePresence) syncWait() { From a8c62902b2e695bd62d0fa08ad4c73f5b4480863 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 28 Aug 2023 23:52:29 +0530 Subject: [PATCH 053/178] refactored presence message send method --- ably/realtime_presence.go | 49 +++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 53f65a1a6..397220cba 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -79,31 +79,34 @@ func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { if err := pres.verifyChanState(); err != nil { return nil, err } - // RTP16 - state is attached at this stage - protomsg := &protocolMessage{ - Action: actionPresence, - Channel: pres.channel.Name, - Presence: []*PresenceMessage{msg}, - } - if pres.channel.state == ChannelStateAttached { - return resultFunc(func(ctx context.Context) error { - - listen := make(chan error, 1) - onAck := func(err error) { - listen <- err - } - if err := pres.channel.send(protomsg, onAck); err != nil { - return err - } + presenceSendFunc := func(ctx context.Context) error { + protomsg := &protocolMessage{ + Action: actionPresence, + Channel: pres.channel.Name, + Presence: []*PresenceMessage{msg}, + } + listen := make(chan error, 1) + onAck := func(err error) { + listen <- err + } + if err := pres.channel.send(protomsg, onAck); err != nil { + return err + } - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-listen: - return err - } - }), nil + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-listen: + return err + } + } + if pres.channel.state == ChannelStateInitialized { + _, err := pres.channel.attach() + if err != nil { + return nil, err + } } + return resultFunc(presenceSendFunc), nil } func (pres *RealtimePresence) syncWait() { From 62a1513b209434371a5c5c460d6bbcb91bf835af Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 28 Aug 2023 23:56:10 +0530 Subject: [PATCH 054/178] Added a separate method for enqueing presence message --- ably/realtime_presence.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 397220cba..c92264b11 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -109,6 +109,10 @@ func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { return resultFunc(presenceSendFunc), nil } +func (pres *RealtimePresence) enqueuePresenceMsg() { + +} + func (pres *RealtimePresence) syncWait() { // If there's an undergoing sync operation or we wait till channel gets // attached, the following lock is going to block until the operations From 55bf6a426dffc1edbd7cc3bf099a57af32c82bd1 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 29 Aug 2023 21:36:13 +0530 Subject: [PATCH 055/178] Fixed enqueue messages in realtime_conn when queuing is not enabled --- ably/realtime_conn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index c7d101016..c8ed64674 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -620,9 +620,9 @@ func (c *Connection) send(msg *protocolMessage, onAck func(err error)) { if onAck != nil { onAck(connStateError(state, errQueueing)) } + } else { + c.queue.Enqueue(msg, onAck) // RTL4i } - c.queue.Enqueue(msg, onAck) // RTL4i - case ConnectionStateConnected: if err := c.verifyAndUpdateMessages(msg); err != nil { c.mtx.Unlock() From c41ddeb3cc3abbe6c1433b3504ea54515a9fa910 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 29 Aug 2023 21:36:47 +0530 Subject: [PATCH 056/178] Added a separate message queue to realtime presence --- ably/realtime_channel.go | 10 +++---- ably/realtime_client.go | 2 +- ably/realtime_presence.go | 62 +++++++++++++++++++++++---------------- ably/state.go | 30 +++++++------------ 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 3e271dec7..bcb108277 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -271,10 +271,10 @@ func newRealtimeChannel(name string, client *Realtime, chOptions *channelOptions func (c *RealtimeChannel) onConnStateChange(change ConnectionStateChange) { switch change.Current { case ConnectionStateConnected: - c.queue.Flush(false) + c.queue.Flush() case ConnectionStateFailed: c.setState(ChannelStateFailed, change.Reason, false) - c.queue.Fail(change.Reason, false) + c.queue.Fail(change.Reason) } } @@ -813,7 +813,7 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { if isNewAttach || !isAttachResumed { //RTL12 c.setState(ChannelStateAttached, newErrorFromProto(msg.Error), isAttachResumed) } - c.queue.Flush(false) + c.queue.Flush() case actionDetached: c.mtx.Lock() err := error(newErrorFromProto(msg.Error)) @@ -858,7 +858,7 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { c.Presence.processIncomingMessage(msg, "") case actionError: c.setState(ChannelStateFailed, newErrorFromProto(msg.Error), false) - c.queue.Fail(newErrorFromProto(msg.Error), false) + c.queue.Fail(newErrorFromProto(msg.Error)) case actionMessage: if c.State() == ChannelStateAttached { for _, msg := range msg.Messages { @@ -970,7 +970,7 @@ func (c *RealtimeChannel) setState(state ChannelState, err error, resumed bool) } // RTP5b if state == ChannelStateAttached { - c.queue.Flush(true) + c.queue.Flush() } // RTP5f if state == ChannelStateSuspended { diff --git a/ably/realtime_client.go b/ably/realtime_client.go index 6e1325580..e5795db21 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -78,7 +78,7 @@ func (c *Realtime) onReconnected(isNewID bool) { // No need to reattach: state is preserved. We just need to flush the // queue of pending messages. for _, ch := range c.Channels.Iterate() { - ch.queue.Flush(false) + ch.queue.Flush() } //RTN19a c.Connection.resendPending() diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index c92264b11..0a28bcf66 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -2,6 +2,7 @@ package ably import ( "context" + "errors" "fmt" "strings" "sync" @@ -31,6 +32,7 @@ type RealtimePresence struct { state PresenceAction syncMtx sync.Mutex syncState syncState + queue *msgQueue } func newRealtimePresence(channel *RealtimeChannel) *RealtimePresence { @@ -41,6 +43,7 @@ func newRealtimePresence(channel *RealtimeChannel) *RealtimePresence { internalMembers: make(map[string]*PresenceMessage), syncState: syncInitial, } + pres.queue = newMsgQueue(pres.channel.client.Connection) // Lock syncMtx to make all callers to Get(true) wait until the presence // is in initial sync state. This is to not make them early return // with an empty presence list before channel attaches. @@ -66,12 +69,23 @@ func (pres *RealtimePresence) onChannelDetachedOrFailed(err error) { for k := range pres.internalMembers { delete(pres.internalMembers, k) } - pres.channel.queue.Fail(err, true) + pres.queue.Fail(err) } // RTP5f func (pres *RealtimePresence) onChannelSuspended(err error) { - pres.channel.queue.Fail(err, true) + pres.queue.Fail(err) +} + +func (pres *RealtimePresence) maybeEnqueue(msg *protocolMessage, onAck func(err error)) bool { + if pres.channel.opts().NoQueueing { + if onAck != nil { + onAck(errors.New("unable enqueue message because Options.QueueMessages is set to false")) + } + return false + } + pres.queue.Enqueue(msg, onAck) + return true } func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { @@ -79,38 +93,34 @@ func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { if err := pres.verifyChanState(); err != nil { return nil, err } - presenceSendFunc := func(ctx context.Context) error { - protomsg := &protocolMessage{ - Action: actionPresence, - Channel: pres.channel.Name, - Presence: []*PresenceMessage{msg}, - } - listen := make(chan error, 1) - onAck := func(err error) { - listen <- err - } - if err := pres.channel.send(protomsg, onAck); err != nil { - return err + protomsg := &protocolMessage{ + Action: actionPresence, + Channel: pres.channel.Name, + Presence: []*PresenceMessage{msg}, + } + listen := make(chan error, 1) + onAck := func(err error) { + listen <- err + } + switch pres.channel.state { + case ChannelStateInitialized: + if pres.maybeEnqueue(protomsg, onAck) { + pres.channel.attach() } + case ChannelStateAttaching: + pres.maybeEnqueue(protomsg, onAck) + case ChannelStateAttached: + pres.channel.client.Connection.send(protomsg, onAck) + } + return resultFunc(func(ctx context.Context) error { select { case <-ctx.Done(): return ctx.Err() case err := <-listen: return err } - } - if pres.channel.state == ChannelStateInitialized { - _, err := pres.channel.attach() - if err != nil { - return nil, err - } - } - return resultFunc(presenceSendFunc), nil -} - -func (pres *RealtimePresence) enqueuePresenceMsg() { - + }), nil } func (pres *RealtimePresence) syncWait() { diff --git a/ably/state.go b/ably/state.go index 0fb412336..c742fe9ac 100644 --- a/ably/state.go +++ b/ably/state.go @@ -214,34 +214,24 @@ func (q *msgQueue) Enqueue(msg *protocolMessage, onAck func(err error)) { q.mtx.Unlock() } -func (q *msgQueue) Flush(onlyPresenceMessages bool) { +func (q *msgQueue) Flush() { q.mtx.Lock() - var pendingQueuedMessages []msgWithAck - for _, msgWithAck := range q.queue { - if onlyPresenceMessages && msgWithAck.msg.Action != actionPresence { - pendingQueuedMessages = append(pendingQueuedMessages, msgWithAck) - continue - } - q.conn.send(msgWithAck.msg, msgWithAck.onAck) + for _, msgch := range q.queue { + q.conn.send(msgch.msg, msgch.onAck) } - q.queue = pendingQueuedMessages + q.queue = nil q.mtx.Unlock() } -func (q *msgQueue) Fail(err error, onlyPresenceMessages bool) { +func (q *msgQueue) Fail(err error) { q.mtx.Lock() - var nonFailedQueueMessages []msgWithAck - for _, msgWithAck := range q.queue { - if onlyPresenceMessages && msgWithAck.msg.Action != actionPresence { - nonFailedQueueMessages = append(nonFailedQueueMessages, msgWithAck) - continue - } - q.log().Errorf("failure sending message (serial=%d): %v", msgWithAck.msg.MsgSerial, err) - if msgWithAck.onAck != nil { - msgWithAck.onAck(newError(90000, err)) + for _, msgch := range q.queue { + q.log().Errorf("failure sending message (serial=%d): %v", msgch.msg.MsgSerial, err) + if msgch.onAck != nil { + msgch.onAck(newError(90000, err)) } } - q.queue = nonFailedQueueMessages + q.queue = nil q.mtx.Unlock() } From eeb86dccd8f585cbfaee6b8969e548c30b63004b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 29 Aug 2023 23:55:37 +0530 Subject: [PATCH 057/178] Added warn log when transport level conn send error occurs --- ably/realtime_conn.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index c8ed64674..79d97618d 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -642,6 +642,7 @@ func (c *Connection) send(msg *protocolMessage, onAck func(err error)) { // reconnection logic. But in case it isn't, force that by closing the // connection. Otherwise, the message we enqueue here may be in the queue // indefinitely. + c.log().Warnf("transport level failure while sending message, %v", err) c.conn.Close() c.mtx.Unlock() c.queue.Enqueue(msg, onAck) From 73f2823fc93cb160aa3c85ff3f2bcdc8911d8828 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 29 Aug 2023 23:55:57 +0530 Subject: [PATCH 058/178] Added realtime presence method to sendLocalPresenceQueueMessages --- ably/realtime_presence.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 0a28bcf66..7d20d2c10 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -88,6 +88,13 @@ func (pres *RealtimePresence) maybeEnqueue(msg *protocolMessage, onAck func(err return true } +func (pres *RealtimePresence) sendLocalPresenceQueueMessages() { + if len(pres.queue.queue) == 0 { + return + } + protoPresenceMsg := &protocolMessage{Presence: make([]*PresenceMessage, len(pres.queue.queue))} +} + func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { // RTP16c if err := pres.verifyChanState(); err != nil { @@ -169,6 +176,7 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { pres.syncMtx.Unlock() } } + pres.sendLocalPresenceQueueMessages() } // SyncComplete gives true if the initial SYNC operation has completed for the members present on the channel. From cefbf8d8dc0fb2b40ba9352708641fb2e425f1b3 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 30 Aug 2023 17:18:16 +0530 Subject: [PATCH 059/178] Removed unnecessary implementation for queueFlush, sending all messages from queue when channelState is attached --- ably/realtime_presence.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 7d20d2c10..330dfd254 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -88,13 +88,6 @@ func (pres *RealtimePresence) maybeEnqueue(msg *protocolMessage, onAck func(err return true } -func (pres *RealtimePresence) sendLocalPresenceQueueMessages() { - if len(pres.queue.queue) == 0 { - return - } - protoPresenceMsg := &protocolMessage{Presence: make([]*PresenceMessage, len(pres.queue.queue))} -} - func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { // RTP16c if err := pres.verifyChanState(); err != nil { @@ -176,7 +169,7 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { pres.syncMtx.Unlock() } } - pres.sendLocalPresenceQueueMessages() + pres.queue.Flush() } // SyncComplete gives true if the initial SYNC operation has completed for the members present on the channel. From 4cced5f273e6a75139bddac3c8425dd5b51800df Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 30 Aug 2023 17:18:41 +0530 Subject: [PATCH 060/178] Refactored queue flush and fail method in state --- ably/state.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ably/state.go b/ably/state.go index c742fe9ac..08e00f3e7 100644 --- a/ably/state.go +++ b/ably/state.go @@ -216,8 +216,8 @@ func (q *msgQueue) Enqueue(msg *protocolMessage, onAck func(err error)) { func (q *msgQueue) Flush() { q.mtx.Lock() - for _, msgch := range q.queue { - q.conn.send(msgch.msg, msgch.onAck) + for _, queueMsg := range q.queue { + q.conn.send(queueMsg.msg, queueMsg.onAck) } q.queue = nil q.mtx.Unlock() @@ -225,10 +225,10 @@ func (q *msgQueue) Flush() { func (q *msgQueue) Fail(err error) { q.mtx.Lock() - for _, msgch := range q.queue { - q.log().Errorf("failure sending message (serial=%d): %v", msgch.msg.MsgSerial, err) - if msgch.onAck != nil { - msgch.onAck(newError(90000, err)) + for _, queueMsg := range q.queue { + q.log().Errorf("failure sending message (serial=%d): %v", queueMsg.msg.MsgSerial, err) + if queueMsg.onAck != nil { + queueMsg.onAck(newError(90000, err)) } } q.queue = nil From e3c78e62fdc463df0bc6668e0075ff9129be3290 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 30 Aug 2023 18:59:35 +0530 Subject: [PATCH 061/178] Removed unnecessary todo from realtime presence --- ably/realtime_presence.go | 1 - 1 file changed, 1 deletion(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 330dfd254..50be11368 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -142,7 +142,6 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { serial := syncSerial(msg) pres.mtx.Lock() defer pres.mtx.Unlock() - // TODO - need to move this logic only when channel enters attached state if isNewAttach { // RTP17f for _, member := range pres.internalMembers { err := pres.enterClient(context.Background(), member.ClientID, member.Data, member.ID) // RTP17g From 9ff4f48bff86c438ce532a44864a2b87f0d44dc8 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 30 Aug 2023 23:10:50 +0530 Subject: [PATCH 062/178] Fixed queue flush issues with realtime connection --- ably/realtime_conn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 79d97618d..9a60632ba 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -873,7 +873,7 @@ func (c *Connection) eventloop() { c.lockSetState(ConnectionStateConnected, newErrorFromProto(msg.Error), 0) c.mtx.Unlock() } - c.queue.Flush(false) + c.queue.Flush() case actionDisconnected: if !isTokenError(msg.Error) { // The spec doesn't say what to do in this case, so do nothing. @@ -916,7 +916,7 @@ func (c *Connection) failedConnSideEffects(err *errorInfo) { } c.lockSetState(ConnectionStateFailed, newErrorFromProto(err), 0) c.mtx.Unlock() - c.queue.Fail(newErrorFromProto(err), false) + c.queue.Fail(newErrorFromProto(err)) if c.conn != nil { c.conn.Close() } From 6193f1be723b56a668dbfc70bba65fc78e3fe566 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 30 Aug 2023 23:53:36 +0530 Subject: [PATCH 063/178] Refactored realtime presence, annotated with right spec --- ably/realtime_presence.go | 33 +++++++++++++------------ ably/realtime_presence_internal_test.go | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 50be11368..d603d8872 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -52,7 +52,7 @@ func newRealtimePresence(channel *RealtimeChannel) *RealtimePresence { } // RTP16c -func (pres *RealtimePresence) verifyChanState() error { +func (pres *RealtimePresence) isValidChannelState() error { switch state := pres.channel.State(); state { case ChannelStateDetaching, ChannelStateDetached, ChannelStateFailed, ChannelStateSuspended: return newError(91001, fmt.Errorf("unable to enter presence channel (invalid channel state: %s)", state.String())) @@ -69,10 +69,10 @@ func (pres *RealtimePresence) onChannelDetachedOrFailed(err error) { for k := range pres.internalMembers { delete(pres.internalMembers, k) } - pres.queue.Fail(err) + pres.queue.Fail(err) // RTP16b } -// RTP5f +// RTP5f, RTP16b func (pres *RealtimePresence) onChannelSuspended(err error) { pres.queue.Fail(err) } @@ -90,7 +90,7 @@ func (pres *RealtimePresence) maybeEnqueue(msg *protocolMessage, onAck func(err func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { // RTP16c - if err := pres.verifyChanState(); err != nil { + if err := pres.isValidChannelState(); err != nil { return nil, err } protomsg := &protocolMessage{ @@ -103,14 +103,14 @@ func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { listen <- err } switch pres.channel.state { - case ChannelStateInitialized: + case ChannelStateInitialized: // RTP16b if pres.maybeEnqueue(protomsg, onAck) { pres.channel.attach() } - case ChannelStateAttaching: + case ChannelStateAttaching: // RTP16b pres.maybeEnqueue(protomsg, onAck) - case ChannelStateAttached: - pres.channel.client.Connection.send(protomsg, onAck) + case ChannelStateAttached: // RTP16a + pres.channel.client.Connection.send(protomsg, onAck) // RTL6c2 } return resultFunc(func(ctx context.Context) error { @@ -139,7 +139,6 @@ func syncSerial(msg *protocolMessage) string { // RTP18 } func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { - serial := syncSerial(msg) pres.mtx.Lock() defer pres.mtx.Unlock() if isNewAttach { // RTP17f @@ -159,8 +158,9 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { } } } + // RTP1 if msg.Flags.Has(flagHasPresence) { - pres.syncStart(serial) + pres.syncStart(syncSerial(msg)) } else { pres.leaveMembers(pres.members) // RTP19a if pres.syncState == syncInitial { @@ -168,6 +168,7 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { pres.syncMtx.Unlock() } } + // RTP5b pres.queue.Flush() } @@ -266,7 +267,7 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS } } pres.mtx.Lock() - if syncSerial != "" { //RTP2g + if syncSerial != "" { // RTP18a pres.syncStart(syncSerial) } @@ -307,14 +308,14 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS } } - if syncSerial == "" { + if syncSerial == "" { // RTP18b pres.syncEnd() } pres.mtx.Unlock() - msg.Count = len(updatedPresenceMessages) - msg.Presence = updatedPresenceMessages - for _, msg := range msg.Presence { - pres.messageEmitter.Emit(msg.Action, (*subscriptionPresenceMessage)(msg)) // RTP2g + + // RTP2g + for _, msg := range updatedPresenceMessages { + pres.messageEmitter.Emit(msg.Action, (*subscriptionPresenceMessage)(msg)) } } diff --git a/ably/realtime_presence_internal_test.go b/ably/realtime_presence_internal_test.go index ad112fd0a..da440699f 100644 --- a/ably/realtime_presence_internal_test.go +++ b/ably/realtime_presence_internal_test.go @@ -48,7 +48,7 @@ func TestVerifyChanState(t *testing.T) { for testName, test := range tests { t.Run(testName, func(t *testing.T) { presence := newRealtimePresence(test.channel) - err := presence.verifyChanState() + err := presence.isValidChannelState() assert.Equal(t, test.expectedErr, err) }) } From 25eebe5847a5c7f52f3c0cf3f1c30979b52d2be1 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 31 Aug 2023 16:14:17 +0530 Subject: [PATCH 064/178] Removed unnecessary inner message timestamp setting, already covered by tm2i --- ably/realtime_presence.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index d603d8872..ea2d5bbdf 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -261,11 +261,6 @@ func (pres *RealtimePresence) removePresenceMember(memberMap map[string]*Presenc } func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncSerial string) { - for _, presmsg := range msg.Presence { - if presmsg.Timestamp == 0 { - presmsg.Timestamp = msg.Timestamp - } - } pres.mtx.Lock() if syncSerial != "" { // RTP18a pres.syncStart(syncSerial) From 1fafafc4b74e09caf0fc632bd7d0dc15390ea83d Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 31 Aug 2023 20:57:00 +0530 Subject: [PATCH 065/178] Refactored spec annotations for realtime presence --- ably/realtime_presence.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index ea2d5bbdf..64f3654a7 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -26,7 +26,7 @@ type RealtimePresence struct { serial string messageEmitter *eventEmitter channel *RealtimeChannel - members map[string]*PresenceMessage + members map[string]*PresenceMessage // RTP2 internalMembers map[string]*PresenceMessage // RTP17 syncResidualMembers map[string]*PresenceMessage state PresenceAction @@ -204,7 +204,7 @@ func (pres *RealtimePresence) leaveMembers(members map[string]*PresenceMessage) msg.Action = PresenceActionLeave msg.ID = "" msg.Timestamp = time.Now().UnixMilli() - pres.messageEmitter.Emit(msg.Action, (*subscriptionPresenceMessage)(msg)) // RTP2g + pres.messageEmitter.Emit(msg.Action, (*subscriptionPresenceMessage)(msg)) } } @@ -274,7 +274,7 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS } switch presenceMember.Action { case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: // RTP2d, RTP17b - presenceMemberShallowCopy := presenceMember // RTP2g shouldn't mutate action for next loop + presenceMemberShallowCopy := presenceMember presenceMemberShallowCopy.Action = PresenceActionPresent pres.addPresenceMember(pres.internalMembers, memberKey, presenceMemberShallowCopy) case PresenceActionLeave: // RTP17b, RTP2e @@ -287,17 +287,18 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS // Update presence map / channel's member state. updatedPresenceMessages := make([]*PresenceMessage, 0, len(msg.Presence)) for _, presenceMember := range msg.Presence { - memberKey := presenceMember.ConnectionID + presenceMember.ClientID + memberKey := presenceMember.ConnectionID + presenceMember.ClientID // TP3h memberUpdated := false switch presenceMember.Action { case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: // RTP2d delete(pres.syncResidualMembers, memberKey) presenceMemberShallowCopy := presenceMember - presenceMemberShallowCopy.Action = PresenceActionPresent // RTP2g + presenceMemberShallowCopy.Action = PresenceActionPresent memberUpdated = pres.addPresenceMember(pres.members, memberKey, presenceMemberShallowCopy) case PresenceActionLeave: // RTP2e memberUpdated = pres.removePresenceMember(pres.members, memberKey, presenceMember) } + // RTP2g if memberUpdated { updatedPresenceMessages = append(updatedPresenceMessages, presenceMember) } From 65faee6d9c71efbf23460344f75cd984a5415b32 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 31 Aug 2023 23:46:46 +0530 Subject: [PATCH 066/178] Added missing spec annotations to presence spec --- ably/realtime_presence.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 64f3654a7..6e0f523ef 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -141,7 +141,7 @@ func syncSerial(msg *protocolMessage) string { // RTP18 func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { pres.mtx.Lock() defer pres.mtx.Unlock() - if isNewAttach { // RTP17f + if isNewAttach { // RTP17f, RTP17 for _, member := range pres.internalMembers { err := pres.enterClient(context.Background(), member.ClientID, member.Data, member.ID) // RTP17g // RTP17e @@ -168,8 +168,7 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { pres.syncMtx.Unlock() } } - // RTP5b - pres.queue.Flush() + pres.queue.Flush() // RTP5b } // SyncComplete gives true if the initial SYNC operation has completed for the members present on the channel. @@ -197,7 +196,7 @@ func (pres *RealtimePresence) syncStart(serial string) { // RTP19, RTP19a func (pres *RealtimePresence) leaveMembers(members map[string]*PresenceMessage) { - for memberKey := range members { // RTP19 + for memberKey := range members { delete(pres.members, memberKey) } for _, msg := range members { @@ -226,6 +225,7 @@ func (pres *RealtimePresence) syncEnd() { pres.syncMtx.Unlock() } +// RTP2a, RTP2b, RTP2c func (pres *RealtimePresence) addPresenceMember(memberMap map[string]*PresenceMessage, memberKey string, presenceMember *PresenceMessage) bool { if existingMember, ok := memberMap[memberKey]; ok { // RTP2a isMemberNew, err := presenceMember.IsNewerThan(existingMember) // RTP2b @@ -244,6 +244,7 @@ func (pres *RealtimePresence) addPresenceMember(memberMap map[string]*PresenceMe return true } +// RTP2a, RTP2b, RTP2c func (pres *RealtimePresence) removePresenceMember(memberMap map[string]*PresenceMessage, memberKey string, presenceMember *PresenceMessage) bool { if existingMember, ok := memberMap[memberKey]; ok { // RTP2a isMemberNew, err := presenceMember.IsNewerThan(existingMember) // RTP2b @@ -268,8 +269,8 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS // RTP17 - Update internal presence map for _, presenceMember := range msg.Presence { - memberKey := presenceMember.ClientID // RTP17h - if pres.channel.client.Connection.id != presenceMember.ConnectionID { + memberKey := presenceMember.ClientID // RTP17h + if pres.channel.client.Connection.id != presenceMember.ConnectionID { // RTP17 continue } switch presenceMember.Action { From 176bc4c96b019e173c1da7c40fd0cc1d6010179a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 2 Sep 2023 19:55:06 +0530 Subject: [PATCH 067/178] Refactored implementation for actionSync and actionPresence --- ably/realtime_channel.go | 4 +- ably/realtime_presence.go | 79 +++++++++++++++++++++++---------------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index bcb108277..4ef4789f9 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -853,9 +853,9 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { c.lockStartRetryAttachLoop(err) case actionSync: - c.Presence.processIncomingMessage(msg, syncSerial(msg)) + c.Presence.processSyncMessage(msg) // RTP18 case actionPresence: - c.Presence.processIncomingMessage(msg, "") + c.Presence.processIncomingMessage(msg) case actionError: c.setState(ChannelStateFailed, newErrorFromProto(msg.Error), false) c.queue.Fail(newErrorFromProto(msg.Error)) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 6e0f523ef..6a04debdc 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -21,18 +21,17 @@ const ( // It allows entering, leaving and updating presence state for the current client or on behalf of other client. // It enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel. type RealtimePresence struct { - mtx sync.Mutex - data interface{} - serial string - messageEmitter *eventEmitter - channel *RealtimeChannel - members map[string]*PresenceMessage // RTP2 - internalMembers map[string]*PresenceMessage // RTP17 - syncResidualMembers map[string]*PresenceMessage - state PresenceAction - syncMtx sync.Mutex - syncState syncState - queue *msgQueue + mtx sync.Mutex + data interface{} + messageEmitter *eventEmitter + channel *RealtimeChannel + members map[string]*PresenceMessage // RTP2 + internalMembers map[string]*PresenceMessage // RTP17 + beforeSyncMembers map[string]*PresenceMessage + state PresenceAction + syncMtx sync.Mutex + syncState syncState + queue *msgQueue } func newRealtimePresence(channel *RealtimeChannel) *RealtimePresence { @@ -131,11 +130,18 @@ func (pres *RealtimePresence) syncWait() { pres.syncMtx.Unlock() } -func syncSerial(msg *protocolMessage) string { // RTP18 - if i := strings.IndexRune(msg.ChannelSerial, ':'); i != -1 { - return msg.ChannelSerial[i+1:] +// RTP18 +func syncSerial(msg *protocolMessage) (noChannelSerial bool, syncCursor bool) { + if empty(msg.ChannelSerial) { // RTP18c + noChannelSerial = true + return + } + if i := strings.IndexRune(msg.ChannelSerial, ':'); i != -1 { // RTP18a + // syncCursor = msg.ChannelSerial[i+1:] + syncCursor = true + return } - return "" + return // RTP18b } func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { @@ -160,7 +166,7 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { } // RTP1 if msg.Flags.Has(flagHasPresence) { - pres.syncStart(syncSerial(msg)) + pres.syncStart() } else { pres.leaveMembers(pres.members) // RTP19a if pres.syncState == syncInitial { @@ -178,7 +184,7 @@ func (pres *RealtimePresence) SyncComplete() bool { return pres.syncState == syncComplete } -func (pres *RealtimePresence) syncStart(serial string) { +func (pres *RealtimePresence) syncStart() { if pres.syncState == syncInProgress { return } else if pres.syncState != syncInitial { @@ -186,11 +192,10 @@ func (pres *RealtimePresence) syncStart(serial string) { // initial sync, the callers are already waiting. pres.syncMtx.Lock() } - pres.serial = serial pres.syncState = syncInProgress - pres.syncResidualMembers = make(map[string]*PresenceMessage, len(pres.members)) // RTP19 + pres.beforeSyncMembers = make(map[string]*PresenceMessage, len(pres.members)) // RTP19 for memberKey, member := range pres.members { - pres.syncResidualMembers[memberKey] = member + pres.beforeSyncMembers[memberKey] = member } } @@ -211,14 +216,14 @@ func (pres *RealtimePresence) syncEnd() { if pres.syncState != syncInProgress { return } - pres.leaveMembers(pres.syncResidualMembers) // RTP19 + pres.leaveMembers(pres.beforeSyncMembers) // RTP19 for memberKey, presence := range pres.members { // RTP2f if presence.Action == PresenceActionAbsent { delete(pres.members, memberKey) } } - pres.syncResidualMembers = nil + pres.beforeSyncMembers = nil pres.syncState = syncComplete // Sync has completed, unblock all callers to Get(true) waiting // for the sync. @@ -261,12 +266,26 @@ func (pres *RealtimePresence) removePresenceMember(memberMap map[string]*Presenc return false } -func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncSerial string) { - pres.mtx.Lock() - if syncSerial != "" { // RTP18a - pres.syncStart(syncSerial) +// RTP18 +// TODO - Part of RTP18a where new sequence id is received in middle of sync +// will not call synStart because sync is in progress. +// Though it will process the message in the next step when lock is ended. +func (pres *RealtimePresence) processSyncMessage(msg *protocolMessage) { + hasAllPresenceData, syncCursor := syncSerial(msg) + + if hasAllPresenceData || syncCursor { // RTP18a, RTP18c + pres.syncStart() + } + + pres.processIncomingMessage(msg) + + if hasAllPresenceData || !syncCursor { // RTP18b, RTP18c + pres.syncEnd() } +} +func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage) { + pres.mtx.Lock() // RTP17 - Update internal presence map for _, presenceMember := range msg.Presence { memberKey := presenceMember.ClientID // RTP17h @@ -292,7 +311,7 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS memberUpdated := false switch presenceMember.Action { case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: // RTP2d - delete(pres.syncResidualMembers, memberKey) + delete(pres.beforeSyncMembers, memberKey) presenceMemberShallowCopy := presenceMember presenceMemberShallowCopy.Action = PresenceActionPresent memberUpdated = pres.addPresenceMember(pres.members, memberKey, presenceMemberShallowCopy) @@ -304,10 +323,6 @@ func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage, syncS updatedPresenceMessages = append(updatedPresenceMessages, presenceMember) } } - - if syncSerial == "" { // RTP18b - pres.syncEnd() - } pres.mtx.Unlock() // RTP2g From 83a54ca486627a1c51b0147e87ae501750bbd6bc Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 2 Sep 2023 19:57:52 +0530 Subject: [PATCH 068/178] Removed duplicate queue flush for channel state attached --- ably/realtime_channel.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 4ef4789f9..4406a6fe9 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -968,10 +968,6 @@ func (c *RealtimeChannel) setState(state ChannelState, err error, resumed bool) if state == ChannelStateDetached || state == ChannelStateSuspended || state == ChannelStateFailed { c.properties.ChannelSerial = "" } - // RTP5b - if state == ChannelStateAttached { - c.queue.Flush() - } // RTP5f if state == ChannelStateSuspended { c.Presence.onChannelSuspended(channelStateError(state, err)) From 4edf8572c38a09e4c5ec48212ead3385b643026f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 3 Sep 2023 21:15:17 +0530 Subject: [PATCH 069/178] Added separate implementation for emitting an error update --- ably/realtime_channel.go | 25 ++++++++++++++++++------- ably/realtime_presence.go | 19 +++++++------------ 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 4406a6fe9..714c68ad8 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -805,13 +805,13 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { if msg.Flags != 0 { c.setModes(channelModeFromFlag(msg.Flags)) } - - isNewAttach := c.state != ChannelStateAttached - c.Presence.onAttach(msg, isNewAttach) - - isAttachResumed := msg.Flags.Has(flagResumed) - if isNewAttach || !isAttachResumed { //RTL12 - c.setState(ChannelStateAttached, newErrorFromProto(msg.Error), isAttachResumed) + // RTL12 + if c.state == ChannelStateAttached && !msg.Flags.Has(flagResumed) { + c.emitErrorUpdate(newErrorFromProto(msg.Error), false) // RTL12 + c.Presence.onAttach(msg, false) + } else { + c.Presence.onAttach(msg, true) + c.setState(ChannelStateAttached, newErrorFromProto(msg.Error), msg.Flags.Has(flagResumed)) } c.queue.Flush() case actionDetached: @@ -988,6 +988,17 @@ func (c *RealtimeChannel) lockSetAttachResume(state ChannelState) { } } +func (channel *RealtimeChannel) emitErrorUpdate(err *ErrorInfo, resumed bool) { + change := ChannelStateChange{ + Current: channel.state, + Previous: channel.state, + Reason: err, + Resumed: resumed, + Event: ChannelEventUpdate, + } + channel.emitter.Emit(change.Event, change) +} + func (c *RealtimeChannel) lockSetState(state ChannelState, err error, resumed bool) error { c.lockSetAttachResume(state) previous := c.state diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 6a04debdc..38d25a56c 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -144,23 +144,18 @@ func syncSerial(msg *protocolMessage) (noChannelSerial bool, syncCursor bool) { return // RTP18b } -func (pres *RealtimePresence) onAttach(msg *protocolMessage, isNewAttach bool) { +func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMessageLoss bool) { pres.mtx.Lock() defer pres.mtx.Unlock() - if isNewAttach { // RTP17f, RTP17 + // RTP17f + if isAttachWithoutMessageLoss { for _, member := range pres.internalMembers { - err := pres.enterClient(context.Background(), member.ClientID, member.Data, member.ID) // RTP17g + // RTP17g + err := pres.enterClient(context.Background(), member.ClientID, member.Data, member.ID) // RTP17e if err != nil { - pres.log().Errorf("Error for internal member presence enter with id %v, clientId %v, err %v", member.ID, member.ClientID, err) - change := ChannelStateChange{ - Current: pres.channel.state, - Previous: pres.channel.state, - Reason: newError(91004, err), - Resumed: true, - Event: ChannelEventUpdate, - } - pres.channel.emitter.Emit(change.Event, change) + pres.channel.log().Errorf("Error for internal member presence enter with id %v, clientId %v, err %v", member.ID, member.ClientID, err) + pres.channel.emitErrorUpdate(newError(91004, err), msg.Flags.Has(flagResumed)) } } } From 542ff69706e3563cb8c0280b62f127afab115c8c Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 4 Sep 2023 00:39:27 +0530 Subject: [PATCH 070/178] Extracted entering members from internal presence map into separate method --- ably/realtime_channel.go | 4 ++-- ably/realtime_presence.go | 30 +++++++++++++++++------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 714c68ad8..dce9608cf 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -853,9 +853,9 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { c.lockStartRetryAttachLoop(err) case actionSync: - c.Presence.processSyncMessage(msg) // RTP18 + c.Presence.processProtoSyncMessage(msg) // RTP18 case actionPresence: - c.Presence.processIncomingMessage(msg) + c.Presence.processProtoPresenceMessage(msg) case actionError: c.setState(ChannelStateFailed, newErrorFromProto(msg.Error), false) c.queue.Fail(newErrorFromProto(msg.Error)) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 38d25a56c..bf0a73817 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -109,7 +109,7 @@ func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { case ChannelStateAttaching: // RTP16b pres.maybeEnqueue(protomsg, onAck) case ChannelStateAttached: // RTP16a - pres.channel.client.Connection.send(protomsg, onAck) // RTL6c2 + pres.channel.client.Connection.send(protomsg, onAck) // RTP16a, RTL6c } return resultFunc(func(ctx context.Context) error { @@ -144,20 +144,24 @@ func syncSerial(msg *protocolMessage) (noChannelSerial bool, syncCursor bool) { return // RTP18b } +func (pres *RealtimePresence) enterMembersFromInternalPresenceMap() { + for _, member := range pres.internalMembers { + // RTP17g + err := pres.enterClient(context.Background(), member.ClientID, member.Data, member.ID) + // RTP17e + if err != nil { + pres.channel.log().Errorf("Error for internal member presence enter with id %v, clientId %v, err %v", member.ID, member.ClientID, err) + pres.channel.emitErrorUpdate(newError(91004, err), true) + } + } +} + func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMessageLoss bool) { pres.mtx.Lock() defer pres.mtx.Unlock() // RTP17f if isAttachWithoutMessageLoss { - for _, member := range pres.internalMembers { - // RTP17g - err := pres.enterClient(context.Background(), member.ClientID, member.Data, member.ID) - // RTP17e - if err != nil { - pres.channel.log().Errorf("Error for internal member presence enter with id %v, clientId %v, err %v", member.ID, member.ClientID, err) - pres.channel.emitErrorUpdate(newError(91004, err), msg.Flags.Has(flagResumed)) - } - } + pres.enterMembersFromInternalPresenceMap() } // RTP1 if msg.Flags.Has(flagHasPresence) { @@ -265,21 +269,21 @@ func (pres *RealtimePresence) removePresenceMember(memberMap map[string]*Presenc // TODO - Part of RTP18a where new sequence id is received in middle of sync // will not call synStart because sync is in progress. // Though it will process the message in the next step when lock is ended. -func (pres *RealtimePresence) processSyncMessage(msg *protocolMessage) { +func (pres *RealtimePresence) processProtoSyncMessage(msg *protocolMessage) { hasAllPresenceData, syncCursor := syncSerial(msg) if hasAllPresenceData || syncCursor { // RTP18a, RTP18c pres.syncStart() } - pres.processIncomingMessage(msg) + pres.processProtoPresenceMessage(msg) if hasAllPresenceData || !syncCursor { // RTP18b, RTP18c pres.syncEnd() } } -func (pres *RealtimePresence) processIncomingMessage(msg *protocolMessage) { +func (pres *RealtimePresence) processProtoPresenceMessage(msg *protocolMessage) { pres.mtx.Lock() // RTP17 - Update internal presence map for _, presenceMember := range msg.Presence { From 22878b0f777363391113bb2f8f0a2e194008f081 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 4 Sep 2023 02:13:23 +0530 Subject: [PATCH 071/178] Refactored todo for processing in flight sync messages --- ably/realtime_presence.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index bf0a73817..9940b336f 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -266,10 +266,10 @@ func (pres *RealtimePresence) removePresenceMember(memberMap map[string]*Presenc } // RTP18 -// TODO - Part of RTP18a where new sequence id is received in middle of sync -// will not call synStart because sync is in progress. -// Though it will process the message in the next step when lock is ended. func (pres *RealtimePresence) processProtoSyncMessage(msg *protocolMessage) { + // TODO - Part of RTP18a where new sequence id is received in middle of sync will not call synStart + // because sync is in progress. Though it will wait till all proto messages are processed. + // This is not implemented because of additional complexity of managing locks. hasAllPresenceData, syncCursor := syncSerial(msg) if hasAllPresenceData || syncCursor { // RTP18a, RTP18c From f184caaee4f9c9ead4c72150ce7ddbcc91deb164 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 6 Sep 2023 21:30:32 +0530 Subject: [PATCH 072/178] Updated implementation for syncSerial --- ably/realtime_presence.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 9940b336f..4f79c5d9d 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -131,17 +131,18 @@ func (pres *RealtimePresence) syncWait() { } // RTP18 -func syncSerial(msg *protocolMessage) (noChannelSerial bool, syncCursor bool) { +func syncSerial(msg *protocolMessage) (noChannelSerial bool, syncSequenceId string, syncCursor string) { if empty(msg.ChannelSerial) { // RTP18c noChannelSerial = true return } - if i := strings.IndexRune(msg.ChannelSerial, ':'); i != -1 { // RTP18a - // syncCursor = msg.ChannelSerial[i+1:] - syncCursor = true - return + // RTP18a + serials := strings.Split(msg.ChannelSerial, ":") + syncSequenceId = serials[0] + if len(serials) > 1 { + syncCursor = serials[1] } - return // RTP18b + return false, syncSequenceId, syncCursor } func (pres *RealtimePresence) enterMembersFromInternalPresenceMap() { @@ -270,15 +271,13 @@ func (pres *RealtimePresence) processProtoSyncMessage(msg *protocolMessage) { // TODO - Part of RTP18a where new sequence id is received in middle of sync will not call synStart // because sync is in progress. Though it will wait till all proto messages are processed. // This is not implemented because of additional complexity of managing locks. - hasAllPresenceData, syncCursor := syncSerial(msg) + noChannelSerial, _, syncCursor := syncSerial(msg) - if hasAllPresenceData || syncCursor { // RTP18a, RTP18c - pres.syncStart() - } + pres.syncStart() // RTP18a, RTP18c pres.processProtoPresenceMessage(msg) - if hasAllPresenceData || !syncCursor { // RTP18b, RTP18c + if noChannelSerial || empty(syncCursor) { // RTP18c, RTP18b pres.syncEnd() } } From 72601618b9c4eaafe5280d245c9a1718e4b8be51 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 6 Sep 2023 22:40:27 +0530 Subject: [PATCH 073/178] Fixed spec annotations --- ably/realtime_presence.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 4f79c5d9d..651daec07 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -68,7 +68,7 @@ func (pres *RealtimePresence) onChannelDetachedOrFailed(err error) { for k := range pres.internalMembers { delete(pres.internalMembers, k) } - pres.queue.Fail(err) // RTP16b + pres.queue.Fail(err) } // RTP5f, RTP16b @@ -160,6 +160,7 @@ func (pres *RealtimePresence) enterMembersFromInternalPresenceMap() { func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMessageLoss bool) { pres.mtx.Lock() defer pres.mtx.Unlock() + pres.queue.Flush() // RTP5b // RTP17f if isAttachWithoutMessageLoss { pres.enterMembersFromInternalPresenceMap() @@ -174,7 +175,6 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMess pres.syncMtx.Unlock() } } - pres.queue.Flush() // RTP5b } // SyncComplete gives true if the initial SYNC operation has completed for the members present on the channel. @@ -270,7 +270,7 @@ func (pres *RealtimePresence) removePresenceMember(memberMap map[string]*Presenc func (pres *RealtimePresence) processProtoSyncMessage(msg *protocolMessage) { // TODO - Part of RTP18a where new sequence id is received in middle of sync will not call synStart // because sync is in progress. Though it will wait till all proto messages are processed. - // This is not implemented because of additional complexity of managing locks. + // This is not implemented because of additional complexity of managing locks and reverting to prev. memberstate noChannelSerial, _, syncCursor := syncSerial(msg) pres.syncStart() // RTP18a, RTP18c From 7ee21edc819646d3166eb7ee9775f7c348da29c4 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 7 Sep 2023 03:09:09 +0530 Subject: [PATCH 074/178] Removed unnecessary id param for internal map presence enter --- ably/realtime_presence.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 651daec07..79494da09 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -148,7 +148,7 @@ func syncSerial(msg *protocolMessage) (noChannelSerial bool, syncSequenceId stri func (pres *RealtimePresence) enterMembersFromInternalPresenceMap() { for _, member := range pres.internalMembers { // RTP17g - err := pres.enterClient(context.Background(), member.ClientID, member.Data, member.ID) + err := pres.EnterClient(context.Background(), member.ClientID, member.Data) // RTP17e if err != nil { pres.channel.log().Errorf("Error for internal member presence enter with id %v, clientId %v, err %v", member.ID, member.ClientID, err) @@ -518,10 +518,6 @@ func (pres *RealtimePresence) Leave(ctx context.Context, data interface{}) error // If the context is cancelled before the operation finishes, the call returns with an error, // but the operation carries on in the background and presence state may eventually be updated anyway. func (pres *RealtimePresence) EnterClient(ctx context.Context, clientID string, data interface{}) error { - return pres.enterClient(ctx, clientID, data, "") -} - -func (pres *RealtimePresence) enterClient(ctx context.Context, clientID string, data interface{}, msgId string) error { pres.mtx.Lock() pres.data = data pres.state = PresenceActionEnter @@ -531,7 +527,6 @@ func (pres *RealtimePresence) enterClient(ctx context.Context, clientID string, } msg.Data = data msg.ClientID = clientID - msg.ID = msgId res, err := pres.send(&msg) if err != nil { return err From 74d2138266905f0696c69162f3c0070f8aaf868a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 12 Sep 2023 19:09:04 +0530 Subject: [PATCH 075/178] Updated realtime presence to flush queue on attached and enter members from internal map --- ably/realtime_presence.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 79494da09..103527153 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -160,11 +160,6 @@ func (pres *RealtimePresence) enterMembersFromInternalPresenceMap() { func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMessageLoss bool) { pres.mtx.Lock() defer pres.mtx.Unlock() - pres.queue.Flush() // RTP5b - // RTP17f - if isAttachWithoutMessageLoss { - pres.enterMembersFromInternalPresenceMap() - } // RTP1 if msg.Flags.Has(flagHasPresence) { pres.syncStart() @@ -175,6 +170,11 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMess pres.syncMtx.Unlock() } } + pres.queue.Flush() // RTP5b + // RTP17f + if isAttachWithoutMessageLoss { + pres.enterMembersFromInternalPresenceMap() + } } // SyncComplete gives true if the initial SYNC operation has completed for the members present on the channel. From 30590003326d055f39df3248d87db6ac07402632 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 19 Oct 2023 19:27:44 +0530 Subject: [PATCH 076/178] Fixed failing tests for enter client presence --- ably/error_names.go | 1 + ably/realtime_presence_internal_test.go | 23 ++++++++++++++++------- ably/state.go | 2 ++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/ably/error_names.go b/ably/error_names.go index d14381ca1..708477e1d 100644 --- a/ably/error_names.go +++ b/ably/error_names.go @@ -24,6 +24,7 @@ const ( ErrTimeoutError ErrorCode = 50003 ErrConnectionFailed ErrorCode = 80000 ErrConnectionSuspended ErrorCode = 80002 + ErrConnectionClosed ErrorCode = 80017 ErrDisconnected ErrorCode = 80003 ErrProtocolError ErrorCode = 80013 ErrChannelOperationFailed ErrorCode = 90000 diff --git a/ably/realtime_presence_internal_test.go b/ably/realtime_presence_internal_test.go index da440699f..b8a6a2925 100644 --- a/ably/realtime_presence_internal_test.go +++ b/ably/realtime_presence_internal_test.go @@ -4,13 +4,14 @@ package ably import ( + "context" "errors" "testing" "github.com/stretchr/testify/assert" ) -func TestVerifyChanState(t *testing.T) { +func TestVerifyChanState_RTP16(t *testing.T) { tests := map[string]struct { channel *RealtimeChannel expectedErr error @@ -27,9 +28,9 @@ func TestVerifyChanState(t *testing.T) { channel: mockChannelWithState(&ChannelStateAttached, nil), expectedErr: nil, }, - `No error if the channel is in state: "SUSPENDED"`: { + `Error if the channel is in state: "SUSPENDED"`: { channel: mockChannelWithState(&ChannelStateSuspended, nil), - expectedErr: nil, + expectedErr: newError(91001, errors.New("unable to enter presence channel (invalid channel state: SUSPENDED)")), }, `Error if the channel is in state: "DETACHING"`: { channel: mockChannelWithState(&ChannelStateDetaching, nil), @@ -67,7 +68,15 @@ func TestSend(t *testing.T) { Message: Message{Name: "Hello"}, Action: PresenceActionEnter, }, - expectedErr: nil, + expectedErr: (*ErrorInfo)(nil), + }, + `Error if channel is: "ATTACHED" and connection is :"CLOSED"`: { + channel: mockChannelWithState(&ChannelStateAttached, &ConnectionStateClosed), + msg: PresenceMessage{ + Message: Message{Name: "Hello"}, + Action: PresenceActionEnter, + }, + expectedErr: newError(80017, errors.New("Connection unavailable")), }, `Error if channel is: "DETACHED" and connection is :"CLOSED"`: { channel: mockChannelWithState(&ChannelStateDetached, &ConnectionStateClosed), @@ -75,15 +84,15 @@ func TestSend(t *testing.T) { Message: Message{Name: "Hello"}, Action: PresenceActionEnter, }, - expectedErr: newError(80000, errors.New("cannot Attach channel because connection is in CLOSED state")), + expectedErr: newError(91001, errors.New("unable to enter presence channel (invalid channel state: DETACHED)")), }, } for testName, test := range tests { t.Run(testName, func(t *testing.T) { presence := newRealtimePresence(test.channel) - _, err := presence.send(&test.msg) - assert.Equal(t, test.expectedErr, err) + err := presence.EnterClient(context.Background(), "clientId", &test.msg.Message) + assert.Equal(t, test.expectedErr, err.(*ErrorInfo)) }) } } diff --git a/ably/state.go b/ably/state.go index 08e00f3e7..59e900dce 100644 --- a/ably/state.go +++ b/ably/state.go @@ -45,6 +45,7 @@ func goWaiter(f func() error) result { var ( errDisconnected = newErrorf(ErrDisconnected, "Connection temporarily unavailable") errSuspended = newErrorf(ErrConnectionSuspended, "Connection unavailable") + errClosed = newErrorf(ErrConnectionClosed, "Connection unavailable") errFailed = newErrorf(ErrConnectionFailed, "Connection failed") errNeverConnected = newErrorf(ErrConnectionSuspended, "Unable to establish connection") @@ -57,6 +58,7 @@ var connStateErrors = map[ConnectionState]ErrorInfo{ ConnectionStateDisconnected: *errDisconnected, ConnectionStateFailed: *errFailed, ConnectionStateSuspended: *errSuspended, + ConnectionStateClosed: *errClosed, } func connStateError(state ConnectionState, err error) *ErrorInfo { From 1544ee499157c8c94199c4010d1fd0639802cfb5 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 23 Oct 2023 17:34:18 +0530 Subject: [PATCH 077/178] Updated realtime presence test for checking 250 members --- ably/realtime_presence_integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ably/realtime_presence_integration_test.go b/ably/realtime_presence_integration_test.go index f29c49253..bde3465a2 100644 --- a/ably/realtime_presence_integration_test.go +++ b/ably/realtime_presence_integration_test.go @@ -57,7 +57,7 @@ func TestRealtimePresence_Sync(t *testing.T) { assert.NoError(t, err) } -func TestRealtimePresence_Sync250(t *testing.T) { +func TestRealtimePresence_Sync250_RTP4(t *testing.T) { app, client1 := ablytest.NewRealtime(nil...) defer safeclose(t, ablytest.FullRealtimeCloser(client1), app) client2 := app.NewRealtime(nil...) From ceed47700b2bf9c6ebe495db7a81922de6da1a87 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 24 Oct 2023 20:51:08 +0530 Subject: [PATCH 078/178] Updated test for sending message --- ably/realtime_presence_internal_test.go | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/ably/realtime_presence_internal_test.go b/ably/realtime_presence_internal_test.go index b8a6a2925..2fef28972 100644 --- a/ably/realtime_presence_internal_test.go +++ b/ably/realtime_presence_internal_test.go @@ -58,32 +58,23 @@ func TestVerifyChanState_RTP16(t *testing.T) { func TestSend(t *testing.T) { tests := map[string]struct { channel *RealtimeChannel - msg PresenceMessage + msg Message expectedResult result expectedErr error }{ `No error sending presence if the channel is in state: "ATTACHED"`: { - channel: mockChannelWithState(&ChannelStateAttached, nil), - msg: PresenceMessage{ - Message: Message{Name: "Hello"}, - Action: PresenceActionEnter, - }, + channel: mockChannelWithState(&ChannelStateAttached, nil), + msg: Message{Name: "Hello"}, expectedErr: (*ErrorInfo)(nil), }, `Error if channel is: "ATTACHED" and connection is :"CLOSED"`: { - channel: mockChannelWithState(&ChannelStateAttached, &ConnectionStateClosed), - msg: PresenceMessage{ - Message: Message{Name: "Hello"}, - Action: PresenceActionEnter, - }, + channel: mockChannelWithState(&ChannelStateAttached, &ConnectionStateClosed), + msg: Message{Name: "Hello"}, expectedErr: newError(80017, errors.New("Connection unavailable")), }, `Error if channel is: "DETACHED" and connection is :"CLOSED"`: { - channel: mockChannelWithState(&ChannelStateDetached, &ConnectionStateClosed), - msg: PresenceMessage{ - Message: Message{Name: "Hello"}, - Action: PresenceActionEnter, - }, + channel: mockChannelWithState(&ChannelStateDetached, &ConnectionStateClosed), + msg: Message{Name: "Hello"}, expectedErr: newError(91001, errors.New("unable to enter presence channel (invalid channel state: DETACHED)")), }, } @@ -91,7 +82,7 @@ func TestSend(t *testing.T) { for testName, test := range tests { t.Run(testName, func(t *testing.T) { presence := newRealtimePresence(test.channel) - err := presence.EnterClient(context.Background(), "clientId", &test.msg.Message) + err := presence.EnterClient(context.Background(), "clientId", &test.msg) assert.Equal(t, test.expectedErr, err.(*ErrorInfo)) }) } From 6057ceba2f46fef324ea58fb83fa7be35d327241 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 24 Oct 2023 22:03:23 +0530 Subject: [PATCH 079/178] Moved enterClient to realtime presence test, deleted old test file --- ably/realtime_presence_integration_test.go | 26 +++++++++++++++ ably/realtime_presence_test.go | 38 ---------------------- 2 files changed, 26 insertions(+), 38 deletions(-) delete mode 100644 ably/realtime_presence_test.go diff --git a/ably/realtime_presence_integration_test.go b/ably/realtime_presence_integration_test.go index bde3465a2..d27638fcd 100644 --- a/ably/realtime_presence_integration_test.go +++ b/ably/realtime_presence_integration_test.go @@ -167,3 +167,29 @@ func ExampleRealtimePresence_Enter() { return } } + +// When a client is created without a ClientID, EnterClient is used to announce the presence of a client. +// This example shows a client without a clientID announcing the presence of "Client A" using EnterClient. +func ExampleRealtimePresence_EnterClient() { + + // A new realtime client is created without providing a ClientID. + client, err := ably.NewRealtime( + ably.WithKey("ABLY_PRIVATE_KEY"), + ) + if err != nil { + fmt.Println(err) + return + } + + // A new channel is initialised. + channel := client.Channels.Get("chat") + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + // The presence of Client A is announced using EnterClient. + if err := channel.Presence.EnterClient(ctx, "Client A", nil); err != nil { + fmt.Println(err) + return + } +} diff --git a/ably/realtime_presence_test.go b/ably/realtime_presence_test.go deleted file mode 100644 index e6d28464d..000000000 --- a/ably/realtime_presence_test.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build !integration -// +build !integration - -package ably_test - -import ( - "context" - "fmt" - "time" - - "github.com/ably/ably-go/ably" -) - -// When a client is created without a ClientID, EnterClient is used to announce the presence of a client. -// This example shows a client without a clientID announcing the presence of "Client A" using EnterClient. -func ExampleRealtimePresence_EnterClient() { - - // A new realtime client is created without providing a ClientID. - client, err := ably.NewRealtime( - ably.WithKey("ABLY_PRIVATE_KEY"), - ) - if err != nil { - fmt.Println(err) - return - } - - // A new channel is initialised. - channel := client.Channels.Get("chat") - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - - // The presence of Client A is announced using EnterClient. - if err := channel.Presence.EnterClient(ctx, "Client A", nil); err != nil { - fmt.Println(err) - return - } -} From 1e924909f692a41376a6b4992cd9d4d22bfd1b0a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 25 Oct 2023 22:50:28 +0530 Subject: [PATCH 080/178] Refactored mock channel state for presence, removed unnecessary mock_test --- ably/mock_test.go | 32 ------------------------- ably/realtime_presence_internal_test.go | 26 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 32 deletions(-) delete mode 100644 ably/mock_test.go diff --git a/ably/mock_test.go b/ably/mock_test.go deleted file mode 100644 index 1c6ffdf71..000000000 --- a/ably/mock_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// mocks and helpers for unit tests. - -package ably - -import ( - "bytes" - "log" -) - -var ( - buffer bytes.Buffer - mocklogger = log.New(&buffer, "logger: ", log.Lshortfile) -) - -// mockChannelWithState is a test helper that returns a mock channel in a specified state -func mockChannelWithState(channelState *ChannelState, connectionState *ConnectionState) *RealtimeChannel { - mockChannel := RealtimeChannel{ - client: &Realtime{ - rest: &REST{ - log: logger{l: &stdLogger{mocklogger}}, - }, - Connection: &Connection{}, - }, - } - if channelState != nil { - mockChannel.state = *channelState - } - if connectionState != nil { - mockChannel.client.Connection.state = *connectionState - } - return &mockChannel -} diff --git a/ably/realtime_presence_internal_test.go b/ably/realtime_presence_internal_test.go index 2fef28972..3b4494348 100644 --- a/ably/realtime_presence_internal_test.go +++ b/ably/realtime_presence_internal_test.go @@ -4,13 +4,39 @@ package ably import ( + "bytes" "context" "errors" + "log" "testing" "github.com/stretchr/testify/assert" ) +var ( + buffer bytes.Buffer + mocklogger = log.New(&buffer, "logger: ", log.Lshortfile) +) + +// mockChannelWithState is a test helper that returns a mock channel in a specified state +func mockChannelWithState(channelState *ChannelState, connectionState *ConnectionState) *RealtimeChannel { + mockChannel := RealtimeChannel{ + client: &Realtime{ + rest: &REST{ + log: logger{l: &stdLogger{mocklogger}}, + }, + Connection: &Connection{}, + }, + } + if channelState != nil { + mockChannel.state = *channelState + } + if connectionState != nil { + mockChannel.client.Connection.state = *connectionState + } + return &mockChannel +} + func TestVerifyChanState_RTP16(t *testing.T) { tests := map[string]struct { channel *RealtimeChannel From c62f00af293d6cc84948c51632bee7d585c7d90a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 26 Oct 2023 17:45:22 +0530 Subject: [PATCH 081/178] Fixed go compilation error for query --- ably/realtime_conn_spec_integration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 408cbb2e7..d00994ea5 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -2014,11 +2014,11 @@ func TestRealtimeConn_RTN16(t *testing.T) { { //(RTN16e) // This test was adopted from the ably-js project // https://github.com/ably/ably-js/blob/340e5ce31dc9d7434a06ae4e1eec32bdacc9c6c5/spec/realtime/connection.test.js#L119 - var query url.Values + // var query url.Values client2 := app.NewRealtime( ably.WithRecover("_____!ablygo_test_fake-key____:5:3"), ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { - query = u.Query() + // query = u.Query() return ably.DialWebsocket(protocol, u, timeout) })) defer safeclose(t, ablytest.FullRealtimeCloser(client2)) From 8586ba6ece3e6e19cc02c8511a987697be65daea Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 26 Oct 2023 17:45:38 +0530 Subject: [PATCH 082/178] Removed use of connection serial from connect test --- ably/realtime_conn_integration_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ably/realtime_conn_integration_test.go b/ably/realtime_conn_integration_test.go index b43e15a8c..9ed7d1459 100644 --- a/ably/realtime_conn_integration_test.go +++ b/ably/realtime_conn_integration_test.go @@ -34,11 +34,6 @@ func TestRealtimeConn_Connect(t *testing.T) { assert.NoError(t, err, "Connect()=%v", err) - serial := client.Connection.Serial() - assert.NotNil(t, serial) - assert.Equal(t, int64(-1), *serial, - "want serial=-1; got %d", client.Connection.Serial()) - err = ablytest.FullRealtimeCloser(client).Close() assert.NoError(t, err, "ablytest.FullRealtimeCloser(client).Close()=%v", err) From c96a371e5ebcea90d1afac6a66c2f4f946e38007 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 26 Oct 2023 19:11:03 +0530 Subject: [PATCH 083/178] removed use of connection serial from the code --- ably/realtime_conn.go | 12 ------------ ably/realtime_conn_integration_test.go | 5 ----- ably/realtime_conn_spec_integration_test.go | 5 ----- 3 files changed, 22 deletions(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 0c4f8c0fa..186d065d7 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -61,10 +61,6 @@ type Connection struct { // a realtime client docs for more info (RTN9). key string - // serial is the serial number of the last message to be received on this connection, used automatically by - // the library when recovering or resuming a connection. When recovering a connection explicitly, the recoveryKey - // is used in the recover client options as it contains both the key and the last message serial (RTN10). - serial *int64 msgSerial int64 connStateTTL durationFromMsecs err error @@ -523,14 +519,6 @@ func (c *Connection) CreateRecoveryKey() string { return recoveryKey } -// Serial gives serial number of a message received most recently. -// Last known serial number is used when recovering connection state. -func (c *Connection) Serial() *int64 { - c.mtx.Lock() - defer c.mtx.Unlock() - return c.serial -} - // State returns current state of the connection. func (c *Connection) State() ConnectionState { c.mtx.Lock() diff --git a/ably/realtime_conn_integration_test.go b/ably/realtime_conn_integration_test.go index 9ed7d1459..471ff2650 100644 --- a/ably/realtime_conn_integration_test.go +++ b/ably/realtime_conn_integration_test.go @@ -57,11 +57,6 @@ func TestRealtimeConn_NoConnect(t *testing.T) { err := ablytest.Wait(ablytest.ConnWaiter(client, client.Connect, ably.ConnectionEventConnected), nil) assert.NoError(t, err, "Connect()=%v", err) - serial := client.Connection.Serial() - assert.NotNil(t, serial) - assert.Equal(t, int64(-1), *serial, - "want serial=-1; got %d", client.Connection.Serial()) - err = ablytest.FullRealtimeCloser(client).Close() assert.NoError(t, err, "ablytest.FullRealtimeCloser(client).Close()=%v", err) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index d00994ea5..40b596760 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -2034,11 +2034,6 @@ func TestRealtimeConn_RTN16(t *testing.T) { reason := client2.Connection.ErrorReason() assert.Equal(t, 80008, int(reason.Code), "expected 80008 got %d", reason.Code) - serial := client2.Connection.Serial() - assert.NotNil(t, serial) - // verify serial is -1 (new connection), not 5 - assert.Equal(t, int64(-1), *serial, - "expected -1 got %d", serial) msgSerial := client2.Connection.MsgSerial() // verify msgSerial is 0 (new connection), not 3 assert.Equal(t, int64(0), msgSerial, From ce0e549a345a807c75dee8aef8964884a92cf5ef Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 27 Oct 2023 23:30:01 +0530 Subject: [PATCH 084/178] updated realtime conn, added flags for connection resume attempt or failed resume or recover --- ably/realtime_conn.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 186d065d7..efae2ac81 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -822,6 +822,12 @@ func (c *Connection) eventloop() { previousID := c.id c.id = msg.ConnectionID isNewID := previousID != msg.ConnectionID + + // recover is used when set via clientOptions#recover initially, resume will be used for all subsequent requests. + isConnectionResumeOrRecoverAttempt := !empty(c.key) || !empty(c.opts.Recover) + + failedResumeOrRecover := isNewID && msg.Error != nil // RTN15c7, RTN16d + if reconnecting && mode == recoveryMode && msg.Error == nil { // we are setting msgSerial as per (RTN16f) msgSerial, err := strconv.ParseInt(strings.Split(c.opts.Recover, ":")[2], 10, 64) From 8d1128854e27fad06dc64c577c788a11455ba712 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 27 Oct 2023 23:44:48 +0530 Subject: [PATCH 085/178] Added comment to emit error from message --- ably/realtime_conn.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index efae2ac81..ea85473f4 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -851,6 +851,7 @@ func (c *Connection) eventloop() { if reconnecting { // (RTN15c1) (RTN15c2) c.mtx.Lock() + // RTN15c7 - if error, set on connection and part of emitted connected event c.lockSetState(ConnectionStateConnected, newErrorFromProto(msg.Error), 0) c.mtx.Unlock() // (RTN15c3) From a64a479e3a0c8834dc6a43979370b0c19ce041a3 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 28 Oct 2023 21:22:06 +0530 Subject: [PATCH 086/178] Refactored code for connectionResumeorRecoverAttempt --- ably/realtime_conn.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index ea85473f4..65da541bb 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -793,6 +793,9 @@ func (c *Connection) eventloop() { case actionConnected: c.mtx.Lock() + // recover is used when set via clientOptions#recover initially, resume will be used for all subsequent requests. + isConnectionResumeOrRecoverAttempt := !empty(c.key) || !empty(c.opts.Recover) + // we need to get this before we set c.key so as to be sure if we were // resuming or recovering the connection. mode := c.getMode() @@ -823,9 +826,6 @@ func (c *Connection) eventloop() { c.id = msg.ConnectionID isNewID := previousID != msg.ConnectionID - // recover is used when set via clientOptions#recover initially, resume will be used for all subsequent requests. - isConnectionResumeOrRecoverAttempt := !empty(c.key) || !empty(c.opts.Recover) - failedResumeOrRecover := isNewID && msg.Error != nil // RTN15c7, RTN16d if reconnecting && mode == recoveryMode && msg.Error == nil { From fa75fbd45a9a9c9612e6cabd23c39bc9aedbb334 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 28 Oct 2023 21:36:46 +0530 Subject: [PATCH 087/178] Added code to resend pending messages --- ably/realtime_conn.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 65da541bb..465816dfa 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -866,7 +866,7 @@ func (c *Connection) eventloop() { c.lockSetState(ConnectionStateConnected, newErrorFromProto(msg.Error), 0) c.mtx.Unlock() } - c.queue.Flush() + c.sendPendingMessagesOnConnected(failedResumeOrRecover) case actionDisconnected: if !isTokenError(msg.Error) { // The spec doesn't say what to do in this case, so do nothing. @@ -900,6 +900,23 @@ func (c *Connection) eventloop() { } } +func (c *Connection) sendPendingMessagesOnConnected(failedResumeOrRecover bool) { + // RTN19a1 + if failedResumeOrRecover { + // foreach (var messageAndCallback in State.WaitingForAck) + // { + // State.PendingMessages.Add(new MessageAndCallback( + // messageAndCallback.Message, + // messageAndCallback.Callback, + // messageAndCallback.Logger)); + // } + } else { + // RTN19a2 - successful resume, msgSerial doesn't change + c.resendPending() + } + c.queue.Flush() +} + func (c *Connection) failedConnSideEffects(err *errorInfo) { c.mtx.Lock() if c.reconnecting { From b4d5df2a164a5cb18410a541078bcd06536e7ed5 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 29 Oct 2023 22:24:19 +0530 Subject: [PATCH 088/178] Added option for failed resume recover, setting messageserial accordingly --- ably/realtime_conn.go | 41 ++++++++++------------------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 465816dfa..19d138bbc 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -795,6 +795,7 @@ func (c *Connection) eventloop() { // recover is used when set via clientOptions#recover initially, resume will be used for all subsequent requests. isConnectionResumeOrRecoverAttempt := !empty(c.key) || !empty(c.opts.Recover) + c.opts.Recover = "" // RTN16k, explicitly setting null so it won't be used for subsequent connection requests // we need to get this before we set c.key so as to be sure if we were // resuming or recovering the connection. @@ -835,7 +836,9 @@ func (c *Connection) eventloop() { //TODO: how to handle this? Panic? } c.msgSerial = msgSerial - } else if isNewID { + } + + if isConnectionResumeOrRecoverAttempt && failedResumeOrRecover { c.msgSerial = 0 } @@ -846,27 +849,20 @@ func (c *Connection) eventloop() { continue } + // (RTN15c1) (RTN15c2) + // RTN24, RTN15c7 - if error, set on connection and part of emitted connected event + c.lockSetState(ConnectionStateConnected, newErrorFromProto(msg.Error), 0) c.mtx.Unlock() if reconnecting { - // (RTN15c1) (RTN15c2) - c.mtx.Lock() - // RTN15c7 - if error, set on connection and part of emitted connected event - c.lockSetState(ConnectionStateConnected, newErrorFromProto(msg.Error), 0) - c.mtx.Unlock() + // (RTN15c3) // we are calling this outside of locks to avoid deadlock because in the // RealtimeClient client where this callback is implemented we do some ops // with this Conn where we re acquire Conn.Lock again. - c.callbacks.onReconnected(isNewID) - } else { - // preserve old behavior. - c.mtx.Lock() - // RTN24 - c.lockSetState(ConnectionStateConnected, newErrorFromProto(msg.Error), 0) - c.mtx.Unlock() + c.callbacks.onReconnected(failedResumeOrRecover) } - c.sendPendingMessagesOnConnected(failedResumeOrRecover) + c.queue.Flush() case actionDisconnected: if !isTokenError(msg.Error) { // The spec doesn't say what to do in this case, so do nothing. @@ -900,23 +896,6 @@ func (c *Connection) eventloop() { } } -func (c *Connection) sendPendingMessagesOnConnected(failedResumeOrRecover bool) { - // RTN19a1 - if failedResumeOrRecover { - // foreach (var messageAndCallback in State.WaitingForAck) - // { - // State.PendingMessages.Add(new MessageAndCallback( - // messageAndCallback.Message, - // messageAndCallback.Callback, - // messageAndCallback.Logger)); - // } - } else { - // RTN19a2 - successful resume, msgSerial doesn't change - c.resendPending() - } - c.queue.Flush() -} - func (c *Connection) failedConnSideEffects(err *errorInfo) { c.mtx.Lock() if c.reconnecting { From ff0ac671fe0e5ac6840616108f42d94b2c8f4c0c Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 29 Oct 2023 22:28:22 +0530 Subject: [PATCH 089/178] decoded recovery key properly for realtime conn --- ably/realtime_conn.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 19d138bbc..fc2ab4a9e 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -831,11 +831,11 @@ func (c *Connection) eventloop() { if reconnecting && mode == recoveryMode && msg.Error == nil { // we are setting msgSerial as per (RTN16f) - msgSerial, err := strconv.ParseInt(strings.Split(c.opts.Recover, ":")[2], 10, 64) + recoveryKey, err := DecodeRecoveryKey(c.opts.Recover) if err != nil { - //TODO: how to handle this? Panic? + c.log().Errorf("error decoding recovery key, %v", err) } - c.msgSerial = msgSerial + c.msgSerial = recoveryKey.MsgSerial } if isConnectionResumeOrRecoverAttempt && failedResumeOrRecover { From fe84e49ef212ca5009178a57b47874a775b6386f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 29 Oct 2023 22:32:23 +0530 Subject: [PATCH 090/178] used recovery key context decoder for recovery mode query set --- ably/realtime_conn.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index fc2ab4a9e..e835fc4b9 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -6,7 +6,6 @@ import ( "fmt" "net/url" "strconv" - "strings" "sync" "time" ) @@ -286,11 +285,11 @@ func (c *Connection) params(mode connectionMode) (url.Values, error) { case resumeMode: query.Set("resume", c.key) case recoveryMode: - m := strings.Split(c.opts.Recover, ":") - if len(m) != 3 { - return nil, errors.New("conn: Invalid recovery key") + recoveryKey, err := DecodeRecoveryKey(c.opts.Recover) + if err != nil { + c.log().Errorf("error decoding recovery key, %v", err) } - query.Set("recover", m[0]) + query.Set("recover", recoveryKey.ConnectionKey) } return query, nil } From cacdcf78b9a48f1e153f52e5d2f18ebc6040b6fd Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 30 Oct 2023 16:28:14 +0530 Subject: [PATCH 091/178] attach channel on suspended --- ably/realtime_client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ably/realtime_client.go b/ably/realtime_client.go index e5795db21..98933402a 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -87,8 +87,8 @@ func (c *Realtime) onReconnected(isNewID bool) { for _, ch := range c.Channels.Iterate() { switch ch.State() { - // TODO: SUSPENDED - case ChannelStateAttaching, ChannelStateAttached: //RTN19b + // RTN15g3, RTN15c6, RTN15c7, RTN16l, RTN19b + case ChannelStateAttaching, ChannelStateAttached, ChannelStateSuspended: ch.mayAttach(false) case ChannelStateDetaching: //RTN19b ch.detachSkipVerifyActive() From b1fc74f18dc1b130b2efe76969d0d333302d90da Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 30 Oct 2023 16:49:45 +0530 Subject: [PATCH 092/178] Made channel attach mandatory on reconnection --- ably/realtime_client.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ably/realtime_client.go b/ably/realtime_client.go index 98933402a..752fdfbec 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -74,17 +74,6 @@ func (c *Realtime) onChannelMsg(msg *protocolMessage) { } func (c *Realtime) onReconnected(isNewID bool) { - if !isNewID /* RTN15c3, RTN15g3 */ { - // No need to reattach: state is preserved. We just need to flush the - // queue of pending messages. - for _, ch := range c.Channels.Iterate() { - ch.queue.Flush() - } - //RTN19a - c.Connection.resendPending() - return - } - for _, ch := range c.Channels.Iterate() { switch ch.State() { // RTN15g3, RTN15c6, RTN15c7, RTN16l, RTN19b @@ -94,6 +83,19 @@ func (c *Realtime) onReconnected(isNewID bool) { ch.detachSkipVerifyActive() } } + + if !isNewID /* RTN15c3, RTN15g3 */ { + // No need to reattach: state is preserved. We just need to flush the + // queue of pending messages. + // TODO - Once channel is attached, channel queue will be flushed + // for _, ch := range c.Channels.Iterate() { + // ch.queue.Flush() + // } + //RTN19a + c.Connection.resendPending() + return + } + //RTN19a c.Connection.resendPending() } From 6b96b8e4e9ccc110cf36c47a29f504551f81121a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 30 Oct 2023 18:46:55 +0530 Subject: [PATCH 093/178] Added implementation for resending acks without msgserial change for successful resume --- ably/realtime_client.go | 13 ++++++------- ably/realtime_conn.go | 11 ++++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/ably/realtime_client.go b/ably/realtime_client.go index 752fdfbec..92369f658 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -73,7 +73,7 @@ func (c *Realtime) onChannelMsg(msg *protocolMessage) { c.Channels.Get(msg.Channel).notify(msg) } -func (c *Realtime) onReconnected(isNewID bool) { +func (c *Realtime) onReconnected(failedResumeOrRecover bool) { for _, ch := range c.Channels.Iterate() { switch ch.State() { // RTN15g3, RTN15c6, RTN15c7, RTN16l, RTN19b @@ -84,20 +84,19 @@ func (c *Realtime) onReconnected(isNewID bool) { } } - if !isNewID /* RTN15c3, RTN15g3 */ { + if failedResumeOrRecover /* RTN15c3, RTN15g3 */ { // No need to reattach: state is preserved. We just need to flush the // queue of pending messages. // TODO - Once channel is attached, channel queue will be flushed // for _, ch := range c.Channels.Iterate() { // ch.queue.Flush() // } - //RTN19a + //RTN19a1 c.Connection.resendPending() - return + } else { + //RTN19a2 - successful resume, msgSerial doesn't change + c.Connection.resendAcks() } - - //RTN19a - c.Connection.resendPending() } func (c *Realtime) onReconnectionFailed(err *errorInfo) { diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index e835fc4b9..9137a7727 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -90,7 +90,7 @@ type connCallbacks struct { // move this up because some implementation details for (RTN15c) requires // access to Channels, and we don't have it here, so we let RealtimeClient do the // work. - onReconnected func(isNewID bool) + onReconnected func(failedResumeOrRecover bool) // onReconnectionFailed is called when we get a FAILED response from a // reconnection request. onReconnectionFailed func(*errorInfo) @@ -705,6 +705,15 @@ func (c *Connection) log() logger { return c.auth.log() } +func (c *Connection) resendAcks() { + c.mtx.Lock() + defer c.mtx.Unlock() + c.log().Debugf("resending %d messages waiting for ACK/NACK", len(c.pending.queue)) + for _, v := range c.pending.queue { + c.conn.Send(v.msg) + } +} + func (c *Connection) resendPending() { c.mtx.Lock() cx := c.pending.Dismiss() From 0308a0e89e74779d17453720efd85b366f85589f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 30 Oct 2023 23:33:35 +0530 Subject: [PATCH 094/178] Updated set channel serials from recover option --- ably/realtime_channel.go | 3 ++- ably/realtime_conn.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 67a0658b4..6c1b8f652 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -47,11 +47,12 @@ func newChannels(client *Realtime) *RealtimeChannels { } } +// RTN16j, RTL15b func (channels *RealtimeChannels) SetChannelSerialsFromRecoverOption(serials map[string]string) { channels.mtx.Lock() defer channels.mtx.Unlock() for channelName, channelSerial := range serials { - channel := channels.chans[channelName] + channel := channels.Get(channelName) channel.properties.ChannelSerial = channelSerial } } diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 9137a7727..e048443c7 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -45,7 +45,7 @@ type Connection struct { state ConnectionState // errorReason is an [ably.ErrorInfo] object describing the last error received if - // a connection failure occurs (RTN14a). + // a connection failure occurs (RTN14a, RTN15c7). errorReason *ErrorInfo internalEmitter ConnectionEventEmitter From 40e04d35da19cb57b610d26072cd0e6bbeb7ebd7 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 31 Oct 2023 01:01:23 +0530 Subject: [PATCH 095/178] updated realtime client constructor with set channel serials from recover option --- ably/realtime_client.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ably/realtime_client.go b/ably/realtime_client.go index 92369f658..3bddb095c 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -28,6 +28,14 @@ func NewRealtime(options ...ClientOption) (*Realtime, error) { c.rest = rest c.Auth = rest.Auth c.Channels = newChannels(c) + if !empty(c.opts().Recover) { + recover, err := DecodeRecoveryKey(c.opts().Recover) + if err != nil { + c.log().Errorf("Error decoding recover with error %v", err) + } else { + c.Channels.SetChannelSerialsFromRecoverOption(recover.ChannelSerials) + } + } conn := newConn(c.opts(), rest.Auth, connCallbacks{ c.onChannelMsg, c.onReconnected, From 24abbb06d76b6036d8cd28e0e4d872d947255f0e Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 31 Oct 2023 18:38:05 +0530 Subject: [PATCH 096/178] Refactored code for recoverykeycontext decoding logic --- ably/realtime_client.go | 32 +++++++++++++------------------- ably/realtime_conn.go | 20 +++++--------------- 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/ably/realtime_client.go b/ably/realtime_client.go index 3bddb095c..10f36e804 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -28,14 +28,6 @@ func NewRealtime(options ...ClientOption) (*Realtime, error) { c.rest = rest c.Auth = rest.Auth c.Channels = newChannels(c) - if !empty(c.opts().Recover) { - recover, err := DecodeRecoveryKey(c.opts().Recover) - if err != nil { - c.log().Errorf("Error decoding recover with error %v", err) - } else { - c.Channels.SetChannelSerialsFromRecoverOption(recover.ChannelSerials) - } - } conn := newConn(c.opts(), rest.Auth, connCallbacks{ c.onChannelMsg, c.onReconnected, @@ -45,6 +37,16 @@ func NewRealtime(options ...ClientOption) (*Realtime, error) { c.Channels.broadcastConnStateChange(change) }) c.Connection = conn + + if !empty(c.opts().Recover) { + recoverKeyContext, err := DecodeRecoveryKey(c.opts().Recover) + if err != nil { + c.log().Errorf("Error decoding recover with error %v", err) + } else { + c.Channels.SetChannelSerialsFromRecoverOption(recoverKeyContext.ChannelSerials) + c.Connection.msgSerial = recoverKeyContext.MsgSerial + } + } return c, nil } @@ -84,7 +86,7 @@ func (c *Realtime) onChannelMsg(msg *protocolMessage) { func (c *Realtime) onReconnected(failedResumeOrRecover bool) { for _, ch := range c.Channels.Iterate() { switch ch.State() { - // RTN15g3, RTN15c6, RTN15c7, RTN16l, RTN19b + // RTN15g3, RTN15c6, RTN15c7, RTN16l case ChannelStateAttaching, ChannelStateAttached, ChannelStateSuspended: ch.mayAttach(false) case ChannelStateDetaching: //RTN19b @@ -92,17 +94,9 @@ func (c *Realtime) onReconnected(failedResumeOrRecover bool) { } } - if failedResumeOrRecover /* RTN15c3, RTN15g3 */ { - // No need to reattach: state is preserved. We just need to flush the - // queue of pending messages. - // TODO - Once channel is attached, channel queue will be flushed - // for _, ch := range c.Channels.Iterate() { - // ch.queue.Flush() - // } - //RTN19a1 + if failedResumeOrRecover { //RTN19a1 c.Connection.resendPending() - } else { - //RTN19a2 - successful resume, msgSerial doesn't change + } else { //RTN19a2 - successful resume, msgSerial doesn't change c.Connection.resendAcks() } } diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index e048443c7..22f916c5e 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -285,11 +285,11 @@ func (c *Connection) params(mode connectionMode) (url.Values, error) { case resumeMode: query.Set("resume", c.key) case recoveryMode: - recoveryKey, err := DecodeRecoveryKey(c.opts.Recover) + recoveryKeyContext, err := DecodeRecoveryKey(c.opts.Recover) if err != nil { c.log().Errorf("error decoding recovery key, %v", err) } - query.Set("recover", recoveryKey.ConnectionKey) + query.Set("recover", recoveryKeyContext.ConnectionKey) } return query, nil } @@ -801,13 +801,12 @@ func (c *Connection) eventloop() { case actionConnected: c.mtx.Lock() - // recover is used when set via clientOptions#recover initially, resume will be used for all subsequent requests. + // recover is used when set via clientOptions#recover initially, resume will be used for all reconnects. isConnectionResumeOrRecoverAttempt := !empty(c.key) || !empty(c.opts.Recover) c.opts.Recover = "" // RTN16k, explicitly setting null so it won't be used for subsequent connection requests // we need to get this before we set c.key so as to be sure if we were // resuming or recovering the connection. - mode := c.getMode() if msg.ConnectionDetails != nil { // RTN21 connDetails = msg.ConnectionDetails c.key = connDetails.ConnectionKey //(RTN15e) (RTN16d) @@ -831,21 +830,12 @@ func (c *Connection) eventloop() { c.reconnecting = false c.reauthorizing = false } - previousID := c.id + + isNewID := c.id != msg.ConnectionID c.id = msg.ConnectionID - isNewID := previousID != msg.ConnectionID failedResumeOrRecover := isNewID && msg.Error != nil // RTN15c7, RTN16d - if reconnecting && mode == recoveryMode && msg.Error == nil { - // we are setting msgSerial as per (RTN16f) - recoveryKey, err := DecodeRecoveryKey(c.opts.Recover) - if err != nil { - c.log().Errorf("error decoding recovery key, %v", err) - } - c.msgSerial = recoveryKey.MsgSerial - } - if isConnectionResumeOrRecoverAttempt && failedResumeOrRecover { c.msgSerial = 0 } From 981eceb8323a49c73d9dfb73f0b6bac8e17af070 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 31 Oct 2023 18:55:28 +0530 Subject: [PATCH 097/178] Added suspended state when detached message is received --- ably/realtime_channel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 6c1b8f652..73c27a6b8 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -809,7 +809,7 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { c.lockSetState(ChannelStateDetached, err, false) c.mtx.Unlock() return - case ChannelStateAttached: // TODO: Also SUSPENDED; RTL13a + case ChannelStateAttached, ChannelStateSuspended: // RTL13a var res result res, err = c.lockAttach(err) if err != nil { From e10d895e3006af7c1c0a7d4b69a27fa69198cc52 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 31 Oct 2023 23:45:14 +0530 Subject: [PATCH 098/178] annotated code for connection resume and recover impl --- ably/realtime_client.go | 5 +++-- ably/realtime_conn.go | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ably/realtime_client.go b/ably/realtime_client.go index 10f36e804..a4428b2da 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -38,13 +38,14 @@ func NewRealtime(options ...ClientOption) (*Realtime, error) { }) c.Connection = conn + // RTN16 if !empty(c.opts().Recover) { recoverKeyContext, err := DecodeRecoveryKey(c.opts().Recover) if err != nil { c.log().Errorf("Error decoding recover with error %v", err) } else { - c.Channels.SetChannelSerialsFromRecoverOption(recoverKeyContext.ChannelSerials) - c.Connection.msgSerial = recoverKeyContext.MsgSerial + c.Channels.SetChannelSerialsFromRecoverOption(recoverKeyContext.ChannelSerials) // RTN16j + c.Connection.msgSerial = recoverKeyContext.MsgSerial // RTN16f } } return c, nil diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 76e407c50..c5b471cfb 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -283,13 +283,13 @@ func (c *Connection) params(mode connectionMode) (url.Values, error) { } switch mode { case resumeMode: - query.Set("resume", c.key) + query.Set("resume", c.key) // RTN15b case recoveryMode: recoveryKeyContext, err := DecodeRecoveryKey(c.opts.Recover) if err != nil { c.log().Errorf("error decoding recovery key, %v", err) } - query.Set("recover", recoveryKeyContext.ConnectionKey) + query.Set("recover", recoveryKeyContext.ConnectionKey) // RTN16k } return query, nil } @@ -500,6 +500,7 @@ func (c *Connection) RecoveryKey() string { func (c *Connection) CreateRecoveryKey() string { c.mtx.Lock() defer c.mtx.Unlock() + // RTN16g2 if empty(c.key) || c.state == ConnectionStateClosing || c.state == ConnectionStateClosed || c.state == ConnectionStateFailed || From 783d8a9c1489af711c89a842741fe6f7996ff5e0 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 1 Nov 2023 22:49:23 +0530 Subject: [PATCH 099/178] removed conn state closed from connection errors --- ably/realtime_conn_integration_test.go | 3 +-- ably/state.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ably/realtime_conn_integration_test.go b/ably/realtime_conn_integration_test.go index 471ff2650..1eb537d9b 100644 --- a/ably/realtime_conn_integration_test.go +++ b/ably/realtime_conn_integration_test.go @@ -31,8 +31,7 @@ func TestRealtimeConn_Connect(t *testing.T) { defer off() err := ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventConnected), nil) - assert.NoError(t, err, - "Connect()=%v", err) + assert.NoError(t, err, "Connect()=%v", err) err = ablytest.FullRealtimeCloser(client).Close() assert.NoError(t, err, "ablytest.FullRealtimeCloser(client).Close()=%v", err) diff --git a/ably/state.go b/ably/state.go index 59e900dce..72ac54eff 100644 --- a/ably/state.go +++ b/ably/state.go @@ -45,7 +45,7 @@ func goWaiter(f func() error) result { var ( errDisconnected = newErrorf(ErrDisconnected, "Connection temporarily unavailable") errSuspended = newErrorf(ErrConnectionSuspended, "Connection unavailable") - errClosed = newErrorf(ErrConnectionClosed, "Connection unavailable") + errClosed = newErrorf(ErrConnectionClosed, "Connection closed") errFailed = newErrorf(ErrConnectionFailed, "Connection failed") errNeverConnected = newErrorf(ErrConnectionSuspended, "Unable to establish connection") @@ -58,7 +58,6 @@ var connStateErrors = map[ConnectionState]ErrorInfo{ ConnectionStateDisconnected: *errDisconnected, ConnectionStateFailed: *errFailed, ConnectionStateSuspended: *errSuspended, - ConnectionStateClosed: *errClosed, } func connStateError(state ConnectionState, err error) *ErrorInfo { From cfa4e2316285bd8b18d78b69927269b90c3ef9d1 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 1 Nov 2023 22:57:46 +0530 Subject: [PATCH 100/178] Annotated error reason for realtime conn --- ably/realtime_conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index c5b471cfb..799cc86f2 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -45,7 +45,7 @@ type Connection struct { state ConnectionState // errorReason is an [ably.ErrorInfo] object describing the last error received if - // a connection failure occurs (RTN14a, RTN15c7). + // a connection failure occurs (RTN14a, RTN15c7, RTN25). errorReason *ErrorInfo internalEmitter ConnectionEventEmitter From 1c6276bfe87ab0f74b74ea3c19ab272af1482caa Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 2 Nov 2023 15:04:35 +0530 Subject: [PATCH 101/178] Removed unnecessary connection close test --- ably/realtime_conn_integration_test.go | 27 ++------------------------ 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/ably/realtime_conn_integration_test.go b/ably/realtime_conn_integration_test.go index 1eb537d9b..6fd54273e 100644 --- a/ably/realtime_conn_integration_test.go +++ b/ably/realtime_conn_integration_test.go @@ -23,7 +23,7 @@ var connTransitions = []ably.ConnectionState{ ably.ConnectionStateClosed, } -func TestRealtimeConn_Connect(t *testing.T) { +func TestRealtimeConn_AutoConnect_And_Close(t *testing.T) { var rec ablytest.ConnStatesRecorder app, client := ablytest.NewRealtime() defer safeclose(t, ablytest.FullRealtimeCloser(client), app) @@ -43,7 +43,7 @@ func TestRealtimeConn_Connect(t *testing.T) { } } -func TestRealtimeConn_NoConnect(t *testing.T) { +func TestRealtimeConn_No_AutoConnect(t *testing.T) { var rec ablytest.ConnStatesRecorder opts := []ably.ClientOption{ ably.WithAutoConnect(false), @@ -67,29 +67,6 @@ func TestRealtimeConn_NoConnect(t *testing.T) { } } -func TestRealtimeConn_ConnectClose(t *testing.T) { - var rec ablytest.ConnStatesRecorder - app, client := ablytest.NewRealtime() - defer safeclose(t, ablytest.FullRealtimeCloser(client), app) - off := rec.Listen(client) - defer off() - - err := ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventConnected), nil) - assert.NoError(t, err) - err = ablytest.FullRealtimeCloser(client).Close() - assert.NoError(t, err, - "ablytest.FullRealtimeCloser(client).Close()=%v", err) - - err = ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventClosed), nil) - assert.NoError(t, err) - - if !ablytest.Soon.IsTrue(func() bool { - return ablytest.Contains(rec.States(), connTransitions) - }) { - t.Fatalf("expected %+v, got %+v", connTransitions, rec.States()) - } -} - func TestRealtimeConn_AlreadyConnected(t *testing.T) { app, client := ablytest.NewRealtime(ably.WithAutoConnect(false)) defer safeclose(t, ablytest.FullRealtimeCloser(client), app) From e6db4a8e28d6885fcf64cc0e8dca060829df70f0 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 2 Nov 2023 15:22:05 +0530 Subject: [PATCH 102/178] Added explicit error for connection unavailable --- ably/realtime_conn.go | 6 +++++- ably/state.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 799cc86f2..25f3b72d7 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -599,7 +599,11 @@ func (c *Connection) send(msg *protocolMessage, onAck func(err error)) { default: c.mtx.Unlock() if onAck != nil { - onAck(connStateError(state, nil)) + if c.state == ConnectionStateClosed { + onAck(errClosed) + } else { + onAck(connStateError(state, nil)) + } } case ConnectionStateInitialized, ConnectionStateConnecting, ConnectionStateDisconnected: diff --git a/ably/state.go b/ably/state.go index 72ac54eff..a2083a7b0 100644 --- a/ably/state.go +++ b/ably/state.go @@ -45,7 +45,7 @@ func goWaiter(f func() error) result { var ( errDisconnected = newErrorf(ErrDisconnected, "Connection temporarily unavailable") errSuspended = newErrorf(ErrConnectionSuspended, "Connection unavailable") - errClosed = newErrorf(ErrConnectionClosed, "Connection closed") + errClosed = newErrorf(ErrConnectionClosed, "Connection unavailable") errFailed = newErrorf(ErrConnectionFailed, "Connection failed") errNeverConnected = newErrorf(ErrConnectionSuspended, "Unable to establish connection") From fa023dbf842ec5c70418c1d4b02251215df75bb0 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 3 Nov 2023 15:25:13 +0530 Subject: [PATCH 103/178] Fixed proto protocol message test with msgSerial and connection Serial --- ably/proto_protocol_message_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ably/proto_protocol_message_test.go b/ably/proto_protocol_message_test.go index 763a731d7..68532f5da 100644 --- a/ably/proto_protocol_message_test.go +++ b/ably/proto_protocol_message_test.go @@ -25,7 +25,7 @@ func TestProtocolMessageEncodeZeroSerials(t *testing.T) { encoded, err := ablyutil.MarshalMsgpack(msg) assert.NoError(t, err) // expect a 3-element map with both the serial fields set to zero - expected := []byte("\x83\xB0id\xA4test\xA9msgSerial\x00") + expected := []byte("\x82\xa2id\xa4test\xa9msgSerial\x00") assert.True(t, bytes.Equal(encoded, expected), "unexpected msgpack encoding\nexpected: %x\nactual: %x", expected, encoded) } From 6b4d94449a8c81c2f6d41a9bab2494e0c33c5724 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 3 Nov 2023 15:34:26 +0530 Subject: [PATCH 104/178] Switched to new branch - msgFixtures for ably-common --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index e4a5c7692..197c91e48 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit e4a5c7692044807e3011b959426868b5075c998e +Subproject commit 197c91e48a5cffa2f731d4b93574ce9eb9ce18b5 From 22050e13ae95d50b6740eb9191932eb01e5c4ba6 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 3 Nov 2023 16:33:37 +0530 Subject: [PATCH 105/178] Updated common module in sync with main --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index 197c91e48..645153d29 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 197c91e48a5cffa2f731d4b93574ce9eb9ce18b5 +Subproject commit 645153d294d64875b68f2a4a58259338ed0b915c From 6a08ee15071adf04c5c7c255faa6d851129161b1 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 3 Nov 2023 17:11:18 +0530 Subject: [PATCH 106/178] Formatted error.go file with missing errors --- ably/errors.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ably/errors.go b/ably/errors.go index fad63153b..cd18d341f 100644 --- a/ably/errors.go +++ b/ably/errors.go @@ -11,6 +11,8 @@ func (c ErrorCode) String() string { return "(error code not set)" case 10000: return "no error" + case 20000: + return "general error code" case 40000: return "bad request" case 40001: @@ -307,6 +309,16 @@ func (c ErrorCode) String() string { return "presence state is out of sync" case 91100: return "member implicitly left presence channel (connection closed)" + case 101000: + return "must have a non-empty name for the space" + case 101001: + return "must enter a space to perform this operation" + case 101002: + return "lock request already exists" + case 101003: + return "lock is currently locked" + case 101004: + return "lock was invalidated by a concurrent lock request which now holds the lock" } return "" } From 1e67fd5b2b4d5d071cf2d7528a11097a2ef0ddd8 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 3 Nov 2023 17:23:08 +0530 Subject: [PATCH 107/178] Reformatted errors.go file using gofmt --- ably/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ably/errors.go b/ably/errors.go index cd18d341f..bf74f46ce 100644 --- a/ably/errors.go +++ b/ably/errors.go @@ -11,7 +11,7 @@ func (c ErrorCode) String() string { return "(error code not set)" case 10000: return "no error" - case 20000: + case 20000: return "general error code" case 40000: return "bad request" From 4408c2ff357715adf18d3d988e6773355c99d415 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 5 Nov 2023 01:34:42 +0530 Subject: [PATCH 108/178] refactored realtime conn, annotated with spec ids --- ably/realtime_conn.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 25f3b72d7..e73b86727 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -854,17 +854,11 @@ func (c *Connection) eventloop() { continue } - // (RTN15c1) (RTN15c2) - // RTN24, RTN15c7 - if error, set on connection and part of emitted connected event + // RTN24, RTN15c6, RTN15c7 - if error, set on connection and part of emitted connected event c.lockSetState(ConnectionStateConnected, newErrorFromProto(msg.Error), 0) c.mtx.Unlock() if reconnecting { - - // (RTN15c3) - // we are calling this outside of locks to avoid deadlock because in the - // RealtimeClient client where this callback is implemented we do some ops - // with this Conn where we re acquire Conn.Lock again. c.callbacks.onReconnected(failedResumeOrRecover) } c.queue.Flush() From c64a74f33538fc5e5c4f59744c80aa2df075c98b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 5 Nov 2023 01:35:08 +0530 Subject: [PATCH 109/178] Annotated test for RTN15c6 --- ably/realtime_conn_spec_integration_test.go | 66 +++++++++------------ 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 40b596760..c339ba600 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -931,19 +931,19 @@ func recent(msgs []*ably.ProtocolMessage, action ably.ProtoAction) *ably.Protoco return nil } -func TestRealtimeConn_RTN15c1(t *testing.T) { +func TestRealtimeConn_RTN15c6(t *testing.T) { doEOF := make(chan struct{}, 1) - var metaList []*transportMessages + var dialConn []*transportMessages gotDial := make(chan chan struct{}) app, client := ablytest.NewRealtime( ably.WithAutoConnect(false), ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { m := &transportMessages{dial: u} - metaList = append(metaList, m) - if len(metaList) > 1 { + dialConn = append(dialConn, m) + if len(dialConn) > 1 { goOn := make(chan struct{}) gotDial <- goOn <-goOn @@ -970,22 +970,19 @@ func TestRealtimeConn_RTN15c1(t *testing.T) { off := channel.OnAll(chanStateChanges.Receive) defer off() - stateChanges := make(chan ably.ConnectionStateChange, 16) + connStateChanges := make(chan ably.ConnectionStateChange, 16) client.Connection.OnAll(func(c ably.ConnectionStateChange) { - stateChanges <- c + connStateChanges <- c }) doEOF <- struct{}{} - var state ably.ConnectionStateChange + var connState ably.ConnectionStateChange - select { - case state = <-stateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't transition on EOF") - } - assert.Equal(t, ably.ConnectionStateDisconnected, state.Current, - "expected transition to %v, got %v", ably.ConnectionStateDisconnected, state.Current) + ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) + + assert.Equal(t, ably.ConnectionStateDisconnected, connState.Current, + "expected transition to %v, got %v", ably.ConnectionStateDisconnected, connState.Current) rest, err := ably.NewREST(app.Options()...) assert.NoError(t, err) goOn := <-gotDial @@ -993,34 +990,27 @@ func TestRealtimeConn_RTN15c1(t *testing.T) { assert.NoError(t, err) close(goOn) - select { - case state = <-stateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't reconnect") - } - assert.Equal(t, ably.ConnectionStateConnecting, state.Current, - "expected transition to %v, got %v", ably.ConnectionStateConnecting, state.Current) - select { - case msg := <-sub: - assert.Equal(t, "data", msg.Data, - "expected message with data \"data\" got %v", msg.Data) - case <-time.After(ablytest.Timeout): - t.Fatal("expected message after connection recovery; got none") - } + ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) + assert.Equal(t, ably.ConnectionStateConnecting, connState.Current, "expected no state change; got %+v", connState.Current) - // (RTN15c1) - // - // - current connectionId == resume message connectionId - // - resume message has no error - // - no channel state changes happened. + ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) + assert.Equal(t, ably.ConnectionStateConnected, connState.Current, "expected no state change; got %+v", connState.Current) + assert.Nil(t, connState.Reason, "expected no state change; got %+v", connState.Current) + // Check channel goes into attaching and attached state var change ably.ChannelStateChange ablytest.Soon.Recv(t, &change, chanStateChanges, t.Fatalf) - assert.Equal(t, change.Previous, change.Current, - "expected no state change; got %+v", change) - assert.Equal(t, client.Connection.ID(), metaList[1].Messages()[0].ConnectionID, - "expected %q to equal %q", client.Connection.ID(), metaList[1].Messages()[0].ConnectionID) - assert.Nil(t, metaList[1].Messages()[0].Error, + assert.Equal(t, ably.ChannelStateAttaching, change.Current, "expected no state change; got %+v", change) + ablytest.Soon.Recv(t, &change, chanStateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttached, change.Current, "expected no state change; got %+v", change) + + var msg *ably.Message + ablytest.Soon.Recv(t, &msg, sub, t.Fatalf) + + // Check for resume success + assert.Equal(t, client.Connection.ID(), dialConn[1].Messages()[0].ConnectionID, + "expected %q to equal %q", client.Connection.ID(), dialConn[1].Messages()[0].ConnectionID) + assert.Nil(t, dialConn[1].Messages()[0].Error, "expected resume error to be nil") } From f0456388785f5e1016fbeeaa16afe8d7083efae9 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 01:03:34 +0530 Subject: [PATCH 110/178] Updated integration test for RTNc3, replaced with RTNc7 --- ably/realtime_conn_spec_integration_test.go | 127 ++------------------ 1 file changed, 9 insertions(+), 118 deletions(-) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index c339ba600..3d5ea00b3 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -995,7 +995,7 @@ func TestRealtimeConn_RTN15c6(t *testing.T) { ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) assert.Equal(t, ably.ConnectionStateConnected, connState.Current, "expected no state change; got %+v", connState.Current) - assert.Nil(t, connState.Reason, "expected no state change; got %+v", connState.Current) + assert.Nil(t, connState.Reason, "expected nil conn error, got %+v", connState.Reason) // Check channel goes into attaching and attached state var change ably.ChannelStateChange @@ -1014,12 +1014,11 @@ func TestRealtimeConn_RTN15c6(t *testing.T) { "expected resume error to be nil") } -func TestRealtimeConn_RTN15c2(t *testing.T) { +func TestRealtimeConn_RTN15c7_attached(t *testing.T) { doEOF := make(chan struct{}, 1) - + throwResumeErr := false var metaList []*transportMessages - gotDial := make(chan chan struct{}) app, client := ablytest.NewRealtime( ably.WithAutoConnect(false), @@ -1033,118 +1032,9 @@ func TestRealtimeConn_RTN15c2(t *testing.T) { } c, err := ably.DialWebsocket(protocol, u, timeout) return protoConnWithFakeEOF{Conn: c, doEOF: doEOF, onMessage: func(msg *ably.ProtocolMessage) { - if len(metaList) == 2 && len(m.Messages()) == 0 { + if throwResumeErr && msg.Action == ably.ActionConnected { msg.Error = &ably.ProtoErrorInfo{StatusCode: 401} - } - m.Add(msg) - }}, err - })) - defer safeclose(t, ablytest.FullRealtimeCloser(client), app) - - err := ablytest.Wait(ablytest.ConnWaiter(client, client.Connect, ably.ConnectionEventConnected), nil) - assert.NoError(t, err, - "Connect=%s", err) - - channel := client.Channels.Get("channel") - err = channel.Attach(context.Background()) - assert.NoError(t, err) - chanStateChanges := make(ably.ChannelStateChanges) - off := channel.OnAll(chanStateChanges.Receive) - defer off() - - sub, unsub, err := ablytest.ReceiveMessages(channel, "") - assert.NoError(t, err) - defer unsub() - - stateChanges := make(chan ably.ConnectionStateChange, 16) - client.Connection.OnAll(func(c ably.ConnectionStateChange) { - stateChanges <- c - }) - - prevMsgSerial := client.Connection.MsgSerial() - doEOF <- struct{}{} - - var state ably.ConnectionStateChange - - select { - case state = <-stateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't transition on EOF") - } - assert.Equal(t, ably.ConnectionStateDisconnected, state.Current, - "expected transition to %v, got %v", ably.ConnectionStateDisconnected, state.Current) - rest, err := ably.NewREST(app.Options()...) - assert.NoError(t, err) - goOn := <-gotDial - err = rest.Channels.Get("channel").Publish(context.Background(), "name", "data") - assert.NoError(t, err) - close(goOn) - - select { - case state = <-stateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't reconnect") - } - assert.Equal(t, ably.ConnectionStateConnecting, state.Current, - "expected transition to %v, got %v", ably.ConnectionStateConnecting, state.Current) - - select { - case msg := <-sub: - assert.Equal(t, "data", msg.Data, - "expected message with data \"data\", got %v", msg.Data) - case <-time.After(ablytest.Timeout): - t.Fatal("expected message after connection recovery; got none") - } - - // (RTN15c2) - // - // - current connectionId == resume message connectionId - // - resume message has an error - // - Conn.Reqson == message resume error - // - no channel state changes happened. - - var change ably.ChannelStateChange - ablytest.Soon.Recv(t, &change, chanStateChanges, t.Fatalf) - assert.Equal(t, change.Previous, change.Current, - "expected no state change; got %+v", change) - assert.Equal(t, client.Connection.ID(), metaList[1].Messages()[0].ConnectionID, - "expected %q to equal %q", client.Connection.ID(), metaList[1].Messages()[0].ConnectionID) - assert.NotNil(t, metaList[1].Messages()[0].Error, - "expected resume error") - - err = client.Connection.ErrorReason() - assert.NotNil(t, err, - "expected reason to be set") - - reason := err.(*ably.ErrorInfo) - assert.Equal(t, 401, reason.StatusCode, - "expected status code 401 got %d", reason.StatusCode) - assert.Equal(t, prevMsgSerial, client.Connection.MsgSerial(), - "msgSerial shouldn't be reset on resumed connection") -} - -func TestRealtimeConn_RTN15c3_attached(t *testing.T) { - - doEOF := make(chan struct{}, 1) - - var metaList []*transportMessages - connID := "new-conn-id" - gotDial := make(chan chan struct{}) - app, client := ablytest.NewRealtime( - ably.WithAutoConnect(false), - ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { - m := &transportMessages{dial: u} - metaList = append(metaList, m) - if len(metaList) > 1 { - goOn := make(chan struct{}) - gotDial <- goOn - <-goOn - } - c, err := ably.DialWebsocket(protocol, u, timeout) - return protoConnWithFakeEOF{Conn: c, doEOF: doEOF, onMessage: func(msg *ably.ProtocolMessage) { - if len(metaList) == 2 && len(m.Messages()) == 0 { - msg.Error = &ably.ProtoErrorInfo{StatusCode: 401} - msg.ConnectionID = connID + msg.ConnectionID = "new-conn-id" } m.Add(msg) }}, err @@ -1172,6 +1062,7 @@ func TestRealtimeConn_RTN15c3_attached(t *testing.T) { stateChanges <- c }) + throwResumeErr = true doEOF <- struct{}{} var state ably.ConnectionStateChange @@ -1183,6 +1074,7 @@ func TestRealtimeConn_RTN15c3_attached(t *testing.T) { } assert.Equal(t, ably.ConnectionStateDisconnected, state.Current, "expected transition to %v, got %v", ably.ConnectionStateDisconnected, state.Current) + rest, err := ably.NewREST(app.Options()...) assert.NoError(t, err) goOn := <-gotDial @@ -1219,12 +1111,11 @@ func TestRealtimeConn_RTN15c3_attached(t *testing.T) { "expected msgSerial to be reset; got %d", client.Connection.MsgSerial()) } -func TestRealtimeConn_RTN15c3_attaching(t *testing.T) { +func TestRealtimeConn_RTN15c7_attaching(t *testing.T) { doEOF := make(chan struct{}, 1) var metaList []*transportMessages - connID := "new-conn-id" gotDial := make(chan chan struct{}) app, client := ablytest.NewRealtime( ably.WithAutoConnect(false), @@ -1240,7 +1131,7 @@ func TestRealtimeConn_RTN15c3_attaching(t *testing.T) { return protoConnWithFakeEOF{Conn: c, doEOF: doEOF, onMessage: func(msg *ably.ProtocolMessage) { if len(metaList) == 2 && len(m.Messages()) == 0 { msg.Error = &ably.ProtoErrorInfo{StatusCode: 401} - msg.ConnectionID = connID + msg.ConnectionID = "new-conn-id" } if msg.Action == ably.ActionAttached { msg.Action = ably.ActionHeartbeat From e3c4336a71196a53fb129ab512c3056d221c0574 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 02:08:13 +0530 Subject: [PATCH 111/178] Refactored connection resume tests related to RTN15c6 and RTN15c7 --- ably/export_test.go | 12 + ably/realtime_conn_spec_integration_test.go | 269 +++++++------------- 2 files changed, 106 insertions(+), 175 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index 39151bcd8..2f25b184e 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -105,6 +105,12 @@ func (c *RealtimeChannel) SetAttachResume(value bool) { c.attachResume = value } +func (c *RealtimeChannel) SetState(chanState ChannelState) { + c.mtx.Lock() + defer c.mtx.Unlock() + c.state = chanState +} + func (opts *clientOptions) GetFallbackRetryTimeout() time.Duration { return opts.fallbackRetryTimeout() } @@ -193,6 +199,12 @@ func (c *Connection) PendingItems() int { return len(c.pending.queue) } +func (c *Connection) SetKey(key string) { + c.mtx.Lock() + defer c.mtx.Unlock() + c.key = key +} + func (c *Connection) ConnectionStateTTL() time.Duration { return c.connectionStateTTL() } diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 3d5ea00b3..f971f4747 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -935,28 +935,29 @@ func TestRealtimeConn_RTN15c6(t *testing.T) { doEOF := make(chan struct{}, 1) - var dialConn []*transportMessages - gotDial := make(chan chan struct{}) + continueDial := make(chan struct{}, 1) + continueDial <- struct{}{} app, client := ablytest.NewRealtime( ably.WithAutoConnect(false), ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { - m := &transportMessages{dial: u} - dialConn = append(dialConn, m) - if len(dialConn) > 1 { - goOn := make(chan struct{}) - gotDial <- goOn - <-goOn - } + <-continueDial c, err := ably.DialWebsocket(protocol, u, timeout) - return protoConnWithFakeEOF{Conn: c, doEOF: doEOF, onMessage: func(msg *ably.ProtocolMessage) { - m.Add(msg) - }}, err + return protoConnWithFakeEOF{ + Conn: c, + doEOF: doEOF, + }, err })) defer safeclose(t, ablytest.FullRealtimeCloser(client), app) err := ablytest.Wait(ablytest.ConnWaiter(client, client.Connect, ably.ConnectionEventConnected), nil) assert.NoError(t, err, "Connect=%s", err) + prevConnId := client.Connection.ID() + + // Increase msgSerial, to test that it doesn't reset later. + err = client.Channels.Get("publish").Publish(context.Background(), "test", nil) + assert.NoError(t, err) + assert.NotZero(t, client.Connection.MsgSerial()) channel := client.Channels.Get("channel") err = channel.Attach(context.Background()) @@ -983,18 +984,19 @@ func TestRealtimeConn_RTN15c6(t *testing.T) { assert.Equal(t, ably.ConnectionStateDisconnected, connState.Current, "expected transition to %v, got %v", ably.ConnectionStateDisconnected, connState.Current) + rest, err := ably.NewREST(app.Options()...) assert.NoError(t, err) - goOn := <-gotDial err = rest.Channels.Get("channel").Publish(context.Background(), "name", "data") assert.NoError(t, err) - close(goOn) + + continueDial <- struct{}{} ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) - assert.Equal(t, ably.ConnectionStateConnecting, connState.Current, "expected no state change; got %+v", connState.Current) + assert.Equal(t, ably.ConnectionStateConnecting, connState.Current, "expected connecting; got %+v", connState.Current) ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) - assert.Equal(t, ably.ConnectionStateConnected, connState.Current, "expected no state change; got %+v", connState.Current) + assert.Equal(t, ably.ConnectionStateConnected, connState.Current, "expected connected; got %+v", connState.Current) assert.Nil(t, connState.Reason, "expected nil conn error, got %+v", connState.Reason) // Check channel goes into attaching and attached state @@ -1004,210 +1006,127 @@ func TestRealtimeConn_RTN15c6(t *testing.T) { ablytest.Soon.Recv(t, &change, chanStateChanges, t.Fatalf) assert.Equal(t, ably.ChannelStateAttached, change.Current, "expected no state change; got %+v", change) + // Expect message to be received after resume success var msg *ably.Message ablytest.Soon.Recv(t, &msg, sub, t.Fatalf) // Check for resume success - assert.Equal(t, client.Connection.ID(), dialConn[1].Messages()[0].ConnectionID, - "expected %q to equal %q", client.Connection.ID(), dialConn[1].Messages()[0].ConnectionID) - assert.Nil(t, dialConn[1].Messages()[0].Error, - "expected resume error to be nil") -} - -func TestRealtimeConn_RTN15c7_attached(t *testing.T) { - - doEOF := make(chan struct{}, 1) - throwResumeErr := false - var metaList []*transportMessages - gotDial := make(chan chan struct{}) - app, client := ablytest.NewRealtime( - ably.WithAutoConnect(false), - ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { - m := &transportMessages{dial: u} - metaList = append(metaList, m) - if len(metaList) > 1 { - goOn := make(chan struct{}) - gotDial <- goOn - <-goOn - } - c, err := ably.DialWebsocket(protocol, u, timeout) - return protoConnWithFakeEOF{Conn: c, doEOF: doEOF, onMessage: func(msg *ably.ProtocolMessage) { - if throwResumeErr && msg.Action == ably.ActionConnected { - msg.Error = &ably.ProtoErrorInfo{StatusCode: 401} - msg.ConnectionID = "new-conn-id" - } - m.Add(msg) - }}, err - })) - defer safeclose(t, ablytest.FullRealtimeCloser(client), app) - - err := ablytest.Wait(ablytest.ConnWaiter(client, client.Connect, ably.ConnectionEventConnected), nil) - assert.NoError(t, err, - "Connect=%s", err) + assert.Equal(t, prevConnId, client.Connection.ID()) + assert.Nil(t, client.Connection.ErrorReason()) + assert.NotZero(t, client.Connection.MsgSerial()) - // Increase msgSerial, to test that it gets reset later. - err = client.Channels.Get("publish").Publish(context.Background(), "test", nil) - assert.NoError(t, err) - - channel := client.Channels.Get("channel") - err = channel.Attach(context.Background()) - assert.NoError(t, err) - - chanStateChanges := make(ably.ChannelStateChanges, 18) - off := channel.On(ably.ChannelEventAttaching, chanStateChanges.Receive) - defer off() - - stateChanges := make(chan ably.ConnectionStateChange, 16) - client.Connection.OnAll(func(c ably.ConnectionStateChange) { - stateChanges <- c - }) - - throwResumeErr = true + // Set channel to attaching state + channel.SetState(ably.ChannelStateAttaching) doEOF <- struct{}{} + continueDial <- struct{}{} - var state ably.ConnectionStateChange - - select { - case state = <-stateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't transition on EOF") - } - assert.Equal(t, ably.ConnectionStateDisconnected, state.Current, - "expected transition to %v, got %v", ably.ConnectionStateDisconnected, state.Current) - - rest, err := ably.NewREST(app.Options()...) - assert.NoError(t, err) - goOn := <-gotDial - err = rest.Channels.Get("channel").Publish(context.Background(), "name", "data") - assert.NoError(t, err) - close(goOn) - - select { - case state = <-stateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't reconnect") - } - assert.Equal(t, ably.ConnectionStateConnecting, state.Current, - "expected transition to %v, got %v", ably.ConnectionStateConnecting, state.Current) + // Check channel goes into attaching and attached state + ablytest.Soon.Recv(t, &change, chanStateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttaching, change.Current, "expected no state change; got %+v", change) + ablytest.Soon.Recv(t, &change, chanStateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttached, change.Current, "expected no state change; got %+v", change) - <-stateChanges + // Set channel to suspended state + channel.SetState(ably.ChannelStateSuspended) + doEOF <- struct{}{} + continueDial <- struct{}{} - var chanState ably.ChannelStateChange - select { - case chanState = <-chanStateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't change state") - } - // we are testing to make sure we have initiated a new attach for channels - // in ATTACHED state. - assert.Equal(t, ably.ChannelStateAttaching, chanState.Current, - "expected transition to %v, got %v", ably.ChannelStateAttaching, chanState.Current) + // Check channel goes into attaching and attached state + ablytest.Soon.Recv(t, &change, chanStateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttaching, change.Current, "expected no state change; got %+v", change) + ablytest.Soon.Recv(t, &change, chanStateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttached, change.Current, "expected no state change; got %+v", change) - reason := client.Connection.ErrorReason() - assert.NotNil(t, reason, "expected reason to be set") - assert.Equal(t, 401, reason.StatusCode, - "expected status code 401 got %d", reason.StatusCode) - assert.Equal(t, int64(0), client.Connection.MsgSerial(), - "expected msgSerial to be reset; got %d", client.Connection.MsgSerial()) + // Check for resume success + assert.Equal(t, prevConnId, client.Connection.ID()) + assert.Nil(t, client.Connection.ErrorReason()) + assert.NotZero(t, client.Connection.MsgSerial()) } -func TestRealtimeConn_RTN15c7_attaching(t *testing.T) { +func TestRealtimeConn_RTN15c7_attached(t *testing.T) { doEOF := make(chan struct{}, 1) - var metaList []*transportMessages - gotDial := make(chan chan struct{}) + continueDial := make(chan struct{}, 1) + continueDial <- struct{}{} + app, client := ablytest.NewRealtime( ably.WithAutoConnect(false), ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { - m := &transportMessages{dial: u} - metaList = append(metaList, m) - if len(metaList) > 1 { - goOn := make(chan struct{}) - gotDial <- goOn - <-goOn - } + <-continueDial c, err := ably.DialWebsocket(protocol, u, timeout) - return protoConnWithFakeEOF{Conn: c, doEOF: doEOF, onMessage: func(msg *ably.ProtocolMessage) { - if len(metaList) == 2 && len(m.Messages()) == 0 { - msg.Error = &ably.ProtoErrorInfo{StatusCode: 401} - msg.ConnectionID = "new-conn-id" - } - if msg.Action == ably.ActionAttached { - msg.Action = ably.ActionHeartbeat - } - m.Add(msg) - }}, err + return protoConnWithFakeEOF{ + Conn: c, + doEOF: doEOF, + }, err })) defer safeclose(t, ablytest.FullRealtimeCloser(client), app) err := ablytest.Wait(ablytest.ConnWaiter(client, client.Connect, ably.ConnectionEventConnected), nil) - assert.NoError(t, err, - "Connect=%s", err) + assert.NoError(t, err, "Connect=%s", err) + prevConnId := client.Connection.ID() // Increase msgSerial, to test that it gets reset later. err = client.Channels.Get("publish").Publish(context.Background(), "test", nil) assert.NoError(t, err) + assert.NotZero(t, client.Connection.MsgSerial()) channel := client.Channels.Get("channel") - attaching := make(ably.ChannelStateChanges, 1) - off := channel.On(ably.ChannelEventAttaching, attaching.Receive) - defer off() + err = channel.Attach(context.Background()) + assert.NoError(t, err) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go func() { - channel.Attach(ctx) - }() + _, unsub, err := ablytest.ReceiveMessages(channel, "") + assert.NoError(t, err) + defer unsub() - ablytest.Soon.Recv(t, nil, attaching, t.Fatalf) + chanStateChanges := make(ably.ChannelStateChanges) + off := channel.OnAll(chanStateChanges.Receive) + defer off() - stateChanges := make(chan ably.ConnectionStateChange, 16) + connStateChanges := make(chan ably.ConnectionStateChange, 16) client.Connection.OnAll(func(c ably.ConnectionStateChange) { - stateChanges <- c + connStateChanges <- c }) + client.Connection.SetKey("xxxxx!xxxxxxx-xxxxxxxx-xxxxxxxx") // invalid connection key for next resume request doEOF <- struct{}{} - var state ably.ConnectionStateChange + var connState ably.ConnectionStateChange + + ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) + assert.Equal(t, ably.ConnectionStateDisconnected, connState.Current, + "expected transition to %v, got %v", ably.ConnectionStateDisconnected, connState.Current) - select { - case state = <-stateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't transition on EOF") - } - assert.Equal(t, ably.ConnectionStateDisconnected, state.Current, - "expected transition to %v, got %v", ably.ConnectionStateDisconnected, state.Current) rest, err := ably.NewREST(app.Options()...) assert.NoError(t, err) - - goOn := <-gotDial - err = rest.Channels.Get("channel").Publish(ctx, "name", "data") + err = rest.Channels.Get("channel").Publish(context.Background(), "name", "data") assert.NoError(t, err) - close(goOn) - select { - case state = <-stateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't reconnect") - } - assert.Equal(t, ably.ConnectionStateConnecting, state.Current, - "expected transition to %v, got %v", ably.ConnectionStateConnecting, state.Current) + continueDial <- struct{}{} - <-stateChanges + ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) + assert.Equal(t, ably.ConnectionStateConnecting, connState.Current) - // we are testing to make sure we have initiated a new attach for channels - // in ATTACHING state. - assert.Equal(t, ably.ChannelStateAttaching, channel.State(), - "expected transition to %v, got %v", ably.ChannelStateAttaching, channel.State()) + ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) + assert.Equal(t, ably.ConnectionStateConnected, connState.Current) + assert.NotNil(t, connState.Reason, "expected not nil connError, got nil connError") - reason := client.Connection.ErrorReason() - assert.NotNil(t, reason, - "expected reason to be set") - assert.Equal(t, 401, reason.StatusCode, - "expected status code 401 got %d", reason.StatusCode) - assert.Equal(t, int64(0), client.Connection.MsgSerial(), - "expected msgSerial to be reset; got %d", client.Connection.MsgSerial()) + // Check channel goes into attaching and attached state + var change ably.ChannelStateChange + ablytest.Soon.Recv(t, &change, chanStateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttaching, change.Current) + ablytest.Soon.Recv(t, &change, chanStateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttached, change.Current) + + // Check for resume failure + assert.NotEqual(t, prevConnId, client.Connection.ID()) + assert.Zero(t, client.Connection.MsgSerial()) + assert.NotNil(t, client.Connection.ErrorReason()) + assert.Equal(t, 400, client.Connection.ErrorReason().StatusCode) + + // Todo - Expect message not to be arrived due to resume failure + // var msg *ably.Message + // ablytest.Soon.NoRecv(t, &msg, sub, t.Fatalf) } func TestRealtimeConn_RTN15c4(t *testing.T) { From aa5633ed294d4756644ccd7c4334f0619d7eb752 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 02:18:06 +0530 Subject: [PATCH 112/178] Added test to make sure channel goes into failed state --- ably/realtime_conn_spec_integration_test.go | 79 ++++++++------------- 1 file changed, 30 insertions(+), 49 deletions(-) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index f971f4747..1297da2b8 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -1133,32 +1133,22 @@ func TestRealtimeConn_RTN15c4(t *testing.T) { doEOF := make(chan struct{}, 1) - var metaList []*transportMessages - gotDial := make(chan chan struct{}) + continueDial := make(chan struct{}, 1) + continueDial <- struct{}{} app, client := ablytest.NewRealtime( ably.WithAutoConnect(false), ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { - m := &transportMessages{dial: u} - metaList = append(metaList, m) - if len(metaList) > 1 { - goOn := make(chan struct{}) - gotDial <- goOn - <-goOn - } + <-continueDial c, err := ably.DialWebsocket(protocol, u, timeout) - return protoConnWithFakeEOF{Conn: c, doEOF: doEOF, onMessage: func(msg *ably.ProtocolMessage) { - if len(metaList) == 2 && len(m.Messages()) == 0 { - msg.Action = ably.ActionError - msg.Error = &ably.ProtoErrorInfo{StatusCode: http.StatusBadRequest} - } - m.Add(msg) - }}, err + return protoConnWithFakeEOF{ + Conn: c, + doEOF: doEOF, + }, err })) defer safeclose(t, &closeClient{Closer: ablytest.FullRealtimeCloser(client), skip: []int{http.StatusBadRequest}}, app) err := ablytest.Wait(ablytest.ConnWaiter(client, client.Connect, ably.ConnectionEventConnected), nil) - assert.NoError(t, err, - "Connect=%s", err) + assert.NoError(t, err, "Connect=%s", err) channel := client.Channels.Get("channel") err = channel.Attach(context.Background()) @@ -1167,52 +1157,43 @@ func TestRealtimeConn_RTN15c4(t *testing.T) { off := channel.On(ably.ChannelEventFailed, chanStateChanges.Receive) defer off() - stateChanges := make(chan ably.ConnectionStateChange, 16) + connStateChanges := make(chan ably.ConnectionStateChange, 16) client.Connection.OnAll(func(c ably.ConnectionStateChange) { - stateChanges <- c + connStateChanges <- c }) + client.Connection.SetKey("wrong-conn-key") // wrong connection key for next resume request doEOF <- struct{}{} - var state ably.ConnectionStateChange + var connState ably.ConnectionStateChange + + ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) + assert.Equal(t, ably.ConnectionStateDisconnected, connState.Current, + "expected transition to %v, got %v", ably.ConnectionStateDisconnected, connState.Current) - select { - case state = <-stateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't transition on EOF") - } - assert.Equal(t, ably.ConnectionStateDisconnected, state.Current, - "expected transition to %v, got %v", ably.ConnectionStateDisconnected, state.Current) rest, err := ably.NewREST(app.Options()...) assert.NoError(t, err) - goOn := <-gotDial err = rest.Channels.Get("channel").Publish(context.Background(), "name", "data") assert.NoError(t, err) - close(goOn) - select { - case state = <-stateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't reconnect") - } - assert.Equal(t, ably.ConnectionStateConnecting, state.Current, - "expected transition to %v, got %v", ably.ConnectionStateConnecting, state.Current) - <-stateChanges - var chanState ably.ChannelStateChange - select { - case chanState = <-chanStateChanges: - case <-time.After(50 * time.Millisecond): - t.Fatal("didn't change state") - } - assert.Equal(t, ably.ChannelStateFailed, chanState.Current, - "expected transition to %v, got %v", ably.ChannelStateFailed, chanState.Current) + continueDial <- struct{}{} + + ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) + assert.Equal(t, ably.ConnectionStateConnecting, connState.Current) + + // Connection goes into failed state + ablytest.Soon.Recv(t, &connState, connStateChanges, t.Fatalf) + assert.Equal(t, ably.ConnectionStateFailed, connState.Current) + + // Check channel goes into failed state + var change ably.ChannelStateChange + ablytest.Soon.Recv(t, &change, chanStateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateFailed, change.Current) + reason := client.Connection.ErrorReason() assert.NotNil(t, reason, "expected reason to be set") assert.Equal(t, http.StatusBadRequest, reason.StatusCode, "expected %d got %d", http.StatusBadRequest, reason.StatusCode) - // The client should transition to the FAILED state - assert.Equal(t, ably.ConnectionStateFailed, client.Connection.State(), - "expected transition to %v, got %v", ably.ConnectionStateFailed, client.Connection.State()) } func TestRealtimeConn_RTN15d_MessageRecovery(t *testing.T) { From d1771c1163ef4afd2381aa6ef32215c7d64c3fd7 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 02:56:27 +0530 Subject: [PATCH 113/178] Fixed failing test for channel attach --- .../realtime_channel_spec_integration_test.go | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/ably/realtime_channel_spec_integration_test.go b/ably/realtime_channel_spec_integration_test.go index 6be511330..af6352826 100644 --- a/ably/realtime_channel_spec_integration_test.go +++ b/ably/realtime_channel_spec_integration_test.go @@ -184,9 +184,6 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4a: If already attached, nothing is done", func(t *testing.T) { in, out, _, channel, stateChanges, _ := setup(t) - ctx, cancel := context.WithCancel(context.Background()) - - cancel() channel.OnAll(stateChanges.Receive) // Get the channel to ATTACHED. @@ -203,7 +200,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, change.Current) // Attach the channel again - channel.Attach(ctx) + channel.Attach(canceledCtx) ablytest.Instantly.NoRecv(t, nil, out, t.Fatalf) ablytest.Instantly.NoRecv(t, nil, stateChanges, t.Fatalf) @@ -742,8 +739,8 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelTransitioner.To(chAttaching) // check if attach message is sent - checkIfAttachSent := recorder.CheckIfSent(ably.ActionAttach, 1) - attachSent := ablytest.Instantly.IsTrue(checkIfAttachSent) + checkIfAttachSentFn := recorder.CheckIfSent(ably.ActionAttach, 1) + attachSent := ablytest.Instantly.IsTrue(checkIfAttachSentFn) assert.True(t, attachSent, "Should send attach message, since channel is attached") @@ -764,8 +761,8 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { }) // Check that the attach message isn't sent - checkIfAttachSent = recorder.CheckIfSent(ably.ActionAttach, 1) - attachSent = ablytest.Instantly.IsTrue(checkIfAttachSent) + checkIfAttachSentFn = recorder.CheckIfSent(ably.ActionAttach, 1) + attachSent = ablytest.Instantly.IsTrue(checkIfAttachSentFn) assert.False(t, attachSent, "Attach message was sent") @@ -807,8 +804,8 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelTransitioner.To(chAttaching, chAttached, chDetaching) // check if attach message is sent - checkIfAttachSent := recorder.CheckIfSent(ably.ActionAttach, 1) - attachSent := ablytest.Instantly.IsTrue(checkIfAttachSent) + checkIfAttachSentFn := recorder.CheckIfSent(ably.ActionAttach, 1) + attachSent := ablytest.Instantly.IsTrue(checkIfAttachSentFn) assert.True(t, attachSent, "Should send attach message, since channel is attached") @@ -837,8 +834,8 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { }) // Check that the attach message isn't sent - checkIfAttachSent = recorder.CheckIfSent(ably.ActionAttach, 1) - attachSent = ablytest.Instantly.IsTrue(checkIfAttachSent) + checkIfAttachSentFn = recorder.CheckIfSent(ably.ActionAttach, 1) + attachSent = ablytest.Instantly.IsTrue(checkIfAttachSentFn) assert.False(t, attachSent, "Attach message was sent before connection is established") ablytest.Instantly.NoRecv(t, nil, channelStateChanges, t.Fatalf) // Shouldn't send attach, waiting for detach @@ -850,7 +847,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { "expected %v; got %v (event: %+v)", ably.ChannelStateDetached, channelStatechange.Current, channelStatechange) // Check that the attach message is sent - attachSent = ablytest.Instantly.IsTrue(checkIfAttachSent) + attachSent = ablytest.Instantly.IsTrue(checkIfAttachSentFn) assert.True(t, attachSent, "Should send attach message, since channel is detached") ablytest.Instantly.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf) @@ -893,8 +890,8 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channel.Attach(ctx) // Check that the attach message isn't sent - checkIfAttachSent := recorder.CheckIfSent(ably.ActionAttach, 1) - attachSent := ablytest.Instantly.IsTrue(checkIfAttachSent) + checkIfAttachSentFn := recorder.CheckIfSent(ably.ActionAttach, 1) + attachSent := ablytest.Instantly.IsTrue(checkIfAttachSentFn) assert.False(t, attachSent, "Attach message was sent before connection is established") @@ -913,7 +910,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { defer safeclose(t, closer) // Check that the attach message is sent - attachSent = ablytest.Instantly.IsTrue(checkIfAttachSent) + attachSent = ablytest.Instantly.IsTrue(checkIfAttachSentFn) assert.True(t, attachSent, "Should send attach message, since connected") @@ -954,8 +951,8 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channel.Attach(ctx) // Check that the attach message isn't sent - checkIfAttachSent := recorder.CheckIfSent(ably.ActionAttach, 1) - attachSent := ablytest.Instantly.IsTrue(checkIfAttachSent) + checkIfAttachSentFn := recorder.CheckIfSent(ably.ActionAttach, 1) + attachSent := ablytest.Instantly.IsTrue(checkIfAttachSentFn) assert.False(t, attachSent, "Attach message was sent before connection is established") @@ -973,11 +970,16 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { ) // Check that the attach message is sent - attachSent = ablytest.Instantly.IsTrue(checkIfAttachSent) + attachSent = ablytest.Instantly.IsTrue(checkIfAttachSentFn) assert.True(t, attachSent, "Should send attach message, since connected") defer safeclose(t, closer) + // Reconnection makes explicit attach for each channel RTN15c6, RTN15c7 + ablytest.Soon.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttaching, channelStatechange.Current, + "expected %v; got %v (event: %+v)", ably.ChannelStateAttaching, channelStatechange.Current, channelStatechange) + ablytest.Soon.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf) assert.Equal(t, ably.ChannelStateAttached, channelStatechange.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, channelStatechange.Current, channelStatechange) @@ -2855,9 +2857,7 @@ func TestRealtimeChannel_RTL14_HandleChannelError(t *testing.T) { channel = c.Channels.Get("test") - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go channel.Attach(ctx) + go channel.Attach(canceledCtx) ablytest.Instantly.Recv(t, nil, afterCalls, t.Fatalf) // Consume timer ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume ATTACHING From 515a6541ea3326d442584ef43009fb179c108699 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 03:00:56 +0530 Subject: [PATCH 114/178] refactored realtime channel spec integration for cancelled context --- .../realtime_channel_spec_integration_test.go | 124 ++++-------------- 1 file changed, 28 insertions(+), 96 deletions(-) diff --git a/ably/realtime_channel_spec_integration_test.go b/ably/realtime_channel_spec_integration_test.go index af6352826..dc1b4b397 100644 --- a/ably/realtime_channel_spec_integration_test.go +++ b/ably/realtime_channel_spec_integration_test.go @@ -223,11 +223,8 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - // connection is initialized - err = channel.Attach(ctx) + err = channel.Attach(canceledCtx) // Check that the attach message isn't sent checkIfAttachSent := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -304,10 +301,8 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() // connection is failed - err = channel.Attach(ctx) + err = channel.Attach(canceledCtx) // Check that the attach message isn't sent checkIfAttachSent := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -346,10 +341,8 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() // connection state is suspended - err = channel.Attach(ctx) + err = channel.Attach(canceledCtx) // Check that the attach message isn't sent checkIfAttachSent := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -393,15 +386,12 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channel := client.Channels.Get("test") channelStateChanges := make(ably.ChannelStateChanges, 10) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - channeloff := channel.OnAll(channelStateChanges.Receive) defer channeloff() var channelStatechange ably.ChannelStateChange - err := channel.Attach(ctx) + err := channel.Attach(canceledCtx) assert.NoError(t, err) // Check that the attach message is sent @@ -687,12 +677,9 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { assert.Equal(t, ably.ConnectionStateConnected, c.Realtime.Connection.State(), "expected %v; got %v", ably.ConnectionStateConnected, c.Realtime.Connection.State()) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - recorder.Reset() //reset the recorded messages to zero - err = channel.Attach(ctx) + err = channel.Attach(canceledCtx) assert.NoError(t, err) // Check that the attach message is sent @@ -884,10 +871,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - channel.Attach(ctx) + channel.Attach(canceledCtx) // Check that the attach message isn't sent checkIfAttachSentFn := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -945,10 +929,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - channel.Attach(ctx) + channel.Attach(canceledCtx) // Check that the attach message isn't sent checkIfAttachSentFn := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -988,9 +969,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4j RTL13a: If channel attach is not a clean attach, should set ATTACH_RESUME in the ATTACH message", func(t *testing.T) { in, out, _, channel, stateChanges, _ := setup(t) - cancelledCtx, cancel := context.WithCancel(context.Background()) - cancel() - channel.Attach(cancelledCtx) + channel.Attach(canceledCtx) ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume ATTACHING channel.OnAll(stateChanges.Receive) @@ -1173,10 +1152,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { ably.ChannelWithParams("test2", "blahblah"), ably.ChannelWithParams("delta", "vcdiff")) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := channel.Attach(ctx) + err := channel.Attach(canceledCtx) assert.NoError(t, err) attachMessage := recorder.FindFirst(ably.ActionAttach) @@ -1205,13 +1181,10 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { ably.ChannelWithParams("test2", "blahblah"), ably.ChannelWithParams("delta", "vcdiff")) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - err := channel.Attach(ctx) + err := channel.Attach(canceledCtx) assert.NoError(t, err) ablytest.Soon.Recv(t, nil, channelStateChanges, t.Fatalf) // CONSUME ATTACHING @@ -1243,10 +1216,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channel := client.Channels.Get("test", ably.ChannelWithModes(channelModes...)) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := channel.Attach(ctx) + err := channel.Attach(canceledCtx) assert.NoError(t, err) attachMessage := recorder.FindFirst(ably.ActionAttach) @@ -1269,13 +1239,10 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channel := client.Channels.Get("test", ably.ChannelWithModes(channelModes...)) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - err := channel.Attach(ctx) + err := channel.Attach(canceledCtx) assert.NoError(t, err) ablytest.Soon.Recv(t, nil, channelStateChanges, t.Fatalf) // CONSUME ATTACHING @@ -1349,8 +1316,6 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { channel := channelTransitioner.Channel channelStateChanges := make(ably.ChannelStateChanges, 10) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() channel.OnAll(channelStateChanges.Receive) @@ -1358,7 +1323,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.Equal(t, ably.ChannelStateInitialized, channel.State(), "expected %v; got %v", ably.ChannelStateInitialized, channel.State()) - err = channel.Detach(ctx) + err = channel.Detach(canceledCtx) assert.NoError(t, err) // Check that the detach message isn't sent @@ -1439,9 +1404,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.Equal(t, ably.ChannelStateFailed, channelStatechange.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateFailed, channelStatechange.Current, channelStatechange) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - err = channel.Detach(ctx) + err = channel.Detach(canceledCtx) // Check that the detach message isn't sent checkIfDetachSent := recorder.CheckIfSent(ably.ActionDetach, 1) @@ -1483,15 +1446,12 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { channel := client.Channels.Get("test") channelStateChanges := make(ably.ChannelStateChanges, 10) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - channeloff := channel.OnAll(channelStateChanges.Receive) defer channeloff() var channelStatechange ably.ChannelStateChange - err := channel.Attach(ctx) + err := channel.Attach(canceledCtx) assert.NoError(t, err) ablytest.Instantly.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf) @@ -1665,10 +1625,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { closing, ) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err = channel.Detach(ctx) + err = channel.Detach(canceledCtx) // Check that the detach message isn't sent checkIfDetachSent := recorder.CheckIfSent(ably.ActionDetach, 1) @@ -1731,10 +1688,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { connecting, ) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - channel.Detach(ctx) + channel.Detach(canceledCtx) // Check that the detach message isn't sent checkIfDetachSent := recorder.CheckIfSent(ably.ActionDetach, 1) @@ -1800,10 +1754,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { disconnected, ) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - channel.Detach(ctx) + channel.Detach(canceledCtx) // Check that the detach message isn't sent checkIfDetachSent := recorder.CheckIfSent(ably.ActionDetach, 1) @@ -1904,14 +1855,13 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { t.Run("RTL5j: if channel state is SUSPENDED, immediately transition to DETACHED state", func(t *testing.T) { t.Skip("Channel SUSPENDED not implemented yet") _, _, _, channel, stateChanges, _ := setup(t) - ctx, cancel := context.WithCancel(context.Background()) - cancel() + channel.OnAll(stateChanges.Receive) //channel.SetState(ably.ChannelStateSuspended, nil) ablytest.Instantly.Recv(t, nil, stateChanges, t.Fatalf) // State will be changed to suspended - channel.Detach(ctx) + channel.Detach(canceledCtx) var change ably.ChannelStateChange ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) @@ -1928,10 +1878,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { var outMsg *ably.ProtocolMessage var change ably.ChannelStateChange - cancelledContext, cancel := context.WithCancel(context.Background()) - cancel() - - channel.Attach(cancelledContext) + channel.Attach(canceledCtx) // get channel state to attaching ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) @@ -2009,9 +1956,6 @@ func TestRealtimeChannel_RTL6c1_PublishNow(t *testing.T) { channel, closer := chanTransitioner.To(transition...) defer safeclose(t, closer) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - // Make a second client to subscribe and check that messages are // published without interferring with the first client's state. @@ -2028,7 +1972,7 @@ func TestRealtimeChannel_RTL6c1_PublishNow(t *testing.T) { t.Fatal(err) } - err = channel.Publish(ctx, "test", nil) + err = channel.Publish(canceledCtx, "test", nil) if err != nil && !errors.Is(err, context.Canceled) { t.Fatal(err) } @@ -2132,10 +2076,7 @@ func TestRealtimeChannel_RTL6c2_PublishEnqueue(t *testing.T) { closer = c.To(trans.connAfter...) defer safeclose(t, closer) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - err = channel.Publish(ctx, "test", nil) + err = channel.Publish(canceledCtx, "test", nil) if err != nil && !errors.Is(err, context.Canceled) { t.Fatal(err) } @@ -2302,9 +2243,7 @@ func TestRealtimeChannel_RTL2f_RTL12_HandleResume(t *testing.T) { channel = c.Channels.Get("test") - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go channel.Attach(ctx) + go channel.Attach(canceledCtx) ablytest.Instantly.Recv(t, nil, afterCalls, t.Fatalf) // Consume expiry timer for attach ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume ATTACHING @@ -2430,9 +2369,7 @@ func TestRealtimeChannel_RTL13_HandleDetached(t *testing.T) { channel = c.Channels.Get("test") - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go channel.Attach(ctx) + go channel.Attach(canceledCtx) ablytest.Soon.Recv(t, nil, afterCalls, t.Fatalf) // consume TIMER ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume ATTACHING @@ -2658,14 +2595,11 @@ func TestRealtimeChannel_RTL17_IgnoreMessagesWhenNotAttached(t *testing.T) { stateChanges = make(ably.ChannelStateChanges, 10) channel.OnAll(stateChanges.Receive) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - channel.SubscribeAll(ctx, func(message *ably.Message) { + channel.SubscribeAll(canceledCtx, func(message *ably.Message) { msg <- message }) - channel.Attach(ctx) + channel.Attach(canceledCtx) return } @@ -2722,9 +2656,7 @@ func TestRealtimeChannel_RTL17_IgnoreMessagesWhenNotAttached(t *testing.T) { receiveMessage() ablytest.Instantly.Recv(t, nil, msg, t.Fatalf) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - channel.Detach(ctx) + channel.Detach(canceledCtx) // Get the channel to DETACHED. ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume DETACHING From da19b34e3d25427534b3ab2485b29588716f24b2 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 03:04:02 +0530 Subject: [PATCH 115/178] Added missing cancelledCtx to test file --- ably/realtime_channel_spec_integration_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ably/realtime_channel_spec_integration_test.go b/ably/realtime_channel_spec_integration_test.go index dc1b4b397..0bd642a9a 100644 --- a/ably/realtime_channel_spec_integration_test.go +++ b/ably/realtime_channel_spec_integration_test.go @@ -245,7 +245,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { defer safeclose(t, closer) // connection is closing - err = channel.Attach(ctx) + err = channel.Attach(canceledCtx) // Check that the attach message isn't sent attachSent = ablytest.Instantly.IsTrue(checkIfAttachSent) @@ -263,7 +263,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { defer safeclose(t, closer) // connection is closed - err = channel.Attach(ctx) + err = channel.Attach(canceledCtx) // Check that the attach message isn't sent attachSent = ablytest.Instantly.IsTrue(checkIfAttachSent) @@ -1359,7 +1359,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.Equal(t, ably.ChannelStateDetached, channelStatechange.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateDetached, channelStatechange.Current, channelStatechange) - err = channel.Detach(ctx) + err = channel.Detach(canceledCtx) assert.NoError(t, err) // Check that the detach message isn't sent @@ -1462,7 +1462,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.Equal(t, ably.ChannelStateAttached, channelStatechange.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, channelStatechange.Current, channelStatechange) - err = channel.Detach(ctx) + err = channel.Detach(canceledCtx) assert.NoError(t, err) // Check that the detach message sent @@ -1641,7 +1641,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { defer safeclose(t, closer) - err = channel.Detach(ctx) + err = channel.Detach(canceledCtx) // Check that the detach message isn't sent detachSent = ablytest.Instantly.IsTrue(checkIfDetachSent) @@ -1899,7 +1899,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.Equal(t, ably.ChannelStateAttached, change.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, change.Current, change) - channel.Detach(cancelledContext) + channel.Detach(canceledCtx) ablytest.Instantly.Recv(t, &outMsg, out, t.Fatalf) assert.Equal(t, ably.ActionDetach, outMsg.Action, From 4137ff5bd0a438ba98d556bb46996e9bf765b9a5 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 03:09:43 +0530 Subject: [PATCH 116/178] refactored state file, removed unnecessary msgCh struct --- ably/state.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/ably/state.go b/ably/state.go index a2083a7b0..a0e922274 100644 --- a/ably/state.go +++ b/ably/state.go @@ -106,7 +106,7 @@ func channelStateError(state ChannelState, err error) *ErrorInfo { // pendingEmitter emits confirmation events triggered by ACK or NACK messages. type pendingEmitter struct { - queue []msgCh + queue []msgWithAckCallback log logger } @@ -116,15 +116,10 @@ func newPendingEmitter(log logger) pendingEmitter { } } -type msgCh struct { - msg *protocolMessage - onAck func(err error) -} - // Dismiss lets go of the channels that are waiting for an error on this queue. // The queue can continue sending messages. -func (q *pendingEmitter) Dismiss() []msgCh { - cx := make([]msgCh, len(q.queue)) +func (q *pendingEmitter) Dismiss() []msgWithAckCallback { + cx := make([]msgWithAckCallback, len(q.queue)) copy(cx, q.queue) q.queue = nil return cx @@ -137,7 +132,7 @@ func (q *pendingEmitter) Enqueue(msg *protocolMessage, onAck func(err error)) { panic(fmt.Sprintf("protocol violation: expected next enqueued message to have msgSerial %d; got %d", expected, got)) } } - q.queue = append(q.queue, msgCh{msg, onAck}) + q.queue = append(q.queue, msgWithAckCallback{msg, onAck}) } func (q *pendingEmitter) Ack(msg *protocolMessage, errInfo *ErrorInfo) { @@ -191,14 +186,14 @@ func (q *pendingEmitter) Ack(msg *protocolMessage, errInfo *ErrorInfo) { } } -type msgWithAck struct { +type msgWithAckCallback struct { msg *protocolMessage onAck func(err error) } type msgQueue struct { mtx sync.Mutex - queue []msgWithAck + queue []msgWithAckCallback conn *Connection } @@ -211,7 +206,7 @@ func newMsgQueue(conn *Connection) *msgQueue { func (q *msgQueue) Enqueue(msg *protocolMessage, onAck func(err error)) { q.mtx.Lock() // TODO(rjeczalik): reorder the queue so Presence / Messages can be merged - q.queue = append(q.queue, msgWithAck{msg, onAck}) + q.queue = append(q.queue, msgWithAckCallback{msg, onAck}) q.mtx.Unlock() } From 8d6e646d927cac7a40abd771f6c6ab1fa41d52ab Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 03:19:40 +0530 Subject: [PATCH 117/178] Revert "Added missing cancelledCtx to test file" This reverts commit da19b34e3d25427534b3ab2485b29588716f24b2. --- ably/realtime_channel_spec_integration_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ably/realtime_channel_spec_integration_test.go b/ably/realtime_channel_spec_integration_test.go index 0bd642a9a..dc1b4b397 100644 --- a/ably/realtime_channel_spec_integration_test.go +++ b/ably/realtime_channel_spec_integration_test.go @@ -245,7 +245,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { defer safeclose(t, closer) // connection is closing - err = channel.Attach(canceledCtx) + err = channel.Attach(ctx) // Check that the attach message isn't sent attachSent = ablytest.Instantly.IsTrue(checkIfAttachSent) @@ -263,7 +263,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { defer safeclose(t, closer) // connection is closed - err = channel.Attach(canceledCtx) + err = channel.Attach(ctx) // Check that the attach message isn't sent attachSent = ablytest.Instantly.IsTrue(checkIfAttachSent) @@ -1359,7 +1359,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.Equal(t, ably.ChannelStateDetached, channelStatechange.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateDetached, channelStatechange.Current, channelStatechange) - err = channel.Detach(canceledCtx) + err = channel.Detach(ctx) assert.NoError(t, err) // Check that the detach message isn't sent @@ -1462,7 +1462,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.Equal(t, ably.ChannelStateAttached, channelStatechange.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, channelStatechange.Current, channelStatechange) - err = channel.Detach(canceledCtx) + err = channel.Detach(ctx) assert.NoError(t, err) // Check that the detach message sent @@ -1641,7 +1641,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { defer safeclose(t, closer) - err = channel.Detach(canceledCtx) + err = channel.Detach(ctx) // Check that the detach message isn't sent detachSent = ablytest.Instantly.IsTrue(checkIfDetachSent) @@ -1899,7 +1899,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.Equal(t, ably.ChannelStateAttached, change.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, change.Current, change) - channel.Detach(canceledCtx) + channel.Detach(cancelledContext) ablytest.Instantly.Recv(t, &outMsg, out, t.Fatalf) assert.Equal(t, ably.ActionDetach, outMsg.Action, From 4875df9a7111ec80bdcb36e2bd1facb4e8ccfd8c Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 03:19:50 +0530 Subject: [PATCH 118/178] Revert "refactored realtime channel spec integration for cancelled context" This reverts commit 515a6541ea3326d442584ef43009fb179c108699. --- .../realtime_channel_spec_integration_test.go | 124 ++++++++++++++---- 1 file changed, 96 insertions(+), 28 deletions(-) diff --git a/ably/realtime_channel_spec_integration_test.go b/ably/realtime_channel_spec_integration_test.go index dc1b4b397..af6352826 100644 --- a/ably/realtime_channel_spec_integration_test.go +++ b/ably/realtime_channel_spec_integration_test.go @@ -223,8 +223,11 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + // connection is initialized - err = channel.Attach(canceledCtx) + err = channel.Attach(ctx) // Check that the attach message isn't sent checkIfAttachSent := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -301,8 +304,10 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() // connection is failed - err = channel.Attach(canceledCtx) + err = channel.Attach(ctx) // Check that the attach message isn't sent checkIfAttachSent := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -341,8 +346,10 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() // connection state is suspended - err = channel.Attach(canceledCtx) + err = channel.Attach(ctx) // Check that the attach message isn't sent checkIfAttachSent := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -386,12 +393,15 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channel := client.Channels.Get("test") channelStateChanges := make(ably.ChannelStateChanges, 10) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + channeloff := channel.OnAll(channelStateChanges.Receive) defer channeloff() var channelStatechange ably.ChannelStateChange - err := channel.Attach(canceledCtx) + err := channel.Attach(ctx) assert.NoError(t, err) // Check that the attach message is sent @@ -677,9 +687,12 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { assert.Equal(t, ably.ConnectionStateConnected, c.Realtime.Connection.State(), "expected %v; got %v", ably.ConnectionStateConnected, c.Realtime.Connection.State()) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + recorder.Reset() //reset the recorded messages to zero - err = channel.Attach(canceledCtx) + err = channel.Attach(ctx) assert.NoError(t, err) // Check that the attach message is sent @@ -871,7 +884,10 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - channel.Attach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + channel.Attach(ctx) // Check that the attach message isn't sent checkIfAttachSentFn := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -929,7 +945,10 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - channel.Attach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + channel.Attach(ctx) // Check that the attach message isn't sent checkIfAttachSentFn := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -969,7 +988,9 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4j RTL13a: If channel attach is not a clean attach, should set ATTACH_RESUME in the ATTACH message", func(t *testing.T) { in, out, _, channel, stateChanges, _ := setup(t) - channel.Attach(canceledCtx) + cancelledCtx, cancel := context.WithCancel(context.Background()) + cancel() + channel.Attach(cancelledCtx) ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume ATTACHING channel.OnAll(stateChanges.Receive) @@ -1152,7 +1173,10 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { ably.ChannelWithParams("test2", "blahblah"), ably.ChannelWithParams("delta", "vcdiff")) - err := channel.Attach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := channel.Attach(ctx) assert.NoError(t, err) attachMessage := recorder.FindFirst(ably.ActionAttach) @@ -1181,10 +1205,13 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { ably.ChannelWithParams("test2", "blahblah"), ably.ChannelWithParams("delta", "vcdiff")) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - err := channel.Attach(canceledCtx) + err := channel.Attach(ctx) assert.NoError(t, err) ablytest.Soon.Recv(t, nil, channelStateChanges, t.Fatalf) // CONSUME ATTACHING @@ -1216,7 +1243,10 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channel := client.Channels.Get("test", ably.ChannelWithModes(channelModes...)) - err := channel.Attach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := channel.Attach(ctx) assert.NoError(t, err) attachMessage := recorder.FindFirst(ably.ActionAttach) @@ -1239,10 +1269,13 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channel := client.Channels.Get("test", ably.ChannelWithModes(channelModes...)) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - err := channel.Attach(canceledCtx) + err := channel.Attach(ctx) assert.NoError(t, err) ablytest.Soon.Recv(t, nil, channelStateChanges, t.Fatalf) // CONSUME ATTACHING @@ -1316,6 +1349,8 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { channel := channelTransitioner.Channel channelStateChanges := make(ably.ChannelStateChanges, 10) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() channel.OnAll(channelStateChanges.Receive) @@ -1323,7 +1358,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.Equal(t, ably.ChannelStateInitialized, channel.State(), "expected %v; got %v", ably.ChannelStateInitialized, channel.State()) - err = channel.Detach(canceledCtx) + err = channel.Detach(ctx) assert.NoError(t, err) // Check that the detach message isn't sent @@ -1404,7 +1439,9 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.Equal(t, ably.ChannelStateFailed, channelStatechange.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateFailed, channelStatechange.Current, channelStatechange) - err = channel.Detach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + err = channel.Detach(ctx) // Check that the detach message isn't sent checkIfDetachSent := recorder.CheckIfSent(ably.ActionDetach, 1) @@ -1446,12 +1483,15 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { channel := client.Channels.Get("test") channelStateChanges := make(ably.ChannelStateChanges, 10) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + channeloff := channel.OnAll(channelStateChanges.Receive) defer channeloff() var channelStatechange ably.ChannelStateChange - err := channel.Attach(canceledCtx) + err := channel.Attach(ctx) assert.NoError(t, err) ablytest.Instantly.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf) @@ -1625,7 +1665,10 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { closing, ) - err = channel.Detach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err = channel.Detach(ctx) // Check that the detach message isn't sent checkIfDetachSent := recorder.CheckIfSent(ably.ActionDetach, 1) @@ -1688,7 +1731,10 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { connecting, ) - channel.Detach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + channel.Detach(ctx) // Check that the detach message isn't sent checkIfDetachSent := recorder.CheckIfSent(ably.ActionDetach, 1) @@ -1754,7 +1800,10 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { disconnected, ) - channel.Detach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + channel.Detach(ctx) // Check that the detach message isn't sent checkIfDetachSent := recorder.CheckIfSent(ably.ActionDetach, 1) @@ -1855,13 +1904,14 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { t.Run("RTL5j: if channel state is SUSPENDED, immediately transition to DETACHED state", func(t *testing.T) { t.Skip("Channel SUSPENDED not implemented yet") _, _, _, channel, stateChanges, _ := setup(t) - + ctx, cancel := context.WithCancel(context.Background()) + cancel() channel.OnAll(stateChanges.Receive) //channel.SetState(ably.ChannelStateSuspended, nil) ablytest.Instantly.Recv(t, nil, stateChanges, t.Fatalf) // State will be changed to suspended - channel.Detach(canceledCtx) + channel.Detach(ctx) var change ably.ChannelStateChange ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) @@ -1878,7 +1928,10 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { var outMsg *ably.ProtocolMessage var change ably.ChannelStateChange - channel.Attach(canceledCtx) + cancelledContext, cancel := context.WithCancel(context.Background()) + cancel() + + channel.Attach(cancelledContext) // get channel state to attaching ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) @@ -1956,6 +2009,9 @@ func TestRealtimeChannel_RTL6c1_PublishNow(t *testing.T) { channel, closer := chanTransitioner.To(transition...) defer safeclose(t, closer) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + // Make a second client to subscribe and check that messages are // published without interferring with the first client's state. @@ -1972,7 +2028,7 @@ func TestRealtimeChannel_RTL6c1_PublishNow(t *testing.T) { t.Fatal(err) } - err = channel.Publish(canceledCtx, "test", nil) + err = channel.Publish(ctx, "test", nil) if err != nil && !errors.Is(err, context.Canceled) { t.Fatal(err) } @@ -2076,7 +2132,10 @@ func TestRealtimeChannel_RTL6c2_PublishEnqueue(t *testing.T) { closer = c.To(trans.connAfter...) defer safeclose(t, closer) - err = channel.Publish(canceledCtx, "test", nil) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + err = channel.Publish(ctx, "test", nil) if err != nil && !errors.Is(err, context.Canceled) { t.Fatal(err) } @@ -2243,7 +2302,9 @@ func TestRealtimeChannel_RTL2f_RTL12_HandleResume(t *testing.T) { channel = c.Channels.Get("test") - go channel.Attach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go channel.Attach(ctx) ablytest.Instantly.Recv(t, nil, afterCalls, t.Fatalf) // Consume expiry timer for attach ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume ATTACHING @@ -2369,7 +2430,9 @@ func TestRealtimeChannel_RTL13_HandleDetached(t *testing.T) { channel = c.Channels.Get("test") - go channel.Attach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go channel.Attach(ctx) ablytest.Soon.Recv(t, nil, afterCalls, t.Fatalf) // consume TIMER ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume ATTACHING @@ -2595,11 +2658,14 @@ func TestRealtimeChannel_RTL17_IgnoreMessagesWhenNotAttached(t *testing.T) { stateChanges = make(ably.ChannelStateChanges, 10) channel.OnAll(stateChanges.Receive) - channel.SubscribeAll(canceledCtx, func(message *ably.Message) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + channel.SubscribeAll(ctx, func(message *ably.Message) { msg <- message }) - channel.Attach(canceledCtx) + channel.Attach(ctx) return } @@ -2656,7 +2722,9 @@ func TestRealtimeChannel_RTL17_IgnoreMessagesWhenNotAttached(t *testing.T) { receiveMessage() ablytest.Instantly.Recv(t, nil, msg, t.Fatalf) - channel.Detach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + channel.Detach(ctx) // Get the channel to DETACHED. ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume DETACHING From 3f390b75bfa55cc714bc9801acf64cb39725f21b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 03:23:20 +0530 Subject: [PATCH 119/178] Refactored cancelledContext for channel integration tests --- .../realtime_channel_spec_integration_test.go | 57 +++++-------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/ably/realtime_channel_spec_integration_test.go b/ably/realtime_channel_spec_integration_test.go index af6352826..242b94ee7 100644 --- a/ably/realtime_channel_spec_integration_test.go +++ b/ably/realtime_channel_spec_integration_test.go @@ -884,10 +884,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - channel.Attach(ctx) + channel.Attach(canceledCtx) // Check that the attach message isn't sent checkIfAttachSentFn := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -945,10 +942,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { channelStateChanges := make(ably.ChannelStateChanges, 10) channel.OnAll(channelStateChanges.Receive) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - channel.Attach(ctx) + channel.Attach(canceledCtx) // Check that the attach message isn't sent checkIfAttachSentFn := recorder.CheckIfSent(ably.ActionAttach, 1) @@ -988,9 +982,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4j RTL13a: If channel attach is not a clean attach, should set ATTACH_RESUME in the ATTACH message", func(t *testing.T) { in, out, _, channel, stateChanges, _ := setup(t) - cancelledCtx, cancel := context.WithCancel(context.Background()) - cancel() - channel.Attach(cancelledCtx) + channel.Attach(canceledCtx) ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume ATTACHING channel.OnAll(stateChanges.Receive) @@ -1731,10 +1723,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { connecting, ) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - channel.Detach(ctx) + channel.Detach(canceledCtx) // Check that the detach message isn't sent checkIfDetachSent := recorder.CheckIfSent(ably.ActionDetach, 1) @@ -1800,10 +1789,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { disconnected, ) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - channel.Detach(ctx) + channel.Detach(canceledCtx) // Check that the detach message isn't sent checkIfDetachSent := recorder.CheckIfSent(ably.ActionDetach, 1) @@ -1904,14 +1890,13 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { t.Run("RTL5j: if channel state is SUSPENDED, immediately transition to DETACHED state", func(t *testing.T) { t.Skip("Channel SUSPENDED not implemented yet") _, _, _, channel, stateChanges, _ := setup(t) - ctx, cancel := context.WithCancel(context.Background()) - cancel() + channel.OnAll(stateChanges.Receive) //channel.SetState(ably.ChannelStateSuspended, nil) ablytest.Instantly.Recv(t, nil, stateChanges, t.Fatalf) // State will be changed to suspended - channel.Detach(ctx) + channel.Detach(canceledCtx) var change ably.ChannelStateChange ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) @@ -1928,10 +1913,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { var outMsg *ably.ProtocolMessage var change ably.ChannelStateChange - cancelledContext, cancel := context.WithCancel(context.Background()) - cancel() - - channel.Attach(cancelledContext) + channel.Attach(canceledCtx) // get channel state to attaching ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) @@ -1952,7 +1934,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.Equal(t, ably.ChannelStateAttached, change.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, change.Current, change) - channel.Detach(cancelledContext) + channel.Detach(canceledCtx) ablytest.Instantly.Recv(t, &outMsg, out, t.Fatalf) assert.Equal(t, ably.ActionDetach, outMsg.Action, @@ -2009,9 +1991,6 @@ func TestRealtimeChannel_RTL6c1_PublishNow(t *testing.T) { channel, closer := chanTransitioner.To(transition...) defer safeclose(t, closer) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - // Make a second client to subscribe and check that messages are // published without interferring with the first client's state. @@ -2028,7 +2007,7 @@ func TestRealtimeChannel_RTL6c1_PublishNow(t *testing.T) { t.Fatal(err) } - err = channel.Publish(ctx, "test", nil) + err = channel.Publish(canceledCtx, "test", nil) if err != nil && !errors.Is(err, context.Canceled) { t.Fatal(err) } @@ -2132,10 +2111,7 @@ func TestRealtimeChannel_RTL6c2_PublishEnqueue(t *testing.T) { closer = c.To(trans.connAfter...) defer safeclose(t, closer) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - err = channel.Publish(ctx, "test", nil) + err = channel.Publish(canceledCtx, "test", nil) if err != nil && !errors.Is(err, context.Canceled) { t.Fatal(err) } @@ -2658,14 +2634,11 @@ func TestRealtimeChannel_RTL17_IgnoreMessagesWhenNotAttached(t *testing.T) { stateChanges = make(ably.ChannelStateChanges, 10) channel.OnAll(stateChanges.Receive) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - channel.SubscribeAll(ctx, func(message *ably.Message) { + channel.SubscribeAll(canceledCtx, func(message *ably.Message) { msg <- message }) - channel.Attach(ctx) + channel.Attach(canceledCtx) return } @@ -2722,9 +2695,7 @@ func TestRealtimeChannel_RTL17_IgnoreMessagesWhenNotAttached(t *testing.T) { receiveMessage() ablytest.Instantly.Recv(t, nil, msg, t.Fatalf) - ctx, cancel := context.WithCancel(context.Background()) - cancel() - channel.Detach(ctx) + channel.Detach(canceledCtx) // Get the channel to DETACHED. ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume DETACHING From 0550ecc7cd557a41d4bce7c444d3c054f8e2c81d Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 03:51:13 +0530 Subject: [PATCH 120/178] Fixed failing test for realtime chan spec integration --- .../realtime_channel_spec_integration_test.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ably/realtime_channel_spec_integration_test.go b/ably/realtime_channel_spec_integration_test.go index 242b94ee7..2211653f2 100644 --- a/ably/realtime_channel_spec_integration_test.go +++ b/ably/realtime_channel_spec_integration_test.go @@ -1748,12 +1748,16 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { assert.True(t, detachSent, "Detach message was not sent") + ablytest.Instantly.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateDetaching, channelStatechange.Current, + "expected %v; got %v (event: %+v)", ably.ChannelStateDetaching, channelStatechange.Current, channelStatechange) + ablytest.Soon.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf) assert.Equal(t, ably.ChannelStateDetached, channelStatechange.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateDetached, channelStatechange.Current, channelStatechange) }) - t.Run("RTL5h : If Connection state DISCONNECTED, queue the DETACH message and send on CONNECTED", func(t *testing.T) { + t.Run("RTL5h, RTN19b: If Connection state DISCONNECTED, queue the DETACH message and send on CONNECTED", func(t *testing.T) { app, err := ablytest.NewSandbox(nil) assert.NoError(t, err) @@ -1792,8 +1796,8 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { channel.Detach(canceledCtx) // Check that the detach message isn't sent - checkIfDetachSent := recorder.CheckIfSent(ably.ActionDetach, 1) - detachSent := ablytest.Instantly.IsTrue(checkIfDetachSent) + checkIfDetachSentFn := recorder.CheckIfSent(ably.ActionDetach, 1) + detachSent := ablytest.Instantly.IsTrue(checkIfDetachSentFn) assert.False(t, detachSent, "Detach message was sent before connection is established") @@ -1811,9 +1815,12 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { defer safeclose(t, closer) // Check that the detach message sent - detachSent = ablytest.Instantly.IsTrue(checkIfDetachSent) - assert.True(t, detachSent, - "Detach message was not sent") + detachSent = ablytest.Instantly.IsTrue(checkIfDetachSentFn) + assert.True(t, detachSent, "Detach message was not sent") + + ablytest.Instantly.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateDetaching, channelStatechange.Current, + "expected %v; got %v (event: %+v)", ably.ChannelStateDetaching, channelStatechange.Current, channelStatechange) ablytest.Soon.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf) assert.Equal(t, ably.ChannelStateDetached, channelStatechange.Current, From 0d8f475d86dfdef1f54edc672904064a4f72f06a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 11:54:46 +0530 Subject: [PATCH 121/178] Added lock safety for setting and retriving the channel serial --- ably/realtime_channel.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 4ad8fcd2c..2023636c9 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -53,7 +53,7 @@ func (channels *RealtimeChannels) SetChannelSerialsFromRecoverOption(serials map defer channels.mtx.Unlock() for channelName, channelSerial := range serials { channel := channels.Get(channelName) - channel.properties.ChannelSerial = channelSerial + channel.setChannelSerial(channelSerial) } } @@ -354,7 +354,7 @@ func (c *RealtimeChannel) lockAttach(err error) (result, error) { Action: actionAttach, Channel: c.Name, } - msg.ChannelSerial = c.properties.ChannelSerial // RTL4c1 + msg.ChannelSerial = c.getChannelSerial() // RTL4c1 if len(c.channelOpts().Params) > 0 { msg.Params = c.channelOpts().Params } @@ -790,7 +790,7 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { msg.Action == actionPresence || msg.Action == actionAttached { c.log().Debugf("Setting channel serial for channelName - %v, previous - %v, current - %v", c.Name, c.properties.ChannelSerial, msg.ChannelSerial) - c.properties.ChannelSerial = msg.ChannelSerial + c.setChannelSerial(msg.ChannelSerial) } switch msg.Action { @@ -925,6 +925,18 @@ func (c *RealtimeChannel) setParams(params channelParams) { c.params = params } +func (c *RealtimeChannel) setChannelSerial(serial string) { + c.mtx.Lock() + defer c.mtx.Unlock() + c.properties.ChannelSerial = serial +} + +func (c *RealtimeChannel) getChannelSerial() string { + c.mtx.Lock() + defer c.mtx.Unlock() + return c.properties.ChannelSerial +} + func (c *RealtimeChannel) setModes(modes []ChannelMode) { c.mtx.Lock() defer c.mtx.Unlock() @@ -967,7 +979,7 @@ func (c *RealtimeChannel) setState(state ChannelState, err error, resumed bool) } // RTP5a1 if state == ChannelStateDetached || state == ChannelStateSuspended || state == ChannelStateFailed { - c.properties.ChannelSerial = "" + c.setChannelSerial("") } // RTP5f if state == ChannelStateSuspended { From 5bf488ce311218213cbcd9d1ec669a4915a932cd Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 12:06:59 +0530 Subject: [PATCH 122/178] Added mtx lock for clientOptions recover --- ably/options.go | 14 ++++++++++++++ ably/realtime_client.go | 4 ++-- ably/realtime_conn.go | 8 ++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/ably/options.go b/ably/options.go index 98f92e472..59fc92e03 100644 --- a/ably/options.go +++ b/ably/options.go @@ -12,6 +12,7 @@ import ( "os" "strconv" "strings" + "sync" "time" "github.com/ably/ably-go/ably/internal/ablyutil" @@ -235,6 +236,7 @@ func (opts *authOptions) KeySecret() string { // clientOptions passes additional client-specific properties to the [ably.NewREST] or to the [ably.NewRealtime]. // Properties set using [ably.clientOptions] are used instead of the [ably.defaultOptions] values. type clientOptions struct { + mtx *sync.Mutex // authOptions Embedded an [ably.authOptions] object (TO3j). authOptions @@ -1156,6 +1158,18 @@ func WithRecover(key string) ClientOption { } } +func (opts *clientOptions) SetRecover(recover string) { + opts.mtx.Lock() + defer opts.mtx.Unlock() + opts.Recover = recover +} + +func (opts *clientOptions) GetRecover() string { + opts.mtx.Lock() + defer opts.mtx.Unlock() + return opts.Recover +} + // WithTLS is used for setting NoTLS using [ably.ClientOption]. // NoTLS when set to true, the client will use an insecure connection. // The default is false, meaning a TLS connection will be used to connect to Ably (RSC18, TO3d). diff --git a/ably/realtime_client.go b/ably/realtime_client.go index a4428b2da..7402576cc 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -39,8 +39,8 @@ func NewRealtime(options ...ClientOption) (*Realtime, error) { c.Connection = conn // RTN16 - if !empty(c.opts().Recover) { - recoverKeyContext, err := DecodeRecoveryKey(c.opts().Recover) + if !empty(c.opts().GetRecover()) { + recoverKeyContext, err := DecodeRecoveryKey(c.opts().GetRecover()) if err != nil { c.log().Errorf("Error decoding recover with error %v", err) } else { diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index e73b86727..5620af7e3 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -249,7 +249,7 @@ func (c *Connection) getMode() connectionMode { if c.key != "" { return resumeMode } - if c.opts.Recover != "" { + if c.opts.GetRecover() != "" { return recoveryMode } return normalMode @@ -285,7 +285,7 @@ func (c *Connection) params(mode connectionMode) (url.Values, error) { case resumeMode: query.Set("resume", c.key) // RTN15b case recoveryMode: - recoveryKeyContext, err := DecodeRecoveryKey(c.opts.Recover) + recoveryKeyContext, err := DecodeRecoveryKey(c.opts.GetRecover()) if err != nil { c.log().Errorf("error decoding recovery key, %v", err) } @@ -809,8 +809,8 @@ func (c *Connection) eventloop() { c.mtx.Lock() // recover is used when set via clientOptions#recover initially, resume will be used for all reconnects. - isConnectionResumeOrRecoverAttempt := !empty(c.key) || !empty(c.opts.Recover) - c.opts.Recover = "" // RTN16k, explicitly setting null so it won't be used for subsequent connection requests + isConnectionResumeOrRecoverAttempt := !empty(c.key) || !empty(c.opts.GetRecover()) + c.opts.SetRecover("") // RTN16k, explicitly setting null so it won't be used for subsequent connection requests // we need to get this before we set c.key so as to be sure if we were // resuming or recovering the connection. From eb696f55c4a44894f9b08d031931133130e4f130 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 12:21:06 +0530 Subject: [PATCH 123/178] reverted mtx change for recover clientOptions, added explicit field to connection --- ably/options.go | 15 --------------- ably/realtime_client.go | 4 ++-- ably/realtime_conn.go | 10 ++++++---- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/ably/options.go b/ably/options.go index 59fc92e03..5e22a3774 100644 --- a/ably/options.go +++ b/ably/options.go @@ -12,7 +12,6 @@ import ( "os" "strconv" "strings" - "sync" "time" "github.com/ably/ably-go/ably/internal/ablyutil" @@ -236,8 +235,6 @@ func (opts *authOptions) KeySecret() string { // clientOptions passes additional client-specific properties to the [ably.NewREST] or to the [ably.NewRealtime]. // Properties set using [ably.clientOptions] are used instead of the [ably.defaultOptions] values. type clientOptions struct { - mtx *sync.Mutex - // authOptions Embedded an [ably.authOptions] object (TO3j). authOptions @@ -1158,18 +1155,6 @@ func WithRecover(key string) ClientOption { } } -func (opts *clientOptions) SetRecover(recover string) { - opts.mtx.Lock() - defer opts.mtx.Unlock() - opts.Recover = recover -} - -func (opts *clientOptions) GetRecover() string { - opts.mtx.Lock() - defer opts.mtx.Unlock() - return opts.Recover -} - // WithTLS is used for setting NoTLS using [ably.ClientOption]. // NoTLS when set to true, the client will use an insecure connection. // The default is false, meaning a TLS connection will be used to connect to Ably (RSC18, TO3d). diff --git a/ably/realtime_client.go b/ably/realtime_client.go index 7402576cc..a4428b2da 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -39,8 +39,8 @@ func NewRealtime(options ...ClientOption) (*Realtime, error) { c.Connection = conn // RTN16 - if !empty(c.opts().GetRecover()) { - recoverKeyContext, err := DecodeRecoveryKey(c.opts().GetRecover()) + if !empty(c.opts().Recover) { + recoverKeyContext, err := DecodeRecoveryKey(c.opts().Recover) if err != nil { c.log().Errorf("Error decoding recover with error %v", err) } else { diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 5620af7e3..219ba8510 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -82,6 +82,7 @@ type Connection struct { readLimit int64 isReadLimitSetExternally bool + recover string } type connCallbacks struct { @@ -108,6 +109,7 @@ func newConn(opts *clientOptions, auth *Auth, callbacks connCallbacks, client *R callbacks: callbacks, client: client, readLimit: maxMessageSize, + recover: opts.Recover, } auth.onExplicitAuthorize = c.onClientAuthorize c.queue = newMsgQueue(c) @@ -249,7 +251,7 @@ func (c *Connection) getMode() connectionMode { if c.key != "" { return resumeMode } - if c.opts.GetRecover() != "" { + if c.recover != "" { return recoveryMode } return normalMode @@ -285,7 +287,7 @@ func (c *Connection) params(mode connectionMode) (url.Values, error) { case resumeMode: query.Set("resume", c.key) // RTN15b case recoveryMode: - recoveryKeyContext, err := DecodeRecoveryKey(c.opts.GetRecover()) + recoveryKeyContext, err := DecodeRecoveryKey(c.recover) if err != nil { c.log().Errorf("error decoding recovery key, %v", err) } @@ -809,8 +811,8 @@ func (c *Connection) eventloop() { c.mtx.Lock() // recover is used when set via clientOptions#recover initially, resume will be used for all reconnects. - isConnectionResumeOrRecoverAttempt := !empty(c.key) || !empty(c.opts.GetRecover()) - c.opts.SetRecover("") // RTN16k, explicitly setting null so it won't be used for subsequent connection requests + isConnectionResumeOrRecoverAttempt := !empty(c.key) || !empty(c.recover) + c.recover = "" // RTN16k, explicitly setting null so it won't be used for subsequent connection requests // we need to get this before we set c.key so as to be sure if we were // resuming or recovering the connection. From 9751892bc732dba3a64aa070a9edf4ff365488ac Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 6 Nov 2023 12:36:17 +0530 Subject: [PATCH 124/178] refactored code to access channelSerial based on locks --- ably/realtime_channel.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 2023636c9..148b601a6 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -62,7 +62,7 @@ func (channels *RealtimeChannels) GetChannelSerials() map[string]string { defer channels.mtx.Unlock() channelSerials := make(map[string]string) for channelName, realtimeChannel := range channels.chans { - channelSerials[channelName] = realtimeChannel.properties.ChannelSerial + channelSerials[channelName] = realtimeChannel.getChannelSerial() } return channelSerials } @@ -354,7 +354,7 @@ func (c *RealtimeChannel) lockAttach(err error) (result, error) { Action: actionAttach, Channel: c.Name, } - msg.ChannelSerial = c.getChannelSerial() // RTL4c1 + msg.ChannelSerial = c.properties.ChannelSerial // RTL4c1, accessing locked if len(c.channelOpts().Params) > 0 { msg.Params = c.channelOpts().Params } @@ -789,7 +789,7 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { if !empty(msg.ChannelSerial) && msg.Action == actionMessage || msg.Action == actionPresence || msg.Action == actionAttached { c.log().Debugf("Setting channel serial for channelName - %v, previous - %v, current - %v", - c.Name, c.properties.ChannelSerial, msg.ChannelSerial) + c.Name, c.getChannelSerial(), msg.ChannelSerial) c.setChannelSerial(msg.ChannelSerial) } @@ -979,7 +979,7 @@ func (c *RealtimeChannel) setState(state ChannelState, err error, resumed bool) } // RTP5a1 if state == ChannelStateDetached || state == ChannelStateSuspended || state == ChannelStateFailed { - c.setChannelSerial("") + c.properties.ChannelSerial = "" // setting on already locked method } // RTP5f if state == ChannelStateSuspended { From 819a05cd2c9db13ad522740885102b8a7911a50b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 7 Nov 2023 01:31:25 +0530 Subject: [PATCH 125/178] Fixed realtime conn spec integration failing test --- ably/realtime_conn_spec_integration_test.go | 41 +++++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 1297da2b8..d69a3beb5 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -1347,11 +1347,11 @@ func TestRealtimeConn_RTN15g_NewConnectionOnStateLost(t *testing.T) { connIDs <- "conn-1" err := ablytest.Wait(ablytest.ConnWaiter(c, c.Connect, ably.ConnectionEventConnected), nil) assert.NoError(t, err) + prevConnectionKey := c.Connection.Key() - ablytest.Instantly.Recv(t, nil, dials, t.Fatalf) // discard first URL; we're interested in reconnections + ablytest.Instantly.Recv(t, nil, dials, t.Fatalf) // Get channels to ATTACHING, ATTACHED and DETACHED. (TODO: SUSPENDED) - attaching := c.Channels.Get("attaching") _ = ablytest.ResultFunc.Go(func(ctx context.Context) error { return attaching.Attach(ctx) }) msg := <-out @@ -1407,30 +1407,39 @@ func TestRealtimeConn_RTN15g_NewConnectionOnStateLost(t *testing.T) { connIDs <- "conn-1" // Same connection ID so the resume "succeeds". var dialed *url.URL ablytest.Instantly.Recv(t, &dialed, dials, t.Fatalf) - resume := dialed.Query().Get("resume") - assert.NotEqual(t, "", resume, "expected a resume key; got %v", resume) + assert.Equal(t, prevConnectionKey, dialed.Query().Get("resume")) + ablytest.Instantly.Recv(t, nil, connected, t.Fatalf) // wait for CONNECTED before disconnecting again + + // RTN15g3: Expect the previously attaching and attached channels to be + // attached again. + attachExpected := map[string]struct{}{ + "attaching": {}, + "attached": {}, + } + for len(attachExpected) > 0 { + var msg *ably.ProtocolMessage + ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) + _, ok := attachExpected[msg.Channel] + assert.True(t, ok, + "ATTACH sent for unexpected or already attaching channel %q", msg.Channel) + delete(attachExpected, msg.Channel) + } + ablytest.Instantly.NoRecv(t, nil, out, t.Fatalf) // Now do the same, but past connectionStateTTL + maxIdleInterval. This // should make a fresh connection. - ablytest.Instantly.Recv(t, nil, connected, t.Fatalf) // wait for CONNECTED before disconnecting again - setNow(now().Add(discardStateTTL + 1)) breakConn() - - connIDs <- "conn-2" + connIDs <- "conn-2" // different connection id, so resume failure ablytest.Instantly.Recv(t, &dialed, dials, t.Fatalf) - resume = dialed.Query().Get("resume") - assert.Equal(t, "", resume, - "didn't expect a resume key; got %v", resume) - - recoverValue := dialed.Query().Get("recover") - assert.Equal(t, "", recoverValue, - "didn't expect a recover key; got %v", dialed) + assert.Empty(t, dialed.Query().Get("resume")) + assert.Empty(t, dialed.Query().Get("recover")) + ablytest.Instantly.Recv(t, nil, connected, t.Fatalf) // RTN15g3: Expect the previously attaching and attached channels to be // attached again. - attachExpected := map[string]struct{}{ + attachExpected = map[string]struct{}{ "attaching": {}, "attached": {}, } From 3a65ac62d5a3e4200ad29550ffa6694b99e2fc88 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 7 Nov 2023 17:45:54 +0530 Subject: [PATCH 126/178] Simplified assertions for realtime conn integration test --- ably/realtime_conn_spec_integration_test.go | 38 ++++++++------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index d69a3beb5..d906f87fe 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -1412,18 +1412,13 @@ func TestRealtimeConn_RTN15g_NewConnectionOnStateLost(t *testing.T) { // RTN15g3: Expect the previously attaching and attached channels to be // attached again. - attachExpected := map[string]struct{}{ - "attaching": {}, - "attached": {}, - } - for len(attachExpected) > 0 { - var msg *ably.ProtocolMessage - ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) - _, ok := attachExpected[msg.Channel] - assert.True(t, ok, - "ATTACH sent for unexpected or already attaching channel %q", msg.Channel) - delete(attachExpected, msg.Channel) - } + + ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) + assert.Equal(t, "attaching", msg.Channel) + + ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) + assert.Equal(t, "attached", msg.Channel) + ablytest.Instantly.NoRecv(t, nil, out, t.Fatalf) // Now do the same, but past connectionStateTTL + maxIdleInterval. This @@ -1439,18 +1434,13 @@ func TestRealtimeConn_RTN15g_NewConnectionOnStateLost(t *testing.T) { // RTN15g3: Expect the previously attaching and attached channels to be // attached again. - attachExpected = map[string]struct{}{ - "attaching": {}, - "attached": {}, - } - for len(attachExpected) > 0 { - var msg *ably.ProtocolMessage - ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) - _, ok := attachExpected[msg.Channel] - assert.True(t, ok, - "ATTACH sent for unexpected or already attaching channel %q", msg.Channel) - delete(attachExpected, msg.Channel) - } + + ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) + assert.Equal(t, "attaching", msg.Channel) + + ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) + assert.Equal(t, "attached", msg.Channel) + ablytest.Instantly.NoRecv(t, nil, out, t.Fatalf) } From 4c5c4cc691c96f7cd95831c5a246462db8d44b4c Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 7 Nov 2023 18:10:08 +0530 Subject: [PATCH 127/178] Refactored realtime channel code, added locks at right places --- ably/realtime_channel.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 148b601a6..eeaebe2c3 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -49,8 +49,6 @@ func newChannels(client *Realtime) *RealtimeChannels { // RTN16j, RTL15b func (channels *RealtimeChannels) SetChannelSerialsFromRecoverOption(serials map[string]string) { - channels.mtx.Lock() - defer channels.mtx.Unlock() for channelName, channelSerial := range serials { channel := channels.Get(channelName) channel.setChannelSerial(channelSerial) @@ -706,7 +704,9 @@ func (c *RealtimeChannel) HistoryUntilAttach(o ...HistoryOption) (*HistoryReques } untilAttachParam := func(o *historyOptions) { + c.mtx.Lock() o.params.Set("fromSerial", c.properties.AttachSerial) + c.mtx.Unlock() } o = append(o, untilAttachParam) @@ -795,8 +795,10 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { switch msg.Action { case actionAttached: + c.mtx.Lock() c.properties.AttachSerial = msg.ChannelSerial // RTL15a - if c.State() == ChannelStateDetaching { // RTL5K + c.mtx.Unlock() + if c.State() == ChannelStateDetaching { // RTL5K c.sendDetachMsg() return } From e2cd82ea048683309e1bc346375f7b9279352947 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 7 Nov 2023 18:10:32 +0530 Subject: [PATCH 128/178] setting recover only if recoveryContext connectionKey is set --- ably/realtime_conn.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 219ba8510..92154c5ae 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -290,8 +290,9 @@ func (c *Connection) params(mode connectionMode) (url.Values, error) { recoveryKeyContext, err := DecodeRecoveryKey(c.recover) if err != nil { c.log().Errorf("error decoding recovery key, %v", err) + } else { + query.Set("recover", recoveryKeyContext.ConnectionKey) // RTN16k } - query.Set("recover", recoveryKeyContext.ConnectionKey) // RTN16k } return query, nil } From 55fbfc781aaab105cd99590361d7da0efd2ee40b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 7 Nov 2023 19:00:12 +0530 Subject: [PATCH 129/178] Added test assertions for RTN16 --- ably/realtime_conn_spec_integration_test.go | 70 +++++++++++---------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index d906f87fe..1e84f4f48 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -1766,20 +1766,24 @@ func TestRealtimeConn_RTN16(t *testing.T) { err = channel.Publish(context.Background(), "name", "data") assert.NoError(t, err) prevMsgSerial := c.Connection.MsgSerial() + prevConnId := c.Connection.ID() client := app.NewRealtime( - ably.WithRecover(c.Connection.RecoveryKey()), + ably.WithRecover(c.Connection.RecoveryKey()), // RTN16g - createRecoveryKey ) defer safeclose(t, ablytest.FullRealtimeCloser(client)) err = ablytest.Wait(ablytest.ConnWaiter(client, client.Connect, ably.ConnectionEventConnected), nil) assert.NoError(t, err) - { //RTN16b, RTN16f + { // RTN16f, RTN16j assert.True(t, sameConnection(client.Connection.Key(), c.Connection.Key()), "expected the same connection") + assert.Equal(t, prevConnId, c.Connection.ID()) + assert.Nil(t, client.Connection.ErrorReason()) assert.Equal(t, prevMsgSerial, client.Connection.MsgSerial(), "expected %d got %d", prevMsgSerial, client.Connection.MsgSerial()) + assert.True(t, client.Channels.Exists("channel")) } { //(RTN16c) err := ablytest.Wait(ablytest.ConnWaiter(client, client.Close, ably.ConnectionEventClosed), nil) @@ -1791,37 +1795,37 @@ func TestRealtimeConn_RTN16(t *testing.T) { assert.Equal(t, "", client.Connection.ID(), "expected id to be empty got %q instead", client.Connection.ID()) } - { //(RTN16e) - // This test was adopted from the ably-js project - // https://github.com/ably/ably-js/blob/340e5ce31dc9d7434a06ae4e1eec32bdacc9c6c5/spec/realtime/connection.test.js#L119 - // var query url.Values - client2 := app.NewRealtime( - ably.WithRecover("_____!ablygo_test_fake-key____:5:3"), - ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { - // query = u.Query() - return ably.DialWebsocket(protocol, u, timeout) - })) - defer safeclose(t, ablytest.FullRealtimeCloser(client2)) - err = ablytest.Wait(ablytest.ConnWaiter(client2, client2.Connect, ably.ConnectionEventConnected), nil) - assert.Error(t, err, "expected reason to be set") - if err == nil { - t.Fatal("expected reason to be set") - } - { //(RTN16e) - info := err.(*ably.ErrorInfo) - assert.Equal(t, 80008, int(info.Code), - "expected 80008 got %d", info.Code) - reason := client2.Connection.ErrorReason() - assert.Equal(t, 80008, int(reason.Code), - "expected 80008 got %d", reason.Code) - msgSerial := client2.Connection.MsgSerial() - // verify msgSerial is 0 (new connection), not 3 - assert.Equal(t, int64(0), msgSerial, - "expected 0 got %d", msgSerial) - assert.NotContains(t, client2.Connection.Key(), "ablygo_test_fake", - "expected %q not to contain \"ablygo_test_fake\"", client2.Connection.Key()) - } - } + // { //(RTN16e) + // // This test was adopted from the ably-js project + // // https://github.com/ably/ably-js/blob/340e5ce31dc9d7434a06ae4e1eec32bdacc9c6c5/spec/realtime/connection.test.js#L119 + // // var query url.Values + // client2 := app.NewRealtime( + // ably.WithRecover("_____!ablygo_test_fake-key____:5:3"), + // ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { + // // query = u.Query() + // return ably.DialWebsocket(protocol, u, timeout) + // })) + // defer safeclose(t, ablytest.FullRealtimeCloser(client2)) + // err = ablytest.Wait(ablytest.ConnWaiter(client2, client2.Connect, ably.ConnectionEventConnected), nil) + // assert.Error(t, err, "expected reason to be set") + // if err == nil { + // t.Fatal("expected reason to be set") + // } + // { //(RTN16e) + // info := err.(*ably.ErrorInfo) + // assert.Equal(t, 80008, int(info.Code), + // "expected 80008 got %d", info.Code) + // reason := client2.Connection.ErrorReason() + // assert.Equal(t, 80008, int(reason.Code), + // "expected 80008 got %d", reason.Code) + // msgSerial := client2.Connection.MsgSerial() + // // verify msgSerial is 0 (new connection), not 3 + // assert.Equal(t, int64(0), msgSerial, + // "expected 0 got %d", msgSerial) + // assert.NotContains(t, client2.Connection.Key(), "ablygo_test_fake", + // "expected %q not to contain \"ablygo_test_fake\"", client2.Connection.Key()) + // } + // } } func sameConnection(a, b string) bool { From e383770ac1b40ae05fc937a143ecdf1df8e80a51 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 7 Nov 2023 19:52:13 +0530 Subject: [PATCH 130/178] Improved retry mechanism while creating a testing sandbox app --- ablytest/sandbox.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ablytest/sandbox.go b/ablytest/sandbox.go index 9b81ce839..207ac0902 100644 --- a/ablytest/sandbox.go +++ b/ablytest/sandbox.go @@ -165,7 +165,7 @@ func NewSandboxWithEnv(config *Config, env string) (*Sandbox, error) { } } - if err != nil { + if err != nil || (resp != nil && resp.StatusCode == 504) { // gateway timeout // Timeout. Back off before allowing another attempt. log.Println("warn: request timeout, attempting retry") time.Sleep(retryInterval) From 8fbb62fe2dd4eff61a810f0de72d69df33b76174 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 7 Nov 2023 21:11:13 +0530 Subject: [PATCH 131/178] Updated test for connection recovery --- ably/export_test.go | 6 ++ ably/realtime_conn_spec_integration_test.go | 79 ++++++++++++--------- 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index 2f25b184e..8125e7456 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -93,6 +93,12 @@ func (c *REST) GetCachedFallbackHost() string { return c.successFallbackHost.get() } +func (c *RealtimeChannel) GetChannelSerial() string { + c.mtx.Lock() + defer c.mtx.Unlock() + return c.properties.ChannelSerial +} + func (c *RealtimeChannel) GetAttachResume() bool { c.mtx.Lock() defer c.mtx.Unlock() diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 1e84f4f48..693ade8dc 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -1765,18 +1765,26 @@ func TestRealtimeConn_RTN16(t *testing.T) { assert.NoError(t, err) err = channel.Publish(context.Background(), "name", "data") assert.NoError(t, err) + prevMsgSerial := c.Connection.MsgSerial() prevConnId := c.Connection.ID() + recoveryKey := c.Connection.CreateRecoveryKey() // RTN16g - createRecoveryKey + decodedRecoveryKey, err := ably.DecodeRecoveryKey(recoveryKey) // RTN16g1 + assert.Nil(t, err) + + deprecatedRecoveryKey := c.Connection.RecoveryKey() + assert.Equal(t, deprecatedRecoveryKey, recoveryKey) //RTN16m + client := app.NewRealtime( - ably.WithRecover(c.Connection.RecoveryKey()), // RTN16g - createRecoveryKey + ably.WithRecover(recoveryKey), ) defer safeclose(t, ablytest.FullRealtimeCloser(client)) err = ablytest.Wait(ablytest.ConnWaiter(client, client.Connect, ably.ConnectionEventConnected), nil) assert.NoError(t, err) - { // RTN16f, RTN16j + { // RTN16f, RTN16j, RTN16d assert.True(t, sameConnection(client.Connection.Key(), c.Connection.Key()), "expected the same connection") assert.Equal(t, prevConnId, c.Connection.ID()) @@ -1784,8 +1792,10 @@ func TestRealtimeConn_RTN16(t *testing.T) { assert.Equal(t, prevMsgSerial, client.Connection.MsgSerial(), "expected %d got %d", prevMsgSerial, client.Connection.MsgSerial()) assert.True(t, client.Channels.Exists("channel")) + channelSerial := client.Channels.Get("channel").GetChannelSerial() + assert.Equal(t, decodedRecoveryKey.ChannelSerials["channel"], channelSerial) } - { //(RTN16c) + { //(RTN16g2) err := ablytest.Wait(ablytest.ConnWaiter(client, client.Close, ably.ConnectionEventClosed), nil) assert.NoError(t, err) assert.Equal(t, "", client.Connection.Key(), @@ -1795,37 +1805,38 @@ func TestRealtimeConn_RTN16(t *testing.T) { assert.Equal(t, "", client.Connection.ID(), "expected id to be empty got %q instead", client.Connection.ID()) } - // { //(RTN16e) - // // This test was adopted from the ably-js project - // // https://github.com/ably/ably-js/blob/340e5ce31dc9d7434a06ae4e1eec32bdacc9c6c5/spec/realtime/connection.test.js#L119 - // // var query url.Values - // client2 := app.NewRealtime( - // ably.WithRecover("_____!ablygo_test_fake-key____:5:3"), - // ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { - // // query = u.Query() - // return ably.DialWebsocket(protocol, u, timeout) - // })) - // defer safeclose(t, ablytest.FullRealtimeCloser(client2)) - // err = ablytest.Wait(ablytest.ConnWaiter(client2, client2.Connect, ably.ConnectionEventConnected), nil) - // assert.Error(t, err, "expected reason to be set") - // if err == nil { - // t.Fatal("expected reason to be set") - // } - // { //(RTN16e) - // info := err.(*ably.ErrorInfo) - // assert.Equal(t, 80008, int(info.Code), - // "expected 80008 got %d", info.Code) - // reason := client2.Connection.ErrorReason() - // assert.Equal(t, 80008, int(reason.Code), - // "expected 80008 got %d", reason.Code) - // msgSerial := client2.Connection.MsgSerial() - // // verify msgSerial is 0 (new connection), not 3 - // assert.Equal(t, int64(0), msgSerial, - // "expected 0 got %d", msgSerial) - // assert.NotContains(t, client2.Connection.Key(), "ablygo_test_fake", - // "expected %q not to contain \"ablygo_test_fake\"", client2.Connection.Key()) - // } - // } + { //(RTN16l) + // This test was adopted from the ably-js project + // https://github.com/ably/ably-js/blob/340e5ce31dc9d7434a06ae4e1eec32bdacc9c6c5/spec/realtime/connection.test.js#L119 + // var query url.Values + decodedRecoveryKey.ConnectionKey = "ablygo_test_fake-key____" + faultyRecoveryKey, _ := decodedRecoveryKey.Encode() + client2 := app.NewRealtime( + ably.WithRecover(faultyRecoveryKey), + ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { + // query = u.Query() + return ably.DialWebsocket(protocol, u, timeout) + })) + defer safeclose(t, ablytest.FullRealtimeCloser(client2)) + err = ablytest.Wait(ablytest.ConnWaiter(client2, client2.Connect, ably.ConnectionEventConnected), nil) + assert.Error(t, err, "expected reason to be set") + if err == nil { + t.Fatal("expected reason to be set") + } + { //(RTN16e) + info := err.(*ably.ErrorInfo) + assert.Equal(t, 80018, int(info.Code), + "expected 80018 got %d", info.Code) + reason := client2.Connection.ErrorReason() + assert.Equal(t, 80018, int(reason.Code), + "expected 80018 got %d", reason.Code) + msgSerial := client2.Connection.MsgSerial() + // verify msgSerial is 0 (new connection), not 3 + assert.Zero(t, msgSerial) + assert.NotContains(t, client2.Connection.Key(), "ablygo_test_fake", + "expected %q not to contain \"ablygo_test_fake\"", client2.Connection.Key()) + } + } } func sameConnection(a, b string) bool { From 822f4b93d7bbaace3a80b9c017691d7d53ab9674 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 7 Nov 2023 23:51:35 +0530 Subject: [PATCH 132/178] Fixed intermittently failing tests --- .../realtime_channel_spec_integration_test.go | 4 +- ably/realtime_conn_spec_integration_test.go | 37 ++++++++++++------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/ably/realtime_channel_spec_integration_test.go b/ably/realtime_channel_spec_integration_test.go index 2211653f2..aab36ecb1 100644 --- a/ably/realtime_channel_spec_integration_test.go +++ b/ably/realtime_channel_spec_integration_test.go @@ -2835,7 +2835,9 @@ func TestRealtimeChannel_RTL14_HandleChannelError(t *testing.T) { channel = c.Channels.Get("test") - go channel.Attach(canceledCtx) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go channel.Attach(ctx) ablytest.Instantly.Recv(t, nil, afterCalls, t.Fatalf) // Consume timer ablytest.Instantly.Recv(t, nil, out, t.Fatalf) // Consume ATTACHING diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 693ade8dc..cf91a380e 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -1413,12 +1413,18 @@ func TestRealtimeConn_RTN15g_NewConnectionOnStateLost(t *testing.T) { // RTN15g3: Expect the previously attaching and attached channels to be // attached again. - ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) - assert.Equal(t, "attaching", msg.Channel) - - ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) - assert.Equal(t, "attached", msg.Channel) - + attachExpected := map[string]struct{}{ + "attaching": {}, + "attached": {}, + } + for len(attachExpected) > 0 { + var msg *ably.ProtocolMessage + ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) + _, ok := attachExpected[msg.Channel] + assert.True(t, ok, + "ATTACH sent for unexpected or already attaching channel %q", msg.Channel) + delete(attachExpected, msg.Channel) + } ablytest.Instantly.NoRecv(t, nil, out, t.Fatalf) // Now do the same, but past connectionStateTTL + maxIdleInterval. This @@ -1434,13 +1440,18 @@ func TestRealtimeConn_RTN15g_NewConnectionOnStateLost(t *testing.T) { // RTN15g3: Expect the previously attaching and attached channels to be // attached again. - - ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) - assert.Equal(t, "attaching", msg.Channel) - - ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) - assert.Equal(t, "attached", msg.Channel) - + attachExpected = map[string]struct{}{ + "attaching": {}, + "attached": {}, + } + for len(attachExpected) > 0 { + var msg *ably.ProtocolMessage + ablytest.Instantly.Recv(t, &msg, out, t.Fatalf) + _, ok := attachExpected[msg.Channel] + assert.True(t, ok, + "ATTACH sent for unexpected or already attaching channel %q", msg.Channel) + delete(attachExpected, msg.Channel) + } ablytest.Instantly.NoRecv(t, nil, out, t.Fatalf) } From 3d764eb260c8525baefb719a4a74033a942a1ee7 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 8 Nov 2023 00:06:43 +0530 Subject: [PATCH 133/178] Fixed flaky test behavior for RTN16 --- ably/realtime_conn_spec_integration_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index cf91a380e..539cadb15 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -1774,9 +1774,19 @@ func TestRealtimeConn_RTN16(t *testing.T) { channel := c.Channels.Get("channel") err = channel.Attach(context.Background()) assert.NoError(t, err) + + var msg *ably.Message + sub, unsub, err := ablytest.ReceiveMessages(channel, "") + if err != nil { + t.Fatal(err) + } + defer unsub() err = channel.Publish(context.Background(), "name", "data") assert.NoError(t, err) + ablytest.Soon.Recv(t, &msg, sub, t.Fatalf) + assert.Equal(t, "data", msg.Data) + prevMsgSerial := c.Connection.MsgSerial() prevConnId := c.Connection.ID() From d2910bd09463427adb33be4d0045086360dde741 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 8 Nov 2023 16:53:21 +0530 Subject: [PATCH 134/178] Moved unit tests for presence to proto presence message test --- ably/proto_presence_message_test.go | 102 +++++++++++++++++++++++++++ ably/proto_protocol_message_test.go | 103 ---------------------------- ably/realtime_channel.go | 2 +- 3 files changed, 103 insertions(+), 104 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 24fb12eed..fca515931 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -57,3 +57,105 @@ func TestPresenceMessage(t *testing.T) { }) } } + +func TestPresenceCheckForNewNessByTimestampIfSynthesized_RTP2b1(t *testing.T) { + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:12:1", + Timestamp: 125, + ConnectionID: "987", + }, + } + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:12:2", + Timestamp: 123, + ConnectionID: "784", + }, + } + isNewMsg, err := presenceMsg1.IsNewerThan(presenceMsg2) + assert.Nil(t, err) + assert.True(t, isNewMsg) + + isNewMsg, err = presenceMsg2.IsNewerThan(presenceMsg1) + assert.Nil(t, err) + assert.False(t, isNewMsg) +} + +func TestPresenceCheckForNewNessBySerialIfNotSynthesized__RTP2b2(t *testing.T) { + oldPresenceMsg := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:12:0", + Timestamp: 123, + ConnectionID: "123", + }, + } + newPresenceMessage := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:12:1", + Timestamp: 123, + ConnectionID: "123", + }, + } + isNewMsg, err := oldPresenceMsg.IsNewerThan(newPresenceMessage) + assert.Nil(t, err) + assert.False(t, isNewMsg) + + isNewMsg, err = newPresenceMessage.IsNewerThan(oldPresenceMsg) + assert.Nil(t, err) + assert.True(t, isNewMsg) +} + +func TestPresenceMessagesShouldReturnErrorForWrongMessageSerials__RTP2b2(t *testing.T) { + // Both has invalid msgserial + msg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:1a:0", + Timestamp: 123, + ConnectionID: "123", + }, + } + + msg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:1b:1", + Timestamp: 124, + ConnectionID: "123", + }, + } + isNewMsg, err := msg1.IsNewerThan(msg2) + assert.NotNil(t, err) + assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1a:0") + assert.False(t, isNewMsg) + + isNewMsg, err = msg2.IsNewerThan(msg1) + assert.NotNil(t, err) + assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1b:1") + assert.False(t, isNewMsg) + + // msg2 has valid messageSerial + msg2 = &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:10:0", + Timestamp: 124, + ConnectionID: "123", + }, + } + + isNewMsg, err = msg1.IsNewerThan(msg2) + assert.NotNil(t, err) + assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1a:0") + assert.False(t, isNewMsg) + + isNewMsg, err = msg2.IsNewerThan(msg1) + assert.NotNil(t, err) + assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1a:0") + assert.True(t, isNewMsg) +} diff --git a/ably/proto_protocol_message_test.go b/ably/proto_protocol_message_test.go index 68532f5da..d0b746710 100644 --- a/ably/proto_protocol_message_test.go +++ b/ably/proto_protocol_message_test.go @@ -5,7 +5,6 @@ package ably_test import ( "bytes" - "fmt" "strconv" "testing" @@ -138,105 +137,3 @@ func TestIfHasFlg(t *testing.T) { assert.False(t, flags.Has(ably.FlagHasBacklog), "Shouldn't contain flag %v", ably.FlagHasBacklog) } - -func TestPresenceCheckForNewNessByTimestampIfSynthesized_RTP2b1(t *testing.T) { - presenceMsg1 := &ably.PresenceMessage{ - Action: ably.PresenceActionPresent, - Message: ably.Message{ - ID: "123:12:1", - Timestamp: 125, - ConnectionID: "987", - }, - } - presenceMsg2 := &ably.PresenceMessage{ - Action: ably.PresenceActionPresent, - Message: ably.Message{ - ID: "123:12:2", - Timestamp: 123, - ConnectionID: "784", - }, - } - isNewMsg, err := presenceMsg1.IsNewerThan(presenceMsg2) - assert.Nil(t, err) - assert.True(t, isNewMsg) - - isNewMsg, err = presenceMsg2.IsNewerThan(presenceMsg1) - assert.Nil(t, err) - assert.False(t, isNewMsg) -} - -func TestPresenceCheckForNewNessBySerialIfNotSynthesized__RTP2b2(t *testing.T) { - oldPresenceMsg := &ably.PresenceMessage{ - Action: ably.PresenceActionPresent, - Message: ably.Message{ - ID: "123:12:0", - Timestamp: 123, - ConnectionID: "123", - }, - } - newPresenceMessage := &ably.PresenceMessage{ - Action: ably.PresenceActionPresent, - Message: ably.Message{ - ID: "123:12:1", - Timestamp: 123, - ConnectionID: "123", - }, - } - isNewMsg, err := oldPresenceMsg.IsNewerThan(newPresenceMessage) - assert.Nil(t, err) - assert.False(t, isNewMsg) - - isNewMsg, err = newPresenceMessage.IsNewerThan(oldPresenceMsg) - assert.Nil(t, err) - assert.True(t, isNewMsg) -} - -func TestPresenceMessagesShouldReturnErrorForWrongMessageSerials__RTP2b2(t *testing.T) { - // Both has invalid msgserial - msg1 := &ably.PresenceMessage{ - Action: ably.PresenceActionPresent, - Message: ably.Message{ - ID: "123:1a:0", - Timestamp: 123, - ConnectionID: "123", - }, - } - - msg2 := &ably.PresenceMessage{ - Action: ably.PresenceActionPresent, - Message: ably.Message{ - ID: "123:1b:1", - Timestamp: 124, - ConnectionID: "123", - }, - } - isNewMsg, err := msg1.IsNewerThan(msg2) - assert.NotNil(t, err) - assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1a:0") - assert.False(t, isNewMsg) - - isNewMsg, err = msg2.IsNewerThan(msg1) - assert.NotNil(t, err) - assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1b:1") - assert.False(t, isNewMsg) - - // msg2 has valid messageSerial - msg2 = &ably.PresenceMessage{ - Action: ably.PresenceActionPresent, - Message: ably.Message{ - ID: "123:10:0", - Timestamp: 124, - ConnectionID: "123", - }, - } - - isNewMsg, err = msg1.IsNewerThan(msg2) - assert.NotNil(t, err) - assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1a:0") - assert.False(t, isNewMsg) - - isNewMsg, err = msg2.IsNewerThan(msg1) - assert.NotNil(t, err) - assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1a:0") - assert.True(t, isNewMsg) -} diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index eeaebe2c3..dcd8cc8a5 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -975,7 +975,7 @@ func (c *RealtimeChannel) setState(state ChannelState, err error, resumed bool) c.mtx.Lock() defer c.mtx.Unlock() - // RT5Pa + // RTP5a if state == ChannelStateDetached || state == ChannelStateFailed { c.Presence.onChannelDetachedOrFailed(channelStateError(state, err)) } From 3857e180dfe7454393c8350a1f7d80545192ad1a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 8 Nov 2023 18:53:39 +0530 Subject: [PATCH 135/178] Fixed test failure to data race condition --- ably/realtime_presence.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 103527153..7327a4b34 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -101,7 +101,7 @@ func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { onAck := func(err error) { listen <- err } - switch pres.channel.state { + switch pres.channel.State() { case ChannelStateInitialized: // RTP16b if pres.maybeEnqueue(protomsg, onAck) { pres.channel.attach() From 70c7c71805e1b0c630be85c0fc92d8dbed4b8cf2 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 8 Nov 2023 19:25:30 +0530 Subject: [PATCH 136/178] Added one more check for connection retry for connection reset --- ablytest/sandbox.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ablytest/sandbox.go b/ablytest/sandbox.go index 207ac0902..59a7f36cf 100644 --- a/ablytest/sandbox.go +++ b/ablytest/sandbox.go @@ -13,6 +13,7 @@ import ( "net/url" "os" "path" + "syscall" "time" "github.com/ably/ably-go/ably" @@ -159,9 +160,11 @@ func NewSandboxWithEnv(config *Config, env string) (*Sandbox, error) { req.Header.Set("Accept", "application/json") resp, err := app.client.Do(req) if err != nil { - // return from this function now only if the error wasn't due to a timeout - if err, ok := err.(*url.Error); ok && !err.Timeout() { - return nil, err + if !errors.Is(err, syscall.ECONNRESET) { // if not connection reset by peer + // return error if it wasn't due to a timeout + if err, ok := err.(*url.Error); ok && !err.Timeout() { + return nil, err + } } } From be4a5a34d08d971b99f2d1917f6bee2dc0123d5e Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 8 Nov 2023 20:58:55 +0530 Subject: [PATCH 137/178] Updated connection recovery, added recover check for url query --- ably/realtime_conn_spec_integration_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 539cadb15..b2cafacd8 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -1829,13 +1829,13 @@ func TestRealtimeConn_RTN16(t *testing.T) { { //(RTN16l) // This test was adopted from the ably-js project // https://github.com/ably/ably-js/blob/340e5ce31dc9d7434a06ae4e1eec32bdacc9c6c5/spec/realtime/connection.test.js#L119 - // var query url.Values + var query url.Values decodedRecoveryKey.ConnectionKey = "ablygo_test_fake-key____" faultyRecoveryKey, _ := decodedRecoveryKey.Encode() client2 := app.NewRealtime( ably.WithRecover(faultyRecoveryKey), ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { - // query = u.Query() + query = u.Query() return ably.DialWebsocket(protocol, u, timeout) })) defer safeclose(t, ablytest.FullRealtimeCloser(client2)) @@ -1844,6 +1844,11 @@ func TestRealtimeConn_RTN16(t *testing.T) { if err == nil { t.Fatal("expected reason to be set") } + { // (RTN16i) + recoverValue := query.Get("recover") + assert.NotEmpty(t, recoverValue) + assert.Equal(t, "ablygo_test_fake-key____", recoverValue) + } { //(RTN16e) info := err.(*ably.ErrorInfo) assert.Equal(t, 80018, int(info.Code), From e23b82a7390b4951adee04a0b2f3a97eae875239 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 9 Nov 2023 00:31:21 +0530 Subject: [PATCH 138/178] Added dummy tests for RTP2 and RTP18 --- ably/realtime_presence_internal_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ably/realtime_presence_internal_test.go b/ably/realtime_presence_internal_test.go index 3b4494348..ff83da28e 100644 --- a/ably/realtime_presence_internal_test.go +++ b/ably/realtime_presence_internal_test.go @@ -113,3 +113,11 @@ func TestSend(t *testing.T) { }) } } + +func Test_PresenceMap_RTP2(t *testing.T) { + assert.True(t, true) +} + +func Test_Presence_SYNC_RTP18(t *testing.T) { + assert.True(t, true) +} From 5e28c997c333a0ebc2f06a570b49ca38eed00aa5 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 9 Nov 2023 13:10:32 +0530 Subject: [PATCH 139/178] fixed data race condition for realtime presence --- ably/realtime_presence.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 7327a4b34..5a95fcdc2 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -286,8 +286,8 @@ func (pres *RealtimePresence) processProtoPresenceMessage(msg *protocolMessage) pres.mtx.Lock() // RTP17 - Update internal presence map for _, presenceMember := range msg.Presence { - memberKey := presenceMember.ClientID // RTP17h - if pres.channel.client.Connection.id != presenceMember.ConnectionID { // RTP17 + memberKey := presenceMember.ClientID // RTP17h + if pres.channel.client.Connection.ID() != presenceMember.ConnectionID { // RTP17 continue } switch presenceMember.Action { From 1e5823649b0b960bba05e359eed092523965ea5f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 9 Nov 2023 19:01:20 +0530 Subject: [PATCH 140/178] Added dummy test for RTP2 --- ably/proto_presence_message_test.go | 83 +++++++++++++++++++++++++ ably/realtime_presence_internal_test.go | 8 --- 2 files changed, 83 insertions(+), 8 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index fca515931..411e58899 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -7,9 +7,11 @@ import ( "encoding/json" "fmt" "testing" + "time" "github.com/ably/ably-go/ably" "github.com/ably/ably-go/ably/internal/ablyutil" + "github.com/ably/ably-go/ablytest" "github.com/stretchr/testify/assert" ) @@ -159,3 +161,84 @@ func TestPresenceMessagesShouldReturnErrorForWrongMessageSerials__RTP2b2(t *test assert.Contains(t, fmt.Sprint(err), "the presence message has invalid msgSerial, for msgId 123:1a:0") assert.True(t, isNewMsg) } + +func Test_PresenceMap_RTP2(t *testing.T) { + const channelRetryTimeout = 123 * time.Millisecond + const realtimeRequestTimeout = 2 * time.Second + + setup := func(t *testing.T) ( + in, out chan *ably.ProtocolMessage, + c *ably.Realtime, + channel *ably.RealtimeChannel, + stateChanges ably.ChannelStateChanges, + afterCalls chan ablytest.AfterCall, + ) { + in = make(chan *ably.ProtocolMessage, 1) + out = make(chan *ably.ProtocolMessage, 16) + afterCalls = make(chan ablytest.AfterCall, 1) + now, after := ablytest.TimeFuncs(afterCalls) + + c, _ = ably.NewRealtime( + ably.WithToken("fake:token"), + ably.WithAutoConnect(false), + ably.WithChannelRetryTimeout(channelRetryTimeout), + ably.WithRealtimeRequestTimeout(realtimeRequestTimeout), + ably.WithDial(MessagePipe(in, out)), + ably.WithNow(now), + ably.WithAfter(after), + ) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionConnected, + ConnectionID: "connection-id", + ConnectionDetails: &ably.ConnectionDetails{}, + } + + err := ablytest.Wait(ablytest.ConnWaiter(c, c.Connect, ably.ConnectionEventConnected), nil) + assert.NoError(t, err) + + channel = c.Channels.Get("test") + stateChanges = make(ably.ChannelStateChanges, 10) + return + } + + t.Run("RTL14: when Error, should transition to failed state", func(t *testing.T) { + in, out, _, channel, stateChanges, afterCalls := setup(t) + + errInfo := ably.ProtoErrorInfo{ + StatusCode: 500, + Code: 50500, + Message: "fake error", + } + + in <- &ably.ProtocolMessage{ + Action: ably.ActionError, + Channel: channel.Name, + Error: &errInfo, + } + + // Expect a state change with the error. + + var change ably.ChannelStateChange + ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateFailed, change.Current, + "expected %v; got %v (event: %+v)", change) + + got := fmt.Sprint(change.Reason) + assert.Contains(t, got, "fake error", + "expected error info to contain \"fake error\"; got %v", got) + + got = fmt.Sprint(channel.ErrorReason()) + assert.Contains(t, got, "fake error", + "expected error info to contain \"fake error\"; got %v", got) + + ablytest.Instantly.NoRecv(t, nil, afterCalls, t.Fatalf) + ablytest.Instantly.NoRecv(t, nil, stateChanges, t.Fatalf) + ablytest.Instantly.NoRecv(t, nil, out, t.Fatalf) + }) + +} + +func Test_Presence_SYNC_RTP18(t *testing.T) { + assert.True(t, true) +} diff --git a/ably/realtime_presence_internal_test.go b/ably/realtime_presence_internal_test.go index ff83da28e..3b4494348 100644 --- a/ably/realtime_presence_internal_test.go +++ b/ably/realtime_presence_internal_test.go @@ -113,11 +113,3 @@ func TestSend(t *testing.T) { }) } } - -func Test_PresenceMap_RTP2(t *testing.T) { - assert.True(t, true) -} - -func Test_Presence_SYNC_RTP18(t *testing.T) { - assert.True(t, true) -} From 5d5465f0bb3a2280b184888598f3762144760a9f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 9 Nov 2023 21:21:06 +0530 Subject: [PATCH 141/178] Added basic test for RTP2 --- ably/export_test.go | 6 ++ ably/proto_presence_message_test.go | 85 +++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index 8125e7456..7cae2726e 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -211,6 +211,12 @@ func (c *Connection) SetKey(key string) { c.key = key } +func (c *RealtimePresence) GetMembers() map[string]*PresenceMessage { + c.mtx.Lock() + defer c.mtx.Unlock() + return c.members +} + func (c *Connection) ConnectionStateTTL() time.Duration { return c.connectionStateTTL() } diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 411e58899..9c6ba19d1 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -4,6 +4,7 @@ package ably_test import ( + "context" "encoding/json" "fmt" "testing" @@ -172,9 +173,12 @@ func Test_PresenceMap_RTP2(t *testing.T) { channel *ably.RealtimeChannel, stateChanges ably.ChannelStateChanges, afterCalls chan ablytest.AfterCall, + presenceMsgCh chan *ably.PresenceMessage, ) { in = make(chan *ably.ProtocolMessage, 1) out = make(chan *ably.ProtocolMessage, 16) + presenceMsgCh = make(chan *ably.PresenceMessage, 16) + afterCalls = make(chan ablytest.AfterCall, 1) now, after := ablytest.TimeFuncs(afterCalls) @@ -199,44 +203,77 @@ func Test_PresenceMap_RTP2(t *testing.T) { channel = c.Channels.Get("test") stateChanges = make(ably.ChannelStateChanges, 10) + channel.OnAll(stateChanges.Receive) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionAttached, + Channel: channel.Name, + } + + var change ably.ChannelStateChange + + ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttached, change.Current, + "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, change.Current) + + channel.Presence.SubscribeAll(context.Background(), func(message *ably.PresenceMessage) { + presenceMsgCh <- message + }) return } - t.Run("RTL14: when Error, should transition to failed state", func(t *testing.T) { - in, out, _, channel, stateChanges, afterCalls := setup(t) + t.Run("RTP2: should maintain a list of members present on the channel", func(t *testing.T) { + in, _, _, channel, _, _, presenceMsgCh := setup(t) + + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) - errInfo := ably.ProtoErrorInfo{ - StatusCode: 500, - Code: 50500, - Message: "fake error", + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:12:1", + Timestamp: 125, + ConnectionID: "987", + ClientID: "999", + }, } - in <- &ably.ProtocolMessage{ - Action: ably.ActionError, - Channel: channel.Name, - Error: &errInfo, + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "123:12:2", + Timestamp: 123, + ConnectionID: "784", + ClientID: "999", + }, } - // Expect a state change with the error. + msg := &ably.ProtocolMessage{ + Action: ably.ActionPresence, + Channel: channel.Name, + Presence: []*ably.PresenceMessage{presenceMsg1}, + } - var change ably.ChannelStateChange - ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) - assert.Equal(t, ably.ChannelStateFailed, change.Current, - "expected %v; got %v (event: %+v)", change) + in <- msg + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + presenceMembers := channel.Presence.GetMembers() - got := fmt.Sprint(change.Reason) - assert.Contains(t, got, "fake error", - "expected error info to contain \"fake error\"; got %v", got) + assert.Equal(t, 1, len(presenceMembers)) + member := presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] + assert.Equal(t, ably.PresenceActionPresent, member.Action) - got = fmt.Sprint(channel.ErrorReason()) - assert.Contains(t, got, "fake error", - "expected error info to contain \"fake error\"; got %v", got) + msg.Presence = []*ably.PresenceMessage{presenceMsg2} + in <- msg - ablytest.Instantly.NoRecv(t, nil, afterCalls, t.Fatalf) - ablytest.Instantly.NoRecv(t, nil, stateChanges, t.Fatalf) - ablytest.Instantly.NoRecv(t, nil, out, t.Fatalf) + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + assert.Equal(t, 2, len(channel.Presence.GetMembers())) + member2 := presenceMembers[presenceMsg2.ConnectionID+presenceMsg2.ClientID] + assert.Equal(t, ably.PresenceActionPresent, member2.Action) }) + t.Run("RTP2", func(t *testing.T) { + + }) } func Test_Presence_SYNC_RTP18(t *testing.T) { From 6473281dc8df8250b055d5696e4ef1c22a1a02fa Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 9 Nov 2023 23:02:38 +0530 Subject: [PATCH 142/178] Added tests for RTP2 and RTP18 --- ably/proto_presence_message_test.go | 41 ++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 9c6ba19d1..3ad26f7fc 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -271,11 +271,46 @@ func Test_PresenceMap_RTP2(t *testing.T) { assert.Equal(t, ably.PresenceActionPresent, member2.Action) }) - t.Run("RTP2", func(t *testing.T) { + t.Run("RTP2b1: check for newness by timestamp is synthesized", func(t *testing.T) { }) + + t.Run("RTP2b2: check for newness by timestamp is not synthesized", func(t *testing.T) { + + }) + + t.Run("RTP2c: check for newness during sync", func(t *testing.T) { + + }) + + t.Run("RTP2d: when presence msg with ENTER, UPDATE AND PRESENT arrives, add to presence map with action as present", func(t *testing.T) { + + }) + + t.Run("RTP2e: when presence msg with LEAVE action arrives, remove member from presence map", func(t *testing.T) { + + }) + + t.Run("RTP2f: when presence msg with LEAVE action arrives, if sync in progress, store as absent and remove it later", func(t *testing.T) { + + }) + + t.Run("RTP2g: incoming event should be emitted on realtimepresence object", func(t *testing.T) { + + }) + } -func Test_Presence_SYNC_RTP18(t *testing.T) { - assert.True(t, true) +func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { + t.Run("RTP18a: client determines a new sync started with :", func(t *testing.T) { + + }) + + t.Run("RTP18b: client determines sync ended with :", func(t *testing.T) { + + }) + + t.Run("RTP18: client determines sync started and ended with :", func(t *testing.T) { + + }) } From 9dfe0ae543c30e9ef2f9de678e4125013fe5f1c2 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 9 Nov 2023 23:17:06 +0530 Subject: [PATCH 143/178] Added tests for RTP17 --- ably/proto_presence_message_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 3ad26f7fc..fd3a81569 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -314,3 +314,25 @@ func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { }) } + +func Test_internal_presencemap_RTP17(t *testing.T) { + t.Run("RTP17: presence object should have second presencemap containing only currentConnectionId", func(t *testing.T) { + + }) + + t.Run("RTP17b: apply presence message events as per spec", func(t *testing.T) { + + }) + + t.Run("RTP17h: presencemap should be keyed by clientId", func(t *testing.T) { + + }) + + t.Run("RTP17f, RTP17g: automatic re-entry whenever channel moves into ATTACHED state", func(t *testing.T) { + + }) + + t.Run("RTP17e: publish error if automatic re-enter failed", func(t *testing.T) { + + }) +} From 2e07f4c6e7b68d3911ec90d597ef90e11e9c2b82 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 10 Nov 2023 01:02:21 +0530 Subject: [PATCH 144/178] Added test when message is synthesized, check newness by timestamp --- ably/proto_presence_message_test.go | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index fd3a81569..91405a888 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -272,7 +272,55 @@ func Test_PresenceMap_RTP2(t *testing.T) { }) t.Run("RTP2b1: check for newness by timestamp is synthesized", func(t *testing.T) { + in, _, _, channel, _, _, presenceMsgCh := setup(t) + + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) + + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:1", + Timestamp: 125, + ConnectionID: "987", + ClientID: "999", + }, + } + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "989:12:2", + Timestamp: 128, + ConnectionID: "987", + ClientID: "999", + }, + } + + msg := &ably.ProtocolMessage{ + Action: ably.ActionPresence, + Channel: channel.Name, + Presence: []*ably.PresenceMessage{presenceMsg1}, + } + + in <- msg + ablytest.Soon.Recv(t, nil, presenceMsgCh, t.Fatalf) + presenceMembers := channel.Presence.GetMembers() + + assert.Equal(t, 1, len(presenceMembers)) + member := presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] + assert.Equal(t, ably.PresenceActionPresent, member.Action) + assert.Equal(t, "987:12:1", member.ID) + + msg.Presence = []*ably.PresenceMessage{presenceMsg2} + in <- msg + + ablytest.Soon.Recv(t, nil, presenceMsgCh, t.Fatalf) + presenceMembers = channel.Presence.GetMembers() + assert.Equal(t, 1, len(presenceMembers)) + member = presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] + assert.Equal(t, ably.PresenceActionPresent, member.Action) + assert.Equal(t, "989:12:2", member.ID) }) t.Run("RTP2b2: check for newness by timestamp is not synthesized", func(t *testing.T) { From 1f6b1093915335d355809bcba0dc8a1aa6b6a205 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 10 Nov 2023 01:05:24 +0530 Subject: [PATCH 145/178] Added test to check newness when msg not synthesized --- ably/proto_presence_message_test.go | 60 ++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 91405a888..4e2605efd 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -280,7 +280,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { presenceMsg1 := &ably.PresenceMessage{ Action: ably.PresenceActionPresent, Message: ably.Message{ - ID: "987:12:1", + ID: "987:12:5", Timestamp: 125, ConnectionID: "987", ClientID: "999", @@ -290,7 +290,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { presenceMsg2 := &ably.PresenceMessage{ Action: ably.PresenceActionPresent, Message: ably.Message{ - ID: "989:12:2", + ID: "989:12:0", Timestamp: 128, ConnectionID: "987", ClientID: "999", @@ -304,27 +304,75 @@ func Test_PresenceMap_RTP2(t *testing.T) { } in <- msg - ablytest.Soon.Recv(t, nil, presenceMsgCh, t.Fatalf) + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) presenceMembers := channel.Presence.GetMembers() assert.Equal(t, 1, len(presenceMembers)) member := presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] assert.Equal(t, ably.PresenceActionPresent, member.Action) - assert.Equal(t, "987:12:1", member.ID) + assert.Equal(t, "987:12:5", member.ID) msg.Presence = []*ably.PresenceMessage{presenceMsg2} in <- msg - ablytest.Soon.Recv(t, nil, presenceMsgCh, t.Fatalf) + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) presenceMembers = channel.Presence.GetMembers() assert.Equal(t, 1, len(presenceMembers)) member = presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] assert.Equal(t, ably.PresenceActionPresent, member.Action) - assert.Equal(t, "989:12:2", member.ID) + assert.Equal(t, "989:12:0", member.ID) }) t.Run("RTP2b2: check for newness by timestamp is not synthesized", func(t *testing.T) { + in, _, _, channel, _, _, presenceMsgCh := setup(t) + + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:5", + Timestamp: 125, + ConnectionID: "987", + ClientID: "999", + }, + } + + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:0", + Timestamp: 128, + ConnectionID: "987", + ClientID: "999", + }, + } + + msg := &ably.ProtocolMessage{ + Action: ably.ActionPresence, + Channel: channel.Name, + Presence: []*ably.PresenceMessage{presenceMsg1}, + } + + in <- msg + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + presenceMembers := channel.Presence.GetMembers() + + assert.Equal(t, 1, len(presenceMembers)) + member := presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] + assert.Equal(t, ably.PresenceActionPresent, member.Action) + assert.Equal(t, "987:12:5", member.ID) + + msg.Presence = []*ably.PresenceMessage{presenceMsg2} + in <- msg + + ablytest.Instantly.NoRecv(t, nil, presenceMsgCh, t.Fatalf) + presenceMembers = channel.Presence.GetMembers() + assert.Equal(t, 1, len(presenceMembers)) + member = presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] + assert.Equal(t, ably.PresenceActionPresent, member.Action) + assert.Equal(t, "987:12:5", member.ID) }) t.Run("RTP2c: check for newness during sync", func(t *testing.T) { From 7a9d7d4f266e90b4117cdff58e3239d416fecaa7 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 12 Nov 2023 10:12:58 +0530 Subject: [PATCH 146/178] Updated test to check newness by serial if not synthesized --- ably/proto_presence_message_test.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 4e2605efd..d8f9d59a5 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -323,7 +323,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { assert.Equal(t, "989:12:0", member.ID) }) - t.Run("RTP2b2: check for newness by timestamp is not synthesized", func(t *testing.T) { + t.Run("RTP2b2: check for newness by serial if not synthesized", func(t *testing.T) { in, _, _, channel, _, _, presenceMsgCh := setup(t) initialMembers := channel.Presence.GetMembers() @@ -349,6 +349,16 @@ func Test_PresenceMap_RTP2(t *testing.T) { }, } + presenceMsg3 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:7", + Timestamp: 128, + ConnectionID: "987", + ClientID: "999", + }, + } + msg := &ably.ProtocolMessage{ Action: ably.ActionPresence, Channel: channel.Name, @@ -373,6 +383,16 @@ func Test_PresenceMap_RTP2(t *testing.T) { member = presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] assert.Equal(t, ably.PresenceActionPresent, member.Action) assert.Equal(t, "987:12:5", member.ID) + + msg.Presence = []*ably.PresenceMessage{presenceMsg3} + in <- msg + + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + presenceMembers = channel.Presence.GetMembers() + assert.Equal(t, 1, len(presenceMembers)) + member = presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] + assert.Equal(t, ably.PresenceActionPresent, member.Action) + assert.Equal(t, "987:12:7", member.ID) }) t.Run("RTP2c: check for newness during sync", func(t *testing.T) { From b99a651689139c0442e51d475c1ef47f82bf5009 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 12 Nov 2023 11:48:25 +0530 Subject: [PATCH 147/178] Fixed shallowcopy issue, added test for the same --- ably/proto_presence_message_test.go | 64 ++++++++++++++++++++++++++++- ably/realtime_presence.go | 8 ++-- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index d8f9d59a5..6785d8759 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -323,7 +323,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { assert.Equal(t, "989:12:0", member.ID) }) - t.Run("RTP2b2: check for newness by serial if not synthesized", func(t *testing.T) { + t.Run("RTP2b2, RTP2d: check for newness by serial if not synthesized", func(t *testing.T) { in, _, _, channel, _, _, presenceMsgCh := setup(t) initialMembers := channel.Presence.GetMembers() @@ -399,8 +399,68 @@ func Test_PresenceMap_RTP2(t *testing.T) { }) - t.Run("RTP2d: when presence msg with ENTER, UPDATE AND PRESENT arrives, add to presence map with action as present", func(t *testing.T) { + t.Run("RTP2d, RTP2g: when presence msg with ENTER, UPDATE AND PRESENT arrives, add to presence map with action as present", func(t *testing.T) { + in, _, _, channel, _, _, presenceMsgCh := setup(t) + + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) + + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionEnter, + Message: ably.Message{ + ID: "987:12:0", + Timestamp: 125, + ConnectionID: "987", + ClientID: "999", + }, + } + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionUpdate, + Message: ably.Message{ + ID: "987:12:1", + Timestamp: 128, + ConnectionID: "988", + ClientID: "999", + }, + } + + presenceMsg3 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:2", + Timestamp: 128, + ConnectionID: "989", + ClientID: "999", + }, + } + + msg := &ably.ProtocolMessage{ + Action: ably.ActionPresence, + Channel: channel.Name, + Presence: []*ably.PresenceMessage{presenceMsg1}, + } + + var presenceMsg *ably.PresenceMessage + in <- msg + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) // RTP2g + assert.Equal(t, ably.PresenceActionEnter, presenceMsg.Action) + + msg.Presence = []*ably.PresenceMessage{presenceMsg2} + in <- msg + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + assert.Equal(t, ably.PresenceActionUpdate, presenceMsg.Action) + + msg.Presence = []*ably.PresenceMessage{presenceMsg3} + in <- msg + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + assert.Equal(t, ably.PresenceActionPresent, presenceMsg.Action) + + members := channel.Presence.GetMembers() + assert.Equal(t, 3, len(members)) + for _, pm := range members { + assert.Equal(t, ably.PresenceActionPresent, pm.Action) + } }) t.Run("RTP2e: when presence msg with LEAVE action arrives, remove member from presence map", func(t *testing.T) { diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 5a95fcdc2..fe567d4a1 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -292,9 +292,9 @@ func (pres *RealtimePresence) processProtoPresenceMessage(msg *protocolMessage) } switch presenceMember.Action { case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: // RTP2d, RTP17b - presenceMemberShallowCopy := presenceMember + presenceMemberShallowCopy := *presenceMember presenceMemberShallowCopy.Action = PresenceActionPresent - pres.addPresenceMember(pres.internalMembers, memberKey, presenceMemberShallowCopy) + pres.addPresenceMember(pres.internalMembers, memberKey, &presenceMemberShallowCopy) case PresenceActionLeave: // RTP17b, RTP2e if !presenceMember.isServerSynthesized() { pres.removePresenceMember(pres.internalMembers, memberKey, presenceMember) @@ -310,9 +310,9 @@ func (pres *RealtimePresence) processProtoPresenceMessage(msg *protocolMessage) switch presenceMember.Action { case PresenceActionEnter, PresenceActionUpdate, PresenceActionPresent: // RTP2d delete(pres.beforeSyncMembers, memberKey) - presenceMemberShallowCopy := presenceMember + presenceMemberShallowCopy := *presenceMember presenceMemberShallowCopy.Action = PresenceActionPresent - memberUpdated = pres.addPresenceMember(pres.members, memberKey, presenceMemberShallowCopy) + memberUpdated = pres.addPresenceMember(pres.members, memberKey, &presenceMemberShallowCopy) case PresenceActionLeave: // RTP2e memberUpdated = pres.removePresenceMember(pres.members, memberKey, presenceMember) } From 30f7f1092f55901c97876d55dca3362525b25cf5 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 12 Nov 2023 12:16:57 +0530 Subject: [PATCH 148/178] Added a test for presence LEAVE action --- ably/proto_presence_message_test.go | 78 +++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 6785d8759..94c5bd4c6 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -443,16 +443,19 @@ func Test_PresenceMap_RTP2(t *testing.T) { var presenceMsg *ably.PresenceMessage in <- msg - ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) // RTP2g + // RTP2g + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) assert.Equal(t, ably.PresenceActionEnter, presenceMsg.Action) msg.Presence = []*ably.PresenceMessage{presenceMsg2} in <- msg + // RTP2g ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) assert.Equal(t, ably.PresenceActionUpdate, presenceMsg.Action) msg.Presence = []*ably.PresenceMessage{presenceMsg3} in <- msg + // RTP2g ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) assert.Equal(t, ably.PresenceActionPresent, presenceMsg.Action) @@ -463,15 +466,80 @@ func Test_PresenceMap_RTP2(t *testing.T) { } }) - t.Run("RTP2e: when presence msg with LEAVE action arrives, remove member from presence map", func(t *testing.T) { + t.Run("RTP2e, RTP2g: when presence msg with LEAVE action arrives, remove member from presence map", func(t *testing.T) { + in, _, _, channel, _, _, presenceMsgCh := setup(t) - }) + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) - t.Run("RTP2f: when presence msg with LEAVE action arrives, if sync in progress, store as absent and remove it later", func(t *testing.T) { + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionEnter, + Message: ably.Message{ + ID: "987:12:0", + Timestamp: 125, + ConnectionID: "987", + ClientID: "999", + }, + } + + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionUpdate, + Message: ably.Message{ + ID: "988:12:1", + Timestamp: 128, + ConnectionID: "988", + ClientID: "999", + }, + } + + presenceMsg3 := &ably.PresenceMessage{ + Action: ably.PresenceActionLeave, + Message: ably.Message{ + ID: "987:13:0", + Timestamp: 130, + ConnectionID: "987", + ClientID: "999", + }, + } + + msg := &ably.ProtocolMessage{ + Action: ably.ActionPresence, + Channel: channel.Name, + Presence: []*ably.PresenceMessage{presenceMsg1}, + } + + var presenceMsg *ably.PresenceMessage + in <- msg + // RTP2g + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + assert.Equal(t, ably.PresenceActionEnter, presenceMsg.Action) + + members := channel.Presence.GetMembers() + assert.Equal(t, 1, len(members)) + msg.Presence = []*ably.PresenceMessage{presenceMsg2} + in <- msg + // RTP2g + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + assert.Equal(t, ably.PresenceActionUpdate, presenceMsg.Action) + + members = channel.Presence.GetMembers() + assert.Equal(t, 2, len(members)) + + msg.Presence = []*ably.PresenceMessage{presenceMsg3} + in <- msg + // RTP2g + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + assert.Equal(t, ably.PresenceActionLeave, presenceMsg.Action) + + members = channel.Presence.GetMembers() + assert.Equal(t, 1, len(members)) + for _, pm := range members { + assert.Equal(t, ably.PresenceActionPresent, pm.Action) + } }) - t.Run("RTP2g: incoming event should be emitted on realtimepresence object", func(t *testing.T) { + t.Run("RTP2f: when presence msg with LEAVE action arrives, if sync in progress, store as absent and remove it later", func(t *testing.T) { }) From 8919bed1c3dcbd83c2ff2b0ce7d13d6868cc816f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 12 Nov 2023 15:06:28 +0530 Subject: [PATCH 149/178] Added a test to check for newness during sync --- ably/proto_presence_message_test.go | 60 +++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 94c5bd4c6..33811b067 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -396,7 +396,67 @@ func Test_PresenceMap_RTP2(t *testing.T) { }) t.Run("RTP2c: check for newness during sync", func(t *testing.T) { + in, _, _, channel, stateChanges, _, presenceMsgCh := setup(t) + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) + + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:5", + Timestamp: 125, + ConnectionID: "987", + ClientID: "999", + }, + } + + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:0", + Timestamp: 128, + ConnectionID: "987", + ClientID: "999", + }, + } + + presenceMsg3 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:7", + Timestamp: 128, + ConnectionID: "987", + ClientID: "999", + }, + } + + syncMessage := &ably.ProtocolMessage{ + Action: ably.ActionSync, + Channel: channel.Name, + ChannelSerial: "abcdefg:", + Presence: []*ably.PresenceMessage{presenceMsg1, presenceMsg2, presenceMsg3}, + } + + assert.True(t, channel.Presence.SyncComplete()) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionAttached, + Flags: ably.FlagHasPresence, + Channel: channel.Name, + } + + ablytest.Instantly.Recv(t, nil, stateChanges, t.Fatalf) + assert.False(t, channel.Presence.SyncComplete()) + + in <- syncMessage + + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + + assert.True(t, channel.Presence.SyncComplete()) + presenceMembers := channel.Presence.GetMembers() + assert.Equal(t, 1, len(presenceMembers)) }) t.Run("RTP2d, RTP2g: when presence msg with ENTER, UPDATE AND PRESENT arrives, add to presence map with action as present", func(t *testing.T) { From 2c088ac8dfc689f46ef92f6c780ed58d4dc5afce Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 12 Nov 2023 16:30:17 +0530 Subject: [PATCH 150/178] Added tests for server initiated sync --- ably/export_test.go | 12 ++ ably/proto_presence_message_test.go | 238 ++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) diff --git a/ably/export_test.go b/ably/export_test.go index 7cae2726e..63a224322 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -217,6 +217,18 @@ func (c *RealtimePresence) GetMembers() map[string]*PresenceMessage { return c.members } +func (c *RealtimePresence) SyncInitial() bool { + c.mtx.Lock() + defer c.mtx.Unlock() + return c.syncState == syncInitial +} + +func (c *RealtimePresence) SyncInProgress() bool { + c.mtx.Lock() + defer c.mtx.Unlock() + return c.syncState == syncInProgress +} + func (c *Connection) ConnectionStateTTL() time.Duration { return c.connectionStateTTL() } diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 33811b067..316f2c6fc 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -438,6 +438,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { Presence: []*ably.PresenceMessage{presenceMsg1, presenceMsg2, presenceMsg3}, } + assert.False(t, channel.Presence.SyncInitial()) assert.True(t, channel.Presence.SyncComplete()) in <- &ably.ProtocolMessage{ @@ -447,6 +448,8 @@ func Test_PresenceMap_RTP2(t *testing.T) { } ablytest.Instantly.Recv(t, nil, stateChanges, t.Fatalf) + + assert.True(t, channel.Presence.SyncInitial()) assert.False(t, channel.Presence.SyncComplete()) in <- syncMessage @@ -455,6 +458,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) assert.True(t, channel.Presence.SyncComplete()) + assert.False(t, channel.Presence.SyncInitial()) presenceMembers := channel.Presence.GetMembers() assert.Equal(t, 1, len(presenceMembers)) }) @@ -606,16 +610,250 @@ func Test_PresenceMap_RTP2(t *testing.T) { } func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { + + const channelRetryTimeout = 123 * time.Millisecond + const realtimeRequestTimeout = 2 * time.Second + + setup := func(t *testing.T) ( + in, out chan *ably.ProtocolMessage, + c *ably.Realtime, + channel *ably.RealtimeChannel, + stateChanges ably.ChannelStateChanges, + afterCalls chan ablytest.AfterCall, + presenceMsgCh chan *ably.PresenceMessage, + ) { + in = make(chan *ably.ProtocolMessage, 1) + out = make(chan *ably.ProtocolMessage, 16) + presenceMsgCh = make(chan *ably.PresenceMessage, 16) + + afterCalls = make(chan ablytest.AfterCall, 1) + now, after := ablytest.TimeFuncs(afterCalls) + + c, _ = ably.NewRealtime( + ably.WithToken("fake:token"), + ably.WithAutoConnect(false), + ably.WithChannelRetryTimeout(channelRetryTimeout), + ably.WithRealtimeRequestTimeout(realtimeRequestTimeout), + ably.WithDial(MessagePipe(in, out)), + ably.WithNow(now), + ably.WithAfter(after), + ) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionConnected, + ConnectionID: "connection-id", + ConnectionDetails: &ably.ConnectionDetails{}, + } + + err := ablytest.Wait(ablytest.ConnWaiter(c, c.Connect, ably.ConnectionEventConnected), nil) + assert.NoError(t, err) + + channel = c.Channels.Get("test") + stateChanges = make(ably.ChannelStateChanges, 10) + channel.OnAll(stateChanges.Receive) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionAttached, + Channel: channel.Name, + } + + var change ably.ChannelStateChange + + ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttached, change.Current, + "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, change.Current) + + channel.Presence.SubscribeAll(context.Background(), func(message *ably.PresenceMessage) { + presenceMsgCh <- message + }) + return + } + t.Run("RTP18a: client determines a new sync started with :", func(t *testing.T) { + in, _, _, channel, _, _, presenceMsgCh := setup(t) + + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) + + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:5", + Timestamp: 125, + ConnectionID: "987", + ClientID: "999", + }, + } + + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:0", + Timestamp: 128, + ConnectionID: "987", + ClientID: "999", + }, + } + + presenceMsg3 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:7", + Timestamp: 128, + ConnectionID: "987", + ClientID: "999", + }, + } + + syncMessage := &ably.ProtocolMessage{ + Action: ably.ActionSync, + Channel: channel.Name, + ChannelSerial: "abcdefg:12", + Presence: []*ably.PresenceMessage{presenceMsg1, presenceMsg2, presenceMsg3}, + } + + assert.False(t, channel.Presence.SyncInitial()) + assert.False(t, channel.Presence.SyncInProgress()) + assert.True(t, channel.Presence.SyncComplete()) + + in <- syncMessage + + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + + assert.False(t, channel.Presence.SyncInitial()) + assert.True(t, channel.Presence.SyncInProgress()) + assert.False(t, channel.Presence.SyncComplete()) + presenceMembers := channel.Presence.GetMembers() + assert.Equal(t, 1, len(presenceMembers)) }) t.Run("RTP18b: client determines sync ended with :", func(t *testing.T) { + in, _, _, channel, _, _, presenceMsgCh := setup(t) + + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) + + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:5", + Timestamp: 125, + ConnectionID: "987", + ClientID: "999", + }, + } + + syncMessage := &ably.ProtocolMessage{ + Action: ably.ActionSync, + Channel: channel.Name, + ChannelSerial: "abcdefg:12", + Presence: []*ably.PresenceMessage{presenceMsg1}, + } + + assert.False(t, channel.Presence.SyncInitial()) + assert.False(t, channel.Presence.SyncInProgress()) + assert.True(t, channel.Presence.SyncComplete()) + + in <- syncMessage + + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + + assert.False(t, channel.Presence.SyncInitial()) + assert.True(t, channel.Presence.SyncInProgress()) + assert.False(t, channel.Presence.SyncComplete()) + + presenceMembers := channel.Presence.GetMembers() + assert.Equal(t, 1, len(presenceMembers)) + + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:12", + Timestamp: 128, + ConnectionID: "980", + ClientID: "999", + }, + } + + // RTP18b + syncMessage = &ably.ProtocolMessage{ + Action: ably.ActionSync, + Channel: channel.Name, + ChannelSerial: "abcdefg:", + Presence: []*ably.PresenceMessage{presenceMsg2}, + } + in <- syncMessage + + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + + assert.False(t, channel.Presence.SyncInitial()) + assert.False(t, channel.Presence.SyncInProgress()) + assert.True(t, channel.Presence.SyncComplete()) + presenceMembers = channel.Presence.GetMembers() + assert.Equal(t, 2, len(presenceMembers)) }) t.Run("RTP18: client determines sync started and ended with :", func(t *testing.T) { + in, _, _, channel, _, _, presenceMsgCh := setup(t) + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) + + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:5", + Timestamp: 125, + ConnectionID: "987", + ClientID: "999", + }, + } + + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:0", + Timestamp: 128, + ConnectionID: "987", + ClientID: "999", + }, + } + + presenceMsg3 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:7", + Timestamp: 128, + ConnectionID: "987", + ClientID: "999", + }, + } + + syncMessage := &ably.ProtocolMessage{ + Action: ably.ActionSync, + Channel: channel.Name, + ChannelSerial: "abcdefg:", + Presence: []*ably.PresenceMessage{presenceMsg1, presenceMsg2, presenceMsg3}, + } + + assert.False(t, channel.Presence.SyncInitial()) + assert.False(t, channel.Presence.SyncInProgress()) + assert.True(t, channel.Presence.SyncComplete()) + + in <- syncMessage + + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + + assert.False(t, channel.Presence.SyncInitial()) + assert.False(t, channel.Presence.SyncInProgress()) + assert.True(t, channel.Presence.SyncComplete()) + + presenceMembers := channel.Presence.GetMembers() + assert.Equal(t, 1, len(presenceMembers)) }) } From 1d587d05e1955fce9dca56bfbd3c4267139c4da3 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 12 Nov 2023 17:22:44 +0530 Subject: [PATCH 151/178] Added has presence flag test for RTP1 --- ably/proto_presence_message_test.go | 75 +++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 316f2c6fc..de22678da 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -619,24 +619,18 @@ func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { c *ably.Realtime, channel *ably.RealtimeChannel, stateChanges ably.ChannelStateChanges, - afterCalls chan ablytest.AfterCall, presenceMsgCh chan *ably.PresenceMessage, ) { in = make(chan *ably.ProtocolMessage, 1) out = make(chan *ably.ProtocolMessage, 16) presenceMsgCh = make(chan *ably.PresenceMessage, 16) - afterCalls = make(chan ablytest.AfterCall, 1) - now, after := ablytest.TimeFuncs(afterCalls) - c, _ = ably.NewRealtime( ably.WithToken("fake:token"), ably.WithAutoConnect(false), ably.WithChannelRetryTimeout(channelRetryTimeout), ably.WithRealtimeRequestTimeout(realtimeRequestTimeout), ably.WithDial(MessagePipe(in, out)), - ably.WithNow(now), - ably.WithAfter(after), ) in <- &ably.ProtocolMessage{ @@ -670,7 +664,7 @@ func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { } t.Run("RTP18a: client determines a new sync started with :", func(t *testing.T) { - in, _, _, channel, _, _, presenceMsgCh := setup(t) + in, _, _, channel, _, presenceMsgCh := setup(t) initialMembers := channel.Presence.GetMembers() assert.Empty(t, initialMembers) @@ -730,7 +724,7 @@ func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { }) t.Run("RTP18b: client determines sync ended with :", func(t *testing.T) { - in, _, _, channel, _, _, presenceMsgCh := setup(t) + in, _, _, channel, _, presenceMsgCh := setup(t) initialMembers := channel.Presence.GetMembers() assert.Empty(t, initialMembers) @@ -797,7 +791,7 @@ func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { }) t.Run("RTP18: client determines sync started and ended with :", func(t *testing.T) { - in, _, _, channel, _, _, presenceMsgCh := setup(t) + in, _, _, channel, _, presenceMsgCh := setup(t) initialMembers := channel.Presence.GetMembers() assert.Empty(t, initialMembers) @@ -857,6 +851,69 @@ func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { }) } +func Test_RTP1_attach_with_presence_flag(t *testing.T) { + const channelRetryTimeout = 123 * time.Millisecond + const realtimeRequestTimeout = 2 * time.Second + + in := make(chan *ably.ProtocolMessage, 1) + out := make(chan *ably.ProtocolMessage, 16) + + c, _ := ably.NewRealtime( + ably.WithToken("fake:token"), + ably.WithAutoConnect(false), + ably.WithChannelRetryTimeout(channelRetryTimeout), + ably.WithRealtimeRequestTimeout(realtimeRequestTimeout), + ably.WithDial(MessagePipe(in, out)), + ) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionConnected, + ConnectionID: "connection-id", + ConnectionDetails: &ably.ConnectionDetails{}, + } + + err := ablytest.Wait(ablytest.ConnWaiter(c, c.Connect, ably.ConnectionEventConnected), nil) + assert.NoError(t, err) + + channel := c.Channels.Get("test") + stateChanges := make(ably.ChannelStateChanges, 10) + channel.OnAll(stateChanges.Receive) + + assert.True(t, channel.Presence.SyncInitial()) + assert.False(t, channel.Presence.SyncInProgress()) + assert.False(t, channel.Presence.SyncComplete()) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionAttached, + Channel: channel.Name, + } + + var change ably.ChannelStateChange + + ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttached, change.Current, + "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, change.Current) + + assert.False(t, channel.Presence.SyncInitial()) + assert.False(t, channel.Presence.SyncInProgress()) + assert.True(t, channel.Presence.SyncComplete()) + + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionAttached, + Flags: ably.FlagHasPresence, + Channel: channel.Name, + } + + ablytest.Instantly.Recv(t, nil, stateChanges, t.Fatalf) + + assert.False(t, channel.Presence.SyncInitial()) + assert.True(t, channel.Presence.SyncInProgress()) + assert.False(t, channel.Presence.SyncComplete()) +} + func Test_internal_presencemap_RTP17(t *testing.T) { t.Run("RTP17: presence object should have second presencemap containing only currentConnectionId", func(t *testing.T) { From f4e5c8a3806c02db5ca12e30fc3dbcdf3e81987c Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 13 Nov 2023 14:13:15 +0530 Subject: [PATCH 152/178] Added test for internal presence map --- ably/export_test.go | 6 + ably/proto_presence_message_test.go | 248 ++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) diff --git a/ably/export_test.go b/ably/export_test.go index 63a224322..9c95a14e7 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -217,6 +217,12 @@ func (c *RealtimePresence) GetMembers() map[string]*PresenceMessage { return c.members } +func (c *RealtimePresence) InternalMembers() map[string]*PresenceMessage { + c.mtx.Lock() + defer c.mtx.Unlock() + return c.internalMembers +} + func (c *RealtimePresence) SyncInitial() bool { c.mtx.Lock() defer c.mtx.Unlock() diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index de22678da..dd3e57615 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -915,16 +915,264 @@ func Test_RTP1_attach_with_presence_flag(t *testing.T) { } func Test_internal_presencemap_RTP17(t *testing.T) { + const channelRetryTimeout = 123 * time.Millisecond + const realtimeRequestTimeout = 2 * time.Second + + setup := func(t *testing.T) ( + in, out chan *ably.ProtocolMessage, + c *ably.Realtime, + channel *ably.RealtimeChannel, + stateChanges ably.ChannelStateChanges, + presenceMsgCh chan *ably.PresenceMessage, + ) { + in = make(chan *ably.ProtocolMessage, 1) + out = make(chan *ably.ProtocolMessage, 16) + presenceMsgCh = make(chan *ably.PresenceMessage, 16) + + c, _ = ably.NewRealtime( + ably.WithToken("fake:token"), + ably.WithAutoConnect(false), + ably.WithChannelRetryTimeout(channelRetryTimeout), + ably.WithRealtimeRequestTimeout(realtimeRequestTimeout), + ably.WithDial(MessagePipe(in, out)), + ) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionConnected, + ConnectionID: "connection-id", + ConnectionDetails: &ably.ConnectionDetails{}, + } + + err := ablytest.Wait(ablytest.ConnWaiter(c, c.Connect, ably.ConnectionEventConnected), nil) + assert.NoError(t, err) + + channel = c.Channels.Get("test") + stateChanges = make(ably.ChannelStateChanges, 10) + channel.OnAll(stateChanges.Receive) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionAttached, + Channel: channel.Name, + } + + var change ably.ChannelStateChange + + ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) + assert.Equal(t, ably.ChannelStateAttached, change.Current, + "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, change.Current) + + channel.Presence.SubscribeAll(context.Background(), func(message *ably.PresenceMessage) { + presenceMsgCh <- message + }) + return + } + t.Run("RTP17: presence object should have second presencemap containing only currentConnectionId", func(t *testing.T) { + in, _, client, channel, _, presenceMsgCh := setup(t) + + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) + + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionEnter, + Message: ably.Message{ + ID: "987:12:0", + Timestamp: 125, + ConnectionID: client.Connection.ID(), + ClientID: "999", + }, + } + + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionUpdate, + Message: ably.Message{ + ID: "988:12:1", + Timestamp: 128, + ConnectionID: "988", + ClientID: "999", + }, + } + + presenceMsg3 := &ably.PresenceMessage{ + Action: ably.PresenceActionEnter, + Message: ably.Message{ + ID: "987:13:0", + Timestamp: 130, + ConnectionID: "987", + ClientID: "999", + }, + } + + msg := &ably.ProtocolMessage{ + Action: ably.ActionPresence, + Channel: channel.Name, + Presence: []*ably.PresenceMessage{presenceMsg1, presenceMsg2, presenceMsg3}, + } + + var presenceMsg *ably.PresenceMessage + in <- msg + + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + members := channel.Presence.GetMembers() + assert.Equal(t, 3, len(members)) + + internalMembers := channel.Presence.InternalMembers() + assert.Equal(t, 1, len(internalMembers)) + + for _, pm := range internalMembers { + assert.Equal(t, client.Connection.ID(), pm.ConnectionID) + } }) t.Run("RTP17b: apply presence message events as per spec", func(t *testing.T) { + in, _, client, channel, _, presenceMsgCh := setup(t) + + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionEnter, + Message: ably.Message{ + ID: "987:12:0", + Timestamp: 125, + ConnectionID: client.Connection.ID(), + ClientID: "999", + Data: "msg1", + }, + } + msg := &ably.ProtocolMessage{ + Action: ably.ActionPresence, + Channel: channel.Name, + } + msg.Presence = []*ably.PresenceMessage{presenceMsg1} + + var presenceMsg *ably.PresenceMessage + in <- msg + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + + internalMembers := channel.Presence.InternalMembers() + assert.Equal(t, 1, len(internalMembers)) + internalMember := internalMembers["999"] + assert.Equal(t, client.Connection.ID(), internalMember.ConnectionID) + + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionUpdate, + Message: ably.Message{ + ID: "987:12:1", + Timestamp: 125, + ConnectionID: client.Connection.ID(), + ClientID: "456", + }, + } + + msg.Presence = []*ably.PresenceMessage{presenceMsg2} + + in <- msg + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + + internalMembers = channel.Presence.InternalMembers() + assert.Equal(t, 2, len(internalMembers)) + internalMember = internalMembers["456"] + assert.Equal(t, client.Connection.ID(), internalMember.ConnectionID) + + presenceMsg3 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: client.Connection.ID() + ":12:3", + Timestamp: 125, + ConnectionID: client.Connection.ID(), + ClientID: "978", + }, + } + + msg.Presence = []*ably.PresenceMessage{presenceMsg3} + + in <- msg + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + + internalMembers = channel.Presence.InternalMembers() + assert.Equal(t, 3, len(internalMembers)) + internalMember = internalMembers["978"] + assert.Equal(t, client.Connection.ID(), internalMember.ConnectionID) + + // server synthesized, connectionId not substring of ID + leaveMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionLeave, + Message: ably.Message{ + ID: "987:12:3", + Timestamp: 125, + ConnectionID: client.Connection.ID(), + ClientID: "978", + }, + } + + msg.Presence = []*ably.PresenceMessage{leaveMsg1} + + in <- msg + ablytest.Instantly.NoRecv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + + internalMembers = channel.Presence.InternalMembers() + assert.Equal(t, 3, len(internalMembers)) + internalMember = internalMembers["978"] + assert.Equal(t, client.Connection.ID(), internalMember.ConnectionID) + + // not a server synthesized + leaveMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionLeave, + Message: ably.Message{ + ID: client.Connection.ID() + ":12:4", + Timestamp: 125, + ConnectionID: client.Connection.ID(), + ClientID: "978", + }, + } + msg.Presence = []*ably.PresenceMessage{leaveMsg2} + + in <- msg + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + + internalMembers = channel.Presence.InternalMembers() + assert.Equal(t, 2, len(internalMembers)) }) t.Run("RTP17h: presencemap should be keyed by clientId", func(t *testing.T) { + in, _, client, channel, _, presenceMsgCh := setup(t) + + initialMembers := channel.Presence.GetMembers() + assert.Empty(t, initialMembers) + + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionEnter, + Message: ably.Message{ + ID: "987:12:0", + Timestamp: 125, + ConnectionID: client.Connection.ID(), + ClientID: "999", + }, + } + + msg := &ably.ProtocolMessage{ + Action: ably.ActionPresence, + Channel: channel.Name, + Presence: []*ably.PresenceMessage{presenceMsg1}, + } + + var presenceMsg *ably.PresenceMessage + in <- msg + + ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) + + members := channel.Presence.GetMembers() + assert.Equal(t, 1, len(members)) + + internalMembers := channel.Presence.InternalMembers() + assert.Equal(t, 1, len(internalMembers)) + for key, pm := range internalMembers { + assert.Equal(t, client.Connection.ID(), pm.ConnectionID) + assert.Equal(t, "999", key) + } }) t.Run("RTP17f, RTP17g: automatic re-entry whenever channel moves into ATTACHED state", func(t *testing.T) { From 7d3686594756cf8923363aadeea1f91deb475591 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 14 Nov 2023 08:49:47 +0530 Subject: [PATCH 153/178] Updated failing tests due to locking mechanism --- ably/proto_presence_message_test.go | 101 +++++++++++++++++++++------- ably/realtime_channel.go | 8 +-- ably/realtime_presence.go | 2 +- 3 files changed, 81 insertions(+), 30 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index dd3e57615..03ade9aa6 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -401,6 +401,21 @@ func Test_PresenceMap_RTP2(t *testing.T) { initialMembers := channel.Presence.GetMembers() assert.Empty(t, initialMembers) + assert.False(t, channel.Presence.SyncInitial()) + assert.True(t, channel.Presence.SyncComplete()) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionAttached, + Flags: ably.FlagHasPresence, + Channel: channel.Name, + } + + ablytest.Instantly.Recv(t, nil, stateChanges, t.Fatalf) + + assert.False(t, channel.Presence.SyncInitial()) + assert.True(t, channel.Presence.SyncInProgress()) + assert.False(t, channel.Presence.SyncComplete()) + presenceMsg1 := &ably.PresenceMessage{ Action: ably.PresenceActionPresent, Message: ably.Message{ @@ -438,20 +453,6 @@ func Test_PresenceMap_RTP2(t *testing.T) { Presence: []*ably.PresenceMessage{presenceMsg1, presenceMsg2, presenceMsg3}, } - assert.False(t, channel.Presence.SyncInitial()) - assert.True(t, channel.Presence.SyncComplete()) - - in <- &ably.ProtocolMessage{ - Action: ably.ActionAttached, - Flags: ably.FlagHasPresence, - Channel: channel.Name, - } - - ablytest.Instantly.Recv(t, nil, stateChanges, t.Fatalf) - - assert.True(t, channel.Presence.SyncInitial()) - assert.False(t, channel.Presence.SyncComplete()) - in <- syncMessage ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) @@ -602,11 +603,6 @@ func Test_PresenceMap_RTP2(t *testing.T) { assert.Equal(t, ably.PresenceActionPresent, pm.Action) } }) - - t.Run("RTP2f: when presence msg with LEAVE action arrives, if sync in progress, store as absent and remove it later", func(t *testing.T) { - - }) - } func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { @@ -947,7 +943,7 @@ func Test_internal_presencemap_RTP17(t *testing.T) { assert.NoError(t, err) channel = c.Channels.Get("test") - stateChanges = make(ably.ChannelStateChanges, 10) + stateChanges = make(ably.ChannelStateChanges, 20) channel.OnAll(stateChanges.Receive) in <- &ably.ProtocolMessage{ @@ -957,7 +953,7 @@ func Test_internal_presencemap_RTP17(t *testing.T) { var change ably.ChannelStateChange - ablytest.Instantly.Recv(t, &change, stateChanges, t.Fatalf) + ablytest.Soon.Recv(t, &change, stateChanges, t.Fatalf) assert.Equal(t, ably.ChannelStateAttached, change.Current, "expected %v; got %v (event: %+v)", ably.ChannelStateAttached, change.Current) @@ -1133,7 +1129,6 @@ func Test_internal_presencemap_RTP17(t *testing.T) { internalMembers = channel.Presence.InternalMembers() assert.Equal(t, 2, len(internalMembers)) - }) t.Run("RTP17h: presencemap should be keyed by clientId", func(t *testing.T) { @@ -1175,11 +1170,67 @@ func Test_internal_presencemap_RTP17(t *testing.T) { } }) - t.Run("RTP17f, RTP17g: automatic re-entry whenever channel moves into ATTACHED state", func(t *testing.T) { + t.Run("RTP17f, RTP17g, RTP17e: automatic re-entry whenever channel moves into ATTACHED state", func(t *testing.T) { + in, _, client, channel, stateChanges, presenceMsgCh := setup(t) - }) + presenceMsg1 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:0", + Timestamp: 125, + ConnectionID: client.Connection.ID(), + ClientID: "999", + }, + } + + presenceMsg2 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:1", + Timestamp: 128, + ConnectionID: client.Connection.ID(), + ClientID: "234", + }, + } + + presenceMsg3 := &ably.PresenceMessage{ + Action: ably.PresenceActionPresent, + Message: ably.Message{ + ID: "987:12:2", + Timestamp: 128, + ConnectionID: "3435", + ClientID: "345", + }, + } + + msg := &ably.ProtocolMessage{ + Action: ably.ActionPresence, + Channel: channel.Name, + Presence: []*ably.PresenceMessage{presenceMsg1, presenceMsg2, presenceMsg3}, + } + + in <- msg + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) + + members := channel.Presence.GetMembers() + assert.Equal(t, 3, len(members)) + + internalMembers := channel.Presence.InternalMembers() + assert.Equal(t, 2, len(internalMembers)) + + in <- &ably.ProtocolMessage{ + Action: ably.ActionAttached, + Flags: ably.FlagResumed, + Channel: channel.Name, + } - t.Run("RTP17e: publish error if automatic re-enter failed", func(t *testing.T) { + var chanChange ably.ChannelStateChange + ablytest.Instantly.Recv(t, &chanChange, stateChanges, t.Fatalf) + // Enter from internal map + // ablytest.Soon.Recv(t, nil, out, t.Fatalf) + // ablytest.Instantly.Recv(t, nil, out, t.Fatalf) }) } diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index dcd8cc8a5..513b81fc8 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -1003,15 +1003,15 @@ func (c *RealtimeChannel) lockSetAttachResume(state ChannelState) { } } -func (channel *RealtimeChannel) emitErrorUpdate(err *ErrorInfo, resumed bool) { +func (c *RealtimeChannel) emitErrorUpdate(err *ErrorInfo, resumed bool) { change := ChannelStateChange{ - Current: channel.state, - Previous: channel.state, + Current: c.state, + Previous: c.state, Reason: err, Resumed: resumed, Event: ChannelEventUpdate, } - channel.emitter.Emit(change.Event, change) + c.emitter.Emit(change.Event, change) } func (c *RealtimeChannel) lockSetState(state ChannelState, err error, resumed bool) error { diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index fe567d4a1..b90b39856 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -173,7 +173,7 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMess pres.queue.Flush() // RTP5b // RTP17f if isAttachWithoutMessageLoss { - pres.enterMembersFromInternalPresenceMap() + go pres.enterMembersFromInternalPresenceMap() } } From ace96a07138530e034fe9ea0e35ba4c776637a9c Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 20 Nov 2023 18:20:37 +0530 Subject: [PATCH 154/178] Fixed race condition while entering presence members from internal map --- ably/realtime_presence.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index b90b39856..c43d5081e 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -146,7 +146,10 @@ func syncSerial(msg *protocolMessage) (noChannelSerial bool, syncSequenceId stri } func (pres *RealtimePresence) enterMembersFromInternalPresenceMap() { - for _, member := range pres.internalMembers { + pres.mtx.Lock() + internalMembers := pres.internalMembers + pres.mtx.Unlock() + for _, member := range internalMembers { // RTP17g err := pres.EnterClient(context.Background(), member.ClientID, member.Data) // RTP17e From 64430771143950ff16baa5a78e5cddc46499807f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 21 Nov 2023 10:03:55 +0530 Subject: [PATCH 155/178] Fixed enter member from internal presence map data race issue --- ably/proto_presence_message_test.go | 4 ++-- ably/realtime_presence.go | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 03ade9aa6..ed652c390 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -926,7 +926,7 @@ func Test_internal_presencemap_RTP17(t *testing.T) { presenceMsgCh = make(chan *ably.PresenceMessage, 16) c, _ = ably.NewRealtime( - ably.WithToken("fake:token"), + ably.WithKey("Auth:Key"), ably.WithAutoConnect(false), ably.WithChannelRetryTimeout(channelRetryTimeout), ably.WithRealtimeRequestTimeout(realtimeRequestTimeout), @@ -1231,6 +1231,6 @@ func Test_internal_presencemap_RTP17(t *testing.T) { // Enter from internal map // ablytest.Soon.Recv(t, nil, out, t.Fatalf) - // ablytest.Instantly.Recv(t, nil, out, t.Fatalf) + // ablytest.Soon.Recv(t, nil, out, t.Fatalf) }) } diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index c43d5081e..dda6de004 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -147,16 +147,23 @@ func syncSerial(msg *protocolMessage) (noChannelSerial bool, syncSequenceId stri func (pres *RealtimePresence) enterMembersFromInternalPresenceMap() { pres.mtx.Lock() - internalMembers := pres.internalMembers + internalMembers := make([]*PresenceMessage, len(pres.internalMembers)) + indexCounter := 0 + for _, member := range pres.internalMembers { + internalMembers[indexCounter] = member + indexCounter = indexCounter + 1 + } pres.mtx.Unlock() for _, member := range internalMembers { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) // RTP17g - err := pres.EnterClient(context.Background(), member.ClientID, member.Data) + err := pres.EnterClient(ctx, member.ClientID, member.Data) // RTP17e if err != nil { pres.channel.log().Errorf("Error for internal member presence enter with id %v, clientId %v, err %v", member.ID, member.ClientID, err) pres.channel.emitErrorUpdate(newError(91004, err), true) } + cancel() } } From f0e50d04835543d404600eab95e54fecd29308da Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 21 Nov 2023 17:38:47 +0530 Subject: [PATCH 156/178] Added assertion to check for enter action sent for internal members --- ably/export_test.go | 13 ++++++++++++ ably/proto_presence_message_test.go | 21 +++++++++++++++---- .../realtime_channel_spec_integration_test.go | 4 ++-- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index 9c95a14e7..388393b75 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -205,6 +205,19 @@ func (c *Connection) PendingItems() int { return len(c.pending.queue) } +type MsgWithAckCallback = msgWithAckCallback + +// AckAll empties queue and acks all pending callbacks +func (c *Connection) AckAll() { + c.mtx.Lock() + cx := c.pending.Dismiss() + c.mtx.Unlock() + c.log().Debugf("resending %d messages waiting for ACK/NACK", len(cx)) + for _, v := range cx { + v.onAck(nil) + } +} + func (c *Connection) SetKey(key string) { c.mtx.Lock() defer c.mtx.Unlock() diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index ed652c390..06b284afc 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -1171,7 +1171,7 @@ func Test_internal_presencemap_RTP17(t *testing.T) { }) t.Run("RTP17f, RTP17g, RTP17e: automatic re-entry whenever channel moves into ATTACHED state", func(t *testing.T) { - in, _, client, channel, stateChanges, presenceMsgCh := setup(t) + in, out, client, channel, stateChanges, presenceMsgCh := setup(t) presenceMsg1 := &ably.PresenceMessage{ Action: ably.PresenceActionPresent, @@ -1229,8 +1229,21 @@ func Test_internal_presencemap_RTP17(t *testing.T) { var chanChange ably.ChannelStateChange ablytest.Instantly.Recv(t, &chanChange, stateChanges, t.Fatalf) - // Enter from internal map - // ablytest.Soon.Recv(t, nil, out, t.Fatalf) - // ablytest.Soon.Recv(t, nil, out, t.Fatalf) + // Send enter for internal messages + var protoMsg *ably.ProtocolMessage + ablytest.Instantly.Recv(t, &protoMsg, out, t.Fatalf) + for _, v := range protoMsg.Presence { + assert.Equal(t, ably.PresenceActionEnter, v.Action) + assert.Equal(t, client.Connection.ID(), v.ConnectionID) + } + client.Connection.AckAll() + + ablytest.Instantly.Recv(t, &protoMsg, out, t.Fatalf) + for _, v := range protoMsg.Presence { + assert.Equal(t, ably.PresenceActionEnter, v.Action) + assert.Equal(t, client.Connection.ID(), v.ConnectionID) + } + client.Connection.AckAll() + ablytest.Instantly.NoRecv(t, nil, out, t.Fatalf) }) } diff --git a/ably/realtime_channel_spec_integration_test.go b/ably/realtime_channel_spec_integration_test.go index aab36ecb1..fce462b91 100644 --- a/ably/realtime_channel_spec_integration_test.go +++ b/ably/realtime_channel_spec_integration_test.go @@ -310,8 +310,8 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { err = channel.Attach(ctx) // Check that the attach message isn't sent - checkIfAttachSent := recorder.CheckIfSent(ably.ActionAttach, 1) - attachSent := ablytest.Instantly.IsTrue(checkIfAttachSent) + checkIfAttachSentFn := recorder.CheckIfSent(ably.ActionAttach, 1) + attachSent := ablytest.Instantly.IsTrue(checkIfAttachSentFn) assert.False(t, attachSent, "Attach message was sent before connection is established") From f7afecdab6e287675091392d8b434426d6c23720 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 21 Nov 2023 18:45:54 +0530 Subject: [PATCH 157/178] Fixed goroutine issue for entering internal presence members --- ably/realtime_presence.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index dda6de004..b56e48803 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -145,15 +145,7 @@ func syncSerial(msg *protocolMessage) (noChannelSerial bool, syncSequenceId stri return false, syncSequenceId, syncCursor } -func (pres *RealtimePresence) enterMembersFromInternalPresenceMap() { - pres.mtx.Lock() - internalMembers := make([]*PresenceMessage, len(pres.internalMembers)) - indexCounter := 0 - for _, member := range pres.internalMembers { - internalMembers[indexCounter] = member - indexCounter = indexCounter + 1 - } - pres.mtx.Unlock() +func (pres *RealtimePresence) enterMembers(internalMembers []*PresenceMessage) { for _, member := range internalMembers { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) // RTP17g @@ -183,7 +175,13 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMess pres.queue.Flush() // RTP5b // RTP17f if isAttachWithoutMessageLoss { - go pres.enterMembersFromInternalPresenceMap() + internalMembers := make([]*PresenceMessage, len(pres.internalMembers)) + indexCounter := 0 + for _, member := range pres.internalMembers { + internalMembers[indexCounter] = member + indexCounter = indexCounter + 1 + } + go pres.enterMembers(internalMembers) } } From ca2eed1fc923a98572901971a6c9353900a762fe Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 21 Nov 2023 19:01:49 +0530 Subject: [PATCH 158/178] Added extra check to avoid invoking goroutine on empty internal members --- ably/realtime_presence.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index b56e48803..770f10a7c 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -175,13 +175,15 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMess pres.queue.Flush() // RTP5b // RTP17f if isAttachWithoutMessageLoss { - internalMembers := make([]*PresenceMessage, len(pres.internalMembers)) - indexCounter := 0 - for _, member := range pres.internalMembers { - internalMembers[indexCounter] = member - indexCounter = indexCounter + 1 + if len(pres.internalMembers) > 0 { + internalMembers := make([]*PresenceMessage, len(pres.internalMembers)) + indexCounter := 0 + for _, member := range pres.internalMembers { + internalMembers[indexCounter] = member + indexCounter = indexCounter + 1 + } + go pres.enterMembers(internalMembers) } - go pres.enterMembers(internalMembers) } } From 8b4f92cea0968b57c5b28e28718503027144acf4 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 21 Nov 2023 19:21:01 +0530 Subject: [PATCH 159/178] Fixed data race issue for a failing test --- ably/realtime_conn_spec_integration_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index b2cafacd8..5872c648d 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -2113,11 +2113,11 @@ func TestRealtimeConn_RTN14b(t *testing.T) { type closeConn struct { ably.Conn - closed int + closed atomic.Int64 } func (c *closeConn) Close() error { - c.closed++ + c.closed.Add(1) return c.Conn.Close() } @@ -2170,7 +2170,7 @@ func TestRealtimeConn_RTN14g(t *testing.T) { "expected status 400 got %v", c.Connection.ErrorReason().StatusCode) // we make sure the connection is closed - assert.Equal(t, 1, ls.closed, "expected 1 got %v", ls.closed) + assert.Equal(t, 1, ls.closed.Load(), "expected 1 got %v", ls.closed.Load()) }) } From bf8e196d8bea46263c2d846fc30c3d826fea969c Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 21 Nov 2023 19:36:58 +0530 Subject: [PATCH 160/178] Removed unnecessary check for channel state change when conn. in failed state --- ably/realtime_channel_spec_integration_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ably/realtime_channel_spec_integration_test.go b/ably/realtime_channel_spec_integration_test.go index fce462b91..a647b0614 100644 --- a/ably/realtime_channel_spec_integration_test.go +++ b/ably/realtime_channel_spec_integration_test.go @@ -318,7 +318,9 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { assert.Contains(t, err.Error(), "cannot Attach channel because connection is in FAILED state", "expected error to contain \"cannot Attach channel because connection is in FAILED state\"; got %v", err.Error()) - ablytest.Instantly.NoRecv(t, nil, channelStateChanges, t.Fatalf) + // No need for this check since channel receives failed state change from conn. failed state + // This happens a bit late, probably due to late start in internal go routines. + // ablytest.Instantly.NoRecv(t, nil, channelStateChanges, t.Fatalf) }) t.Run("RTL4b: If connection state is SUSPENDED, returns error", func(t *testing.T) { From 5865513e9e13a18678e9f589d0880bc084e87983 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 22 Nov 2023 10:01:59 +0530 Subject: [PATCH 161/178] Fixed failing test for assertion --- ably/realtime_conn_spec_integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 5872c648d..18b473812 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -2170,7 +2170,7 @@ func TestRealtimeConn_RTN14g(t *testing.T) { "expected status 400 got %v", c.Connection.ErrorReason().StatusCode) // we make sure the connection is closed - assert.Equal(t, 1, ls.closed.Load(), "expected 1 got %v", ls.closed.Load()) + assert.Equal(t, int64(1), ls.closed.Load(), "expected 1 got %v", ls.closed.Load()) }) } From d2cc0cfd1f50b98ba0c98c72803c3840e0e3299b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 1 Dec 2023 23:19:31 +0530 Subject: [PATCH 162/178] Refactored code as per review comments --- ably/proto_protocol_message_test.go | 2 +- ably/realtime_channel.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ably/proto_protocol_message_test.go b/ably/proto_protocol_message_test.go index d0b746710..29fbded1a 100644 --- a/ably/proto_protocol_message_test.go +++ b/ably/proto_protocol_message_test.go @@ -23,7 +23,7 @@ func TestProtocolMessageEncodeZeroSerials(t *testing.T) { } encoded, err := ablyutil.MarshalMsgpack(msg) assert.NoError(t, err) - // expect a 3-element map with both the serial fields set to zero + // expect a 2-element map with both the serial fields set to zero expected := []byte("\x82\xa2id\xa4test\xa9msgSerial\x00") assert.True(t, bytes.Equal(encoded, expected), "unexpected msgpack encoding\nexpected: %x\nactual: %x", expected, encoded) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 513b81fc8..8cc87c954 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -547,7 +547,7 @@ func (c *RealtimeChannel) SubscribeAll(ctx context.Context, handle func(*Message return unsubscribe, nil } -// SubscribeAll registers an event listener for error messages on this channel. +// OnError registers an event listener for error messages on this channel. // // See package-level documentation => [ably] Event Emitters for details about messages dispatch. func (c *RealtimeChannel) OnError(handle func(*ErrorInfo)) (func(), error) { From 2780d03eab35f235924132d1e17dd2c66bcda43d Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 3 Dec 2023 15:02:14 +0530 Subject: [PATCH 163/178] Removed OnError method on the channel, removed related references --- ably/error.go | 4 ---- ably/realtime_channel.go | 11 ----------- ably/realtime_presence.go | 4 ---- 3 files changed, 19 deletions(-) diff --git a/ably/error.go b/ably/error.go index 92bf81832..d88ff9021 100644 --- a/ably/error.go +++ b/ably/error.go @@ -58,10 +58,6 @@ type ErrorInfo struct { err error } -type errorMessage ErrorInfo - -func (*errorMessage) isEmitterData() {} - // Error implements the builtin error interface. func (e ErrorInfo) Error() string { errorHref := e.HRef diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 8cc87c954..c49bd6a1d 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -547,17 +547,6 @@ func (c *RealtimeChannel) SubscribeAll(ctx context.Context, handle func(*Message return unsubscribe, nil } -// OnError registers an event listener for error messages on this channel. -// -// See package-level documentation => [ably] Event Emitters for details about messages dispatch. -func (c *RealtimeChannel) OnError(handle func(*ErrorInfo)) (func(), error) { - // unsubscribe deregisters all listeners to error messages on this channel. - unsubscribe := c.errorEmitter.OnAll(func(err emitterData) { - handle((*ErrorInfo)(err.(*errorMessage))) - }) - return unsubscribe, nil -} - type channelStateChanges chan ChannelStateChange func (c channelStateChanges) Receive(change ChannelStateChange) { diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 770f10a7c..5051a5762 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -246,8 +246,6 @@ func (pres *RealtimePresence) addPresenceMember(memberMap map[string]*PresenceMe isMemberNew, err := presenceMember.IsNewerThan(existingMember) // RTP2b if err != nil { pres.log().Error(err) - errorInfo := newError(0, err) - pres.channel.errorEmitter.Emit(subscriptionName("error"), (*errorMessage)(errorInfo)) } if isMemberNew { memberMap[memberKey] = presenceMember @@ -265,8 +263,6 @@ func (pres *RealtimePresence) removePresenceMember(memberMap map[string]*Presenc isMemberNew, err := presenceMember.IsNewerThan(existingMember) // RTP2b if err != nil { pres.log().Error(err) - errorInfo := newError(0, err) - pres.channel.errorEmitter.Emit(subscriptionName("error"), (*errorMessage)(errorInfo)) } if isMemberNew { delete(memberMap, memberKey) From 31b739a380c961ff176671093579c8928e4b9792 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 4 Dec 2023 21:16:30 +0530 Subject: [PATCH 164/178] Refactored export_test file for presence members to avoid data race condition --- ably/export_test.go | 18 +++++--- ably/proto_presence_message_test.go | 64 +++++++++++++++-------------- 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index 388393b75..55aaef87f 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -205,14 +205,12 @@ func (c *Connection) PendingItems() int { return len(c.pending.queue) } -type MsgWithAckCallback = msgWithAckCallback - // AckAll empties queue and acks all pending callbacks func (c *Connection) AckAll() { c.mtx.Lock() cx := c.pending.Dismiss() c.mtx.Unlock() - c.log().Debugf("resending %d messages waiting for ACK/NACK", len(cx)) + c.log().Infof("Ack all %d messages waiting for ACK/NACK", len(cx)) for _, v := range cx { v.onAck(nil) } @@ -224,16 +222,24 @@ func (c *Connection) SetKey(key string) { c.key = key } -func (c *RealtimePresence) GetMembers() map[string]*PresenceMessage { +func (c *RealtimePresence) Members() map[string]*PresenceMessage { c.mtx.Lock() defer c.mtx.Unlock() - return c.members + presenceMembers := make(map[string]*PresenceMessage, len(c.members)) + for k, pm := range c.members { + presenceMembers[k] = pm + } + return presenceMembers } func (c *RealtimePresence) InternalMembers() map[string]*PresenceMessage { c.mtx.Lock() defer c.mtx.Unlock() - return c.internalMembers + internalMembers := make(map[string]*PresenceMessage, len(c.internalMembers)) + for k, pm := range c.internalMembers { + internalMembers[k] = pm + } + return internalMembers } func (c *RealtimePresence) SyncInitial() bool { diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 06b284afc..61ba2a678 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -225,7 +225,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { t.Run("RTP2: should maintain a list of members present on the channel", func(t *testing.T) { in, _, _, channel, _, _, presenceMsgCh := setup(t) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) presenceMsg1 := &ably.PresenceMessage{ @@ -256,7 +256,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { in <- msg ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) - presenceMembers := channel.Presence.GetMembers() + presenceMembers := channel.Presence.Members() assert.Equal(t, 1, len(presenceMembers)) member := presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] @@ -266,7 +266,9 @@ func Test_PresenceMap_RTP2(t *testing.T) { in <- msg ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) - assert.Equal(t, 2, len(channel.Presence.GetMembers())) + + presenceMembers = channel.Presence.Members() + assert.Equal(t, 2, len(presenceMembers)) member2 := presenceMembers[presenceMsg2.ConnectionID+presenceMsg2.ClientID] assert.Equal(t, ably.PresenceActionPresent, member2.Action) }) @@ -274,7 +276,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { t.Run("RTP2b1: check for newness by timestamp is synthesized", func(t *testing.T) { in, _, _, channel, _, _, presenceMsgCh := setup(t) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) presenceMsg1 := &ably.PresenceMessage{ @@ -305,7 +307,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { in <- msg ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) - presenceMembers := channel.Presence.GetMembers() + presenceMembers := channel.Presence.Members() assert.Equal(t, 1, len(presenceMembers)) member := presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] @@ -316,7 +318,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { in <- msg ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) - presenceMembers = channel.Presence.GetMembers() + presenceMembers = channel.Presence.Members() assert.Equal(t, 1, len(presenceMembers)) member = presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] assert.Equal(t, ably.PresenceActionPresent, member.Action) @@ -326,7 +328,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { t.Run("RTP2b2, RTP2d: check for newness by serial if not synthesized", func(t *testing.T) { in, _, _, channel, _, _, presenceMsgCh := setup(t) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) presenceMsg1 := &ably.PresenceMessage{ @@ -367,7 +369,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { in <- msg ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) - presenceMembers := channel.Presence.GetMembers() + presenceMembers := channel.Presence.Members() assert.Equal(t, 1, len(presenceMembers)) member := presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] @@ -378,7 +380,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { in <- msg ablytest.Instantly.NoRecv(t, nil, presenceMsgCh, t.Fatalf) - presenceMembers = channel.Presence.GetMembers() + presenceMembers = channel.Presence.Members() assert.Equal(t, 1, len(presenceMembers)) member = presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] assert.Equal(t, ably.PresenceActionPresent, member.Action) @@ -388,7 +390,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { in <- msg ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) - presenceMembers = channel.Presence.GetMembers() + presenceMembers = channel.Presence.Members() assert.Equal(t, 1, len(presenceMembers)) member = presenceMembers[presenceMsg1.ConnectionID+presenceMsg1.ClientID] assert.Equal(t, ably.PresenceActionPresent, member.Action) @@ -398,7 +400,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { t.Run("RTP2c: check for newness during sync", func(t *testing.T) { in, _, _, channel, stateChanges, _, presenceMsgCh := setup(t) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) assert.False(t, channel.Presence.SyncInitial()) @@ -460,14 +462,14 @@ func Test_PresenceMap_RTP2(t *testing.T) { assert.True(t, channel.Presence.SyncComplete()) assert.False(t, channel.Presence.SyncInitial()) - presenceMembers := channel.Presence.GetMembers() + presenceMembers := channel.Presence.Members() assert.Equal(t, 1, len(presenceMembers)) }) t.Run("RTP2d, RTP2g: when presence msg with ENTER, UPDATE AND PRESENT arrives, add to presence map with action as present", func(t *testing.T) { in, _, _, channel, _, _, presenceMsgCh := setup(t) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) presenceMsg1 := &ably.PresenceMessage{ @@ -524,7 +526,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) assert.Equal(t, ably.PresenceActionPresent, presenceMsg.Action) - members := channel.Presence.GetMembers() + members := channel.Presence.Members() assert.Equal(t, 3, len(members)) for _, pm := range members { assert.Equal(t, ably.PresenceActionPresent, pm.Action) @@ -534,7 +536,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { t.Run("RTP2e, RTP2g: when presence msg with LEAVE action arrives, remove member from presence map", func(t *testing.T) { in, _, _, channel, _, _, presenceMsgCh := setup(t) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) presenceMsg1 := &ably.PresenceMessage{ @@ -579,7 +581,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) assert.Equal(t, ably.PresenceActionEnter, presenceMsg.Action) - members := channel.Presence.GetMembers() + members := channel.Presence.Members() assert.Equal(t, 1, len(members)) msg.Presence = []*ably.PresenceMessage{presenceMsg2} @@ -588,7 +590,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) assert.Equal(t, ably.PresenceActionUpdate, presenceMsg.Action) - members = channel.Presence.GetMembers() + members = channel.Presence.Members() assert.Equal(t, 2, len(members)) msg.Presence = []*ably.PresenceMessage{presenceMsg3} @@ -597,7 +599,7 @@ func Test_PresenceMap_RTP2(t *testing.T) { ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) assert.Equal(t, ably.PresenceActionLeave, presenceMsg.Action) - members = channel.Presence.GetMembers() + members = channel.Presence.Members() assert.Equal(t, 1, len(members)) for _, pm := range members { assert.Equal(t, ably.PresenceActionPresent, pm.Action) @@ -662,7 +664,7 @@ func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { t.Run("RTP18a: client determines a new sync started with :", func(t *testing.T) { in, _, _, channel, _, presenceMsgCh := setup(t) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) presenceMsg1 := &ably.PresenceMessage{ @@ -715,14 +717,14 @@ func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { assert.True(t, channel.Presence.SyncInProgress()) assert.False(t, channel.Presence.SyncComplete()) - presenceMembers := channel.Presence.GetMembers() + presenceMembers := channel.Presence.Members() assert.Equal(t, 1, len(presenceMembers)) }) t.Run("RTP18b: client determines sync ended with :", func(t *testing.T) { in, _, _, channel, _, presenceMsgCh := setup(t) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) presenceMsg1 := &ably.PresenceMessage{ @@ -754,7 +756,7 @@ func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { assert.True(t, channel.Presence.SyncInProgress()) assert.False(t, channel.Presence.SyncComplete()) - presenceMembers := channel.Presence.GetMembers() + presenceMembers := channel.Presence.Members() assert.Equal(t, 1, len(presenceMembers)) presenceMsg2 := &ably.PresenceMessage{ @@ -782,14 +784,14 @@ func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { assert.False(t, channel.Presence.SyncInProgress()) assert.True(t, channel.Presence.SyncComplete()) - presenceMembers = channel.Presence.GetMembers() + presenceMembers = channel.Presence.Members() assert.Equal(t, 2, len(presenceMembers)) }) t.Run("RTP18: client determines sync started and ended with :", func(t *testing.T) { in, _, _, channel, _, presenceMsgCh := setup(t) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) presenceMsg1 := &ably.PresenceMessage{ @@ -842,7 +844,7 @@ func Test_Presence_server_initiated_sync_RTP18(t *testing.T) { assert.False(t, channel.Presence.SyncInProgress()) assert.True(t, channel.Presence.SyncComplete()) - presenceMembers := channel.Presence.GetMembers() + presenceMembers := channel.Presence.Members() assert.Equal(t, 1, len(presenceMembers)) }) } @@ -894,7 +896,7 @@ func Test_RTP1_attach_with_presence_flag(t *testing.T) { assert.False(t, channel.Presence.SyncInProgress()) assert.True(t, channel.Presence.SyncComplete()) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) in <- &ably.ProtocolMessage{ @@ -966,7 +968,7 @@ func Test_internal_presencemap_RTP17(t *testing.T) { t.Run("RTP17: presence object should have second presencemap containing only currentConnectionId", func(t *testing.T) { in, _, client, channel, _, presenceMsgCh := setup(t) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) presenceMsg1 := &ably.PresenceMessage{ @@ -1012,7 +1014,7 @@ func Test_internal_presencemap_RTP17(t *testing.T) { ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) - members := channel.Presence.GetMembers() + members := channel.Presence.Members() assert.Equal(t, 3, len(members)) internalMembers := channel.Presence.InternalMembers() @@ -1134,7 +1136,7 @@ func Test_internal_presencemap_RTP17(t *testing.T) { t.Run("RTP17h: presencemap should be keyed by clientId", func(t *testing.T) { in, _, client, channel, _, presenceMsgCh := setup(t) - initialMembers := channel.Presence.GetMembers() + initialMembers := channel.Presence.Members() assert.Empty(t, initialMembers) presenceMsg1 := &ably.PresenceMessage{ @@ -1158,7 +1160,7 @@ func Test_internal_presencemap_RTP17(t *testing.T) { ablytest.Instantly.Recv(t, &presenceMsg, presenceMsgCh, t.Fatalf) - members := channel.Presence.GetMembers() + members := channel.Presence.Members() assert.Equal(t, 1, len(members)) internalMembers := channel.Presence.InternalMembers() @@ -1214,7 +1216,7 @@ func Test_internal_presencemap_RTP17(t *testing.T) { ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) ablytest.Instantly.Recv(t, nil, presenceMsgCh, t.Fatalf) - members := channel.Presence.GetMembers() + members := channel.Presence.Members() assert.Equal(t, 3, len(members)) internalMembers := channel.Presence.InternalMembers() From 14252e889bebb9239110c441ce27d26a7f1a30cc Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 5 Dec 2023 02:05:59 +0530 Subject: [PATCH 165/178] Updated syncWait method to include syncDone channel --- ably/realtime_presence.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 5051a5762..888af3d82 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -29,9 +29,9 @@ type RealtimePresence struct { internalMembers map[string]*PresenceMessage // RTP17 beforeSyncMembers map[string]*PresenceMessage state PresenceAction - syncMtx sync.Mutex syncState syncState queue *msgQueue + syncDone chan struct{} } func newRealtimePresence(channel *RealtimeChannel) *RealtimePresence { @@ -41,12 +41,12 @@ func newRealtimePresence(channel *RealtimeChannel) *RealtimePresence { members: make(map[string]*PresenceMessage), internalMembers: make(map[string]*PresenceMessage), syncState: syncInitial, + syncDone: make(chan struct{}), } pres.queue = newMsgQueue(pres.channel.client.Connection) // Lock syncMtx to make all callers to Get(true) wait until the presence // is in initial sync state. This is to not make them early return // with an empty presence list before channel attaches. - pres.syncMtx.Lock() return pres } @@ -122,12 +122,16 @@ func (pres *RealtimePresence) send(msg *PresenceMessage) (result, error) { }), nil } -func (pres *RealtimePresence) syncWait() { +func (pres *RealtimePresence) syncWait(ctx context.Context) error { // If there's an undergoing sync operation or we wait till channel gets // attached, the following lock is going to block until the operations // complete. - pres.syncMtx.Lock() - pres.syncMtx.Unlock() + select { + case <-pres.syncDone: + return nil + case <-ctx.Done(): + return ctx.Err() + } } // RTP18 @@ -169,7 +173,7 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMess pres.leaveMembers(pres.members) // RTP19a if pres.syncState == syncInitial { pres.syncState = syncComplete - pres.syncMtx.Unlock() + close(pres.syncDone) } } pres.queue.Flush() // RTP5b @@ -200,7 +204,7 @@ func (pres *RealtimePresence) syncStart() { } else if pres.syncState != syncInitial { // Sync has started, make all callers to Get(true) wait. If it's channel's // initial sync, the callers are already waiting. - pres.syncMtx.Lock() + pres.syncDone = make(chan struct{}) } pres.syncState = syncInProgress pres.beforeSyncMembers = make(map[string]*PresenceMessage, len(pres.members)) // RTP19 @@ -237,7 +241,7 @@ func (pres *RealtimePresence) syncEnd() { pres.syncState = syncComplete // Sync has completed, unblock all callers to Get(true) waiting // for the sync. - pres.syncMtx.Unlock() + close(pres.syncDone) } // RTP2a, RTP2b, RTP2c @@ -395,7 +399,10 @@ func (pres *RealtimePresence) GetWithOptions(ctx context.Context, options ...Pre } if opts.waitForSync { - pres.syncWait() + err = pres.syncWait(ctx) + if err != nil { + return nil, err + } } pres.mtx.Lock() From e4ef0ce2b4e50a92ed222b22aa830f51520a1baa Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 5 Dec 2023 10:37:16 +0530 Subject: [PATCH 166/178] Refactored syncmutex comments for realtime_presence --- ably/realtime_presence.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 888af3d82..21bd2f807 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -44,9 +44,6 @@ func newRealtimePresence(channel *RealtimeChannel) *RealtimePresence { syncDone: make(chan struct{}), } pres.queue = newMsgQueue(pres.channel.client.Connection) - // Lock syncMtx to make all callers to Get(true) wait until the presence - // is in initial sync state. This is to not make them early return - // with an empty presence list before channel attaches. return pres } @@ -202,8 +199,7 @@ func (pres *RealtimePresence) syncStart() { if pres.syncState == syncInProgress { return } else if pres.syncState != syncInitial { - // Sync has started, make all callers to Get(true) wait. If it's channel's - // initial sync, the callers are already waiting. + // Start new sync after previous one was finished pres.syncDone = make(chan struct{}) } pres.syncState = syncInProgress From 1b7a72daa632133f972ae896f10bd400ee103355 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 5 Dec 2023 11:45:46 +0530 Subject: [PATCH 167/178] Fixed race condition while accessing state, updated code as per spec --- ably/proto_presence_message_test.go | 1 - ably/realtime_channel.go | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 61ba2a678..dfb32620a 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -1224,7 +1224,6 @@ func Test_internal_presencemap_RTP17(t *testing.T) { in <- &ably.ProtocolMessage{ Action: ably.ActionAttached, - Flags: ably.FlagResumed, Channel: channel.Name, } diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index c49bd6a1d..fb09d0b21 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -797,10 +797,12 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { if msg.Flags != 0 { c.setModes(channelModeFromFlag(msg.Flags)) } - // RTL12 - if c.state == ChannelStateAttached && !msg.Flags.Has(flagResumed) { - c.emitErrorUpdate(newErrorFromProto(msg.Error), false) // RTL12 - c.Presence.onAttach(msg, false) + + if c.State() == ChannelStateAttached { + if !msg.Flags.Has(flagResumed) { // RTL12 + c.Presence.onAttach(msg, true) + c.emitErrorUpdate(newErrorFromProto(msg.Error), false) + } } else { c.Presence.onAttach(msg, true) c.setState(ChannelStateAttached, newErrorFromProto(msg.Error), msg.Flags.Has(flagResumed)) From 14cd5a206e18571f1012f992397e1092c535919e Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 7 Dec 2023 20:53:27 +0530 Subject: [PATCH 168/178] Updated attached impl as per RTL5k --- ably/realtime_channel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index fb09d0b21..07a8d8aa6 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -787,7 +787,7 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { c.mtx.Lock() c.properties.AttachSerial = msg.ChannelSerial // RTL15a c.mtx.Unlock() - if c.State() == ChannelStateDetaching { // RTL5K + if c.State() == ChannelStateDetaching || c.State() == ChannelStateDetached { // RTL5K c.sendDetachMsg() return } From 7913d25d843cb61de8176c02da139aff4a996b4d Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 12 Jan 2024 13:40:50 +0530 Subject: [PATCH 169/178] Refactored onAttach realtime presence method, removed extra param --- ably/realtime_channel.go | 4 ++-- ably/realtime_presence.go | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 07a8d8aa6..519979377 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -800,11 +800,11 @@ func (c *RealtimeChannel) notify(msg *protocolMessage) { if c.State() == ChannelStateAttached { if !msg.Flags.Has(flagResumed) { // RTL12 - c.Presence.onAttach(msg, true) + c.Presence.onAttach(msg) c.emitErrorUpdate(newErrorFromProto(msg.Error), false) } } else { - c.Presence.onAttach(msg, true) + c.Presence.onAttach(msg) c.setState(ChannelStateAttached, newErrorFromProto(msg.Error), msg.Flags.Has(flagResumed)) } c.queue.Flush() diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index 21bd2f807..c830651b7 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -160,7 +160,7 @@ func (pres *RealtimePresence) enterMembers(internalMembers []*PresenceMessage) { } } -func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMessageLoss bool) { +func (pres *RealtimePresence) onAttach(msg *protocolMessage) { pres.mtx.Lock() defer pres.mtx.Unlock() // RTP1 @@ -173,18 +173,17 @@ func (pres *RealtimePresence) onAttach(msg *protocolMessage, isAttachWithoutMess close(pres.syncDone) } } - pres.queue.Flush() // RTP5b + // RTP5b + pres.queue.Flush() // RTP17f - if isAttachWithoutMessageLoss { - if len(pres.internalMembers) > 0 { - internalMembers := make([]*PresenceMessage, len(pres.internalMembers)) - indexCounter := 0 - for _, member := range pres.internalMembers { - internalMembers[indexCounter] = member - indexCounter = indexCounter + 1 - } - go pres.enterMembers(internalMembers) + if len(pres.internalMembers) > 0 { + internalMembers := make([]*PresenceMessage, len(pres.internalMembers)) + indexCounter := 0 + for _, member := range pres.internalMembers { + internalMembers[indexCounter] = member + indexCounter = indexCounter + 1 } + go pres.enterMembers(internalMembers) } } From 07ab32b76a49b1805e5607e834f6d9fecee7c1ee Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 12 Jan 2024 16:01:19 +0530 Subject: [PATCH 170/178] added parenthesis to condition for setting channelserial --- ably/realtime_channel.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index 519979377..cce96bda0 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -775,8 +775,8 @@ func (c *RealtimeChannel) ErrorReason() *ErrorInfo { func (c *RealtimeChannel) notify(msg *protocolMessage) { // RTL15b - if !empty(msg.ChannelSerial) && msg.Action == actionMessage || - msg.Action == actionPresence || msg.Action == actionAttached { + if !empty(msg.ChannelSerial) && (msg.Action == actionMessage || + msg.Action == actionPresence || msg.Action == actionAttached) { c.log().Debugf("Setting channel serial for channelName - %v, previous - %v, current - %v", c.Name, c.getChannelSerial(), msg.ChannelSerial) c.setChannelSerial(msg.ChannelSerial) From 115b7977d57642b92f10a8ece108b59e82181b5a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 12 Jan 2024 17:21:07 +0530 Subject: [PATCH 171/178] Added a method to enterClient with id, clientID and data --- ably/realtime_presence.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/ably/realtime_presence.go b/ably/realtime_presence.go index c830651b7..e93192bd4 100644 --- a/ably/realtime_presence.go +++ b/ably/realtime_presence.go @@ -146,11 +146,12 @@ func syncSerial(msg *protocolMessage) (noChannelSerial bool, syncSequenceId stri return false, syncSequenceId, syncCursor } +// for every attach local members will be entered func (pres *RealtimePresence) enterMembers(internalMembers []*PresenceMessage) { for _, member := range internalMembers { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) // RTP17g - err := pres.EnterClient(ctx, member.ClientID, member.Data) + err := pres.enterClientWithId(ctx, member.ID, member.ClientID, member.Data) // RTP17e if err != nil { pres.channel.log().Errorf("Error for internal member presence enter with id %v, clientId %v, err %v", member.ID, member.ClientID, err) @@ -542,6 +543,24 @@ func (pres *RealtimePresence) EnterClient(ctx context.Context, clientID string, return res.Wait(ctx) } +func (pres *RealtimePresence) enterClientWithId(ctx context.Context, id string, clientID string, data interface{}) error { + pres.mtx.Lock() + pres.data = data + pres.state = PresenceActionEnter + pres.mtx.Unlock() + msg := PresenceMessage{ + Action: PresenceActionEnter, + } + msg.ID = id + msg.Data = data + msg.ClientID = clientID + res, err := pres.send(&msg) + if err != nil { + return err + } + return res.Wait(ctx) +} + func nonnil(a, b interface{}) interface{} { if a != nil { return a From f3d3e708e1317d1b9a129bbddd6299950e46918e Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 12 Jan 2024 18:04:01 +0530 Subject: [PATCH 172/178] Refactored code to assert for message id --- ably/proto_presence_message_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index dfb32620a..a9596707c 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -1233,16 +1233,18 @@ func Test_internal_presencemap_RTP17(t *testing.T) { // Send enter for internal messages var protoMsg *ably.ProtocolMessage ablytest.Instantly.Recv(t, &protoMsg, out, t.Fatalf) - for _, v := range protoMsg.Presence { - assert.Equal(t, ably.PresenceActionEnter, v.Action) - assert.Equal(t, client.Connection.ID(), v.ConnectionID) + for _, enteredMsg := range protoMsg.Presence { + assert.Equal(t, ably.PresenceActionEnter, enteredMsg.Action) + assert.Equal(t, "987:12:0", enteredMsg.ID) + assert.Equal(t, client.Connection.ID(), enteredMsg.ConnectionID) } client.Connection.AckAll() ablytest.Instantly.Recv(t, &protoMsg, out, t.Fatalf) - for _, v := range protoMsg.Presence { - assert.Equal(t, ably.PresenceActionEnter, v.Action) - assert.Equal(t, client.Connection.ID(), v.ConnectionID) + for _, enteredMsg := range protoMsg.Presence { + assert.Equal(t, ably.PresenceActionEnter, enteredMsg.Action) + assert.Equal(t, "987:12:1", enteredMsg.ID) + assert.Equal(t, client.Connection.ID(), enteredMsg.ConnectionID) } client.Connection.AckAll() ablytest.Instantly.NoRecv(t, nil, out, t.Fatalf) From 22f1e54e93d53523ab9dcd9d498e38ae0f6cc4db Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 14 Jan 2024 12:26:34 +0530 Subject: [PATCH 173/178] Updated recovery error message as per suggestion --- ably/realtime_client.go | 2 ++ ably/realtime_conn.go | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ably/realtime_client.go b/ably/realtime_client.go index a4428b2da..56de739a9 100644 --- a/ably/realtime_client.go +++ b/ably/realtime_client.go @@ -42,7 +42,9 @@ func NewRealtime(options ...ClientOption) (*Realtime, error) { if !empty(c.opts().Recover) { recoverKeyContext, err := DecodeRecoveryKey(c.opts().Recover) if err != nil { + // Ignoring error since no recover will be used for new connection c.log().Errorf("Error decoding recover with error %v", err) + c.log().Errorf("Trying a fresh connection instead") } else { c.Channels.SetChannelSerialsFromRecoverOption(recoverKeyContext.ChannelSerials) // RTN16j c.Connection.msgSerial = recoverKeyContext.MsgSerial // RTN16f diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 92154c5ae..1964283a2 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -289,7 +289,9 @@ func (c *Connection) params(mode connectionMode) (url.Values, error) { case recoveryMode: recoveryKeyContext, err := DecodeRecoveryKey(c.recover) if err != nil { - c.log().Errorf("error decoding recovery key, %v", err) + // Ignoring error since no recover will be used for new connection + c.log().Errorf("Error decoding recovery key, %v", err) + c.log().Errorf("Trying a fresh connection instead") } else { query.Set("recover", recoveryKeyContext.ConnectionKey) // RTN16k } From 851ddbf8952a9d20b45440c902dcda4138b198c6 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 14 Jan 2024 12:41:04 +0530 Subject: [PATCH 174/178] Checking channel state to be attached before getting channel serial --- ably/realtime_channel.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ably/realtime_channel.go b/ably/realtime_channel.go index cce96bda0..844698456 100644 --- a/ably/realtime_channel.go +++ b/ably/realtime_channel.go @@ -60,7 +60,9 @@ func (channels *RealtimeChannels) GetChannelSerials() map[string]string { defer channels.mtx.Unlock() channelSerials := make(map[string]string) for channelName, realtimeChannel := range channels.chans { - channelSerials[channelName] = realtimeChannel.getChannelSerial() + if realtimeChannel.State() == ChannelStateAttached { + channelSerials[channelName] = realtimeChannel.getChannelSerial() + } } return channelSerials } From 9dbc8d9795693796f57fbb413d63b429e2ee237e Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 14 Jan 2024 12:55:33 +0530 Subject: [PATCH 175/178] Added a helper method to check for presence message ids --- ably/realtime_presence_integration_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ably/realtime_presence_integration_test.go b/ably/realtime_presence_integration_test.go index d27638fcd..586a6a869 100644 --- a/ably/realtime_presence_integration_test.go +++ b/ably/realtime_presence_integration_test.go @@ -29,6 +29,19 @@ func contains(members []*ably.PresenceMessage, clients ...string) error { return nil } +func containsIds(members []*ably.PresenceMessage, ids ...string) error { + lookup := make(map[string]struct{}, len(members)) + for _, member := range members { + lookup[member.ID] = struct{}{} + } + for _, id := range ids { + if _, ok := lookup[id]; !ok { + return fmt.Errorf("ID=%q not found in presence map", id) + } + } + return nil +} + func generateClients(num int) []string { clients := make([]string, 0, num) for i := 0; i < num; i++ { From aea1a51ea5e67653453304778ff3f26f93cfff90 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 15 Jan 2024 12:06:34 +0530 Subject: [PATCH 176/178] Added helper method to check for entered presence ids --- ably/proto_presence_message_test.go | 32 +++++++++++++++++----- ably/realtime_presence_integration_test.go | 13 --------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index a9596707c..560b627c5 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -17,6 +17,19 @@ import ( "github.com/stretchr/testify/assert" ) +func containsIds(members []*ably.PresenceMessage, ids ...string) error { + lookup := make(map[string]struct{}, len(members)) + for _, member := range members { + lookup[member.ID] = struct{}{} + } + for _, id := range ids { + if _, ok := lookup[id]; !ok { + return fmt.Errorf("ID=%q not found in presence map", id) + } + } + return nil +} + func TestPresenceMessage(t *testing.T) { actions := []ably.PresenceAction{ ably.PresenceActionAbsent, @@ -1231,22 +1244,27 @@ func Test_internal_presencemap_RTP17(t *testing.T) { ablytest.Instantly.Recv(t, &chanChange, stateChanges, t.Fatalf) // Send enter for internal messages - var protoMsg *ably.ProtocolMessage - ablytest.Instantly.Recv(t, &protoMsg, out, t.Fatalf) - for _, enteredMsg := range protoMsg.Presence { + var protoMsg1 *ably.ProtocolMessage + ablytest.Instantly.Recv(t, &protoMsg1, out, t.Fatalf) + for _, enteredMsg := range protoMsg1.Presence { assert.Equal(t, ably.PresenceActionEnter, enteredMsg.Action) - assert.Equal(t, "987:12:0", enteredMsg.ID) assert.Equal(t, client.Connection.ID(), enteredMsg.ConnectionID) } client.Connection.AckAll() - ablytest.Instantly.Recv(t, &protoMsg, out, t.Fatalf) - for _, enteredMsg := range protoMsg.Presence { + var protoMsg2 *ably.ProtocolMessage + ablytest.Instantly.Recv(t, &protoMsg2, out, t.Fatalf) + for _, enteredMsg := range protoMsg2.Presence { assert.Equal(t, ably.PresenceActionEnter, enteredMsg.Action) - assert.Equal(t, "987:12:1", enteredMsg.ID) assert.Equal(t, client.Connection.ID(), enteredMsg.ConnectionID) } client.Connection.AckAll() + + messages := []*ably.PresenceMessage{protoMsg1.Presence[0], protoMsg2.Presence[0]} + + err := containsIds(messages, "987:12:0", "987:12:1") + assert.Nil(t, err) + ablytest.Instantly.NoRecv(t, nil, out, t.Fatalf) }) } diff --git a/ably/realtime_presence_integration_test.go b/ably/realtime_presence_integration_test.go index 586a6a869..d27638fcd 100644 --- a/ably/realtime_presence_integration_test.go +++ b/ably/realtime_presence_integration_test.go @@ -29,19 +29,6 @@ func contains(members []*ably.PresenceMessage, clients ...string) error { return nil } -func containsIds(members []*ably.PresenceMessage, ids ...string) error { - lookup := make(map[string]struct{}, len(members)) - for _, member := range members { - lookup[member.ID] = struct{}{} - } - for _, id := range ids { - if _, ok := lookup[id]; !ok { - return fmt.Errorf("ID=%q not found in presence map", id) - } - } - return nil -} - func generateClients(num int) []string { clients := make([]string, 0, num) for i := 0; i < num; i++ { From b185b7160d9a0be525fdaa3bfc076b835343b6c3 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 15 Jan 2024 18:16:59 +0530 Subject: [PATCH 177/178] Added warning for deprecated recoveryKey method --- ably/realtime_conn.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 1964283a2..e82eca037 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -498,6 +498,7 @@ func (c *Connection) ErrorReason() *ErrorInfo { // Deprecated: this property is deprecated, use CreateRecoveryKey method instead. func (c *Connection) RecoveryKey() string { + c.log().Warn("RecoveryKey is deprecated, use CreateRecoveryKey method instead") return c.CreateRecoveryKey() } From 3cddaaf254d858b35928a056d118c2efbfaea1c7 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 16 Jan 2024 12:43:27 +0530 Subject: [PATCH 178/178] Fixed logic for checking newer presence message --- ably/proto_presence_message.go | 4 ++-- ably/proto_presence_message_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ably/proto_presence_message.go b/ably/proto_presence_message.go index b8d1ab99b..b1f9ea588 100644 --- a/ably/proto_presence_message.go +++ b/ably/proto_presence_message.go @@ -85,7 +85,7 @@ func (msg *PresenceMessage) getMsgSerialAndIndex() (int64, int64, error) { func (msg1 *PresenceMessage) IsNewerThan(msg2 *PresenceMessage) (bool, error) { // RTP2b1 if msg1.isServerSynthesized() || msg2.isServerSynthesized() { - return msg1.Timestamp > msg2.Timestamp, nil + return msg1.Timestamp >= msg2.Timestamp, nil } // RTP2b2 @@ -98,7 +98,7 @@ func (msg1 *PresenceMessage) IsNewerThan(msg2 *PresenceMessage) (bool, error) { return true, err } if msg1Serial == msg2Serial { - return msg1Index > msg2Index, nil + return msg1Index >= msg2Index, nil } return msg1Serial > msg2Serial, nil } diff --git a/ably/proto_presence_message_test.go b/ably/proto_presence_message_test.go index 560b627c5..63c3d15ef 100644 --- a/ably/proto_presence_message_test.go +++ b/ably/proto_presence_message_test.go @@ -1111,7 +1111,7 @@ func Test_internal_presencemap_RTP17(t *testing.T) { Action: ably.PresenceActionLeave, Message: ably.Message{ ID: "987:12:3", - Timestamp: 125, + Timestamp: 124, ConnectionID: client.Connection.ID(), ClientID: "978", },