Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RTP17b #835

Merged
merged 20 commits into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fff0b4d
RTP17b
ricardopereira Mar 1, 2019
da88c56
Test Suite: HTTPURLResponse allHeaderFields is now case-sensitive
ricardopereira Mar 7, 2019
ed64c01
Log with error convenience method
ricardopereira Mar 7, 2019
ec3a106
RTP17b implementation
ricardopereira Mar 7, 2019
8fb2e0c
Update tests because ARTPresenceMap.isNewestPresence:comparingWith
ricardopereira Mar 7, 2019
3f1bf2d
[fix] Lib is unnecessarily sending another ATTACH
ricardopereira Mar 7, 2019
f3933f7
[fix] Check if response body has data
ricardopereira Mar 8, 2019
8dbc03d
Test Suite: fix [ARTPresenceMessage decodeWithEncoder:error:]: unreco…
ricardopereira Mar 13, 2019
b50cd73
Channel debug log small improvement
ricardopereira Apr 26, 2019
dc84181
syncMsgSerial should only be used when requesting a continue sync
ricardopereira Apr 26, 2019
ca32ef2
Remove duplicated remove local member instruction
ricardopereira Apr 26, 2019
1ea0e4c
Presense Message debug log small fix
ricardopereira Apr 29, 2019
0c2d2f5
fixup! RTP17b
ricardopereira Apr 29, 2019
ec5a7f0
[fix] Should change the sync state after calling sync method
ricardopereira Apr 29, 2019
68b6967
Remove redundant code
ricardopereira Apr 29, 2019
46dd5e7
fixup! RTP17b
ricardopereira May 2, 2019
6058e4a
Merge branch 'develop' into rtp17b
ricardopereira May 6, 2019
782ab1e
[fix] Update Logging test which was improved
ricardopereira May 16, 2019
0d7cdc0
Sync channelSerial should be unset when the SYNC succeeds.
ricardopereira May 21, 2019
9d2ef9e
Test suite: avoid some race conditions
ricardopereira May 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Source/ARTLog.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ typedef NS_ENUM(NSUInteger, ARTLogLevel) {
@property (nonatomic, assign) ARTLogLevel logLevel;

- (void)log:(NSString *)message withLevel:(ARTLogLevel)level;
- (void)logWithError:(ARTErrorInfo *)error;

- (ARTLog *)verboseMode;
- (ARTLog *)debugMode;
Expand Down
4 changes: 4 additions & 0 deletions Source/ARTLog.m
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ - (void)log:(NSString *)message level:(ARTLogLevel)level {
});
}

- (void)logWithError:(ARTErrorInfo *)error {
[self log:error.message withLevel:ARTLogLevelError];
}

- (NSArray<ARTLogLine *> *)history {
return _history;
}
Expand Down
66 changes: 13 additions & 53 deletions Source/ARTPresenceMap.m
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ - (instancetype)initWithQueue:(_Nonnull dispatch_queue_t)queue logger:(ARTLog *)

- (BOOL)add:(ARTPresenceMessage *)message {
ARTPresenceMessage *latest = [_members objectForKey:message.memberKey];
if ([self isNewestPresence:message comparingWith:latest]) {
if ([message isNewerThan:latest]) {
ARTPresenceMessage *messageCopy = [message copy];
switch (message.action) {
case ARTPresenceEnter:
Expand All @@ -96,6 +96,7 @@ - (BOOL)add:(ARTPresenceMessage *)message {
}
return YES;
}
[_logger debug:__FILE__ line:__LINE__ message:@"Presence member \"%@\" with action %@ has been ignored", message.memberKey, ARTPresenceActionToStr(message.action)];
latest.syncSessionId = _syncSessionId;
return NO;
}
Expand All @@ -108,9 +109,9 @@ - (void)internalAdd:(ARTPresenceMessage *)message withSessionId:(NSUInteger)sess
message.syncSessionId = sessionId;
[_members setObject:message forKey:message.memberKey];
// Local member
if ([message.connectionId isEqualToString:[self.delegate connectionId]]) {
if ([message.connectionId isEqualToString:self.delegate.connectionId]) {
[_localMembers addObject:message];
[_logger debug:__FILE__ line:__LINE__ message:@"local member %@ added", message.memberKey];
[_logger debug:__FILE__ line:__LINE__ message:@"local member %@ with action %@ has been added", message.memberKey, ARTPresenceActionToStr(message.action).uppercaseString];
}
}

Expand All @@ -119,57 +120,20 @@ - (void)internalRemove:(ARTPresenceMessage *)message {
}

- (void)internalRemove:(ARTPresenceMessage *)message force:(BOOL)force {
if ([message.connectionId isEqualToString:self.delegate.connectionId] && !message.isSynthesized) {
[_localMembers removeObject:message];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now you've added this, you need to remove line 134, or it'll do the remove unconditionally

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed ca32ef2. Thanks

}

if (!force && self.syncInProgress) {
message.action = ARTPresenceAbsent;
// Should be removed after Sync ends
[self internalAdd:message withSessionId:message.syncSessionId];
}
else {
[_members removeObjectForKey:message.memberKey];
[_localMembers removeObject:message];
}
}

- (BOOL)isNewestPresence:(nonnull ARTPresenceMessage *)received comparingWith:(ARTPresenceMessage *)latest __attribute__((warn_unused_result)) {
if (latest == nil) {
return YES;
}

NSArray<NSString *> *receivedMessageIdParts = [received.id componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@":"]];
if (receivedMessageIdParts.count != 3) {
[_logger error:@"Received presence message id is invalid %@", received.id];
return !received.timestamp ||
[latest.timestamp timeIntervalSince1970] <= [received.timestamp timeIntervalSince1970];
}
NSString *receivedConnectionId = [receivedMessageIdParts objectAtIndex:0];
NSInteger receivedMsgSerial = [[receivedMessageIdParts objectAtIndex:1] integerValue];
NSInteger receivedIndex = [[receivedMessageIdParts objectAtIndex:2] integerValue];

if ([receivedConnectionId isEqualToString:received.connectionId]) {
NSArray<NSString *> *latestRegisteredIdParts = [latest.id componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@":"]];
if (latestRegisteredIdParts.count != 3) {
[_logger error:@"Latest registered presence message id is invalid %@", latest.id];
return !received.timestamp ||
[latest.timestamp timeIntervalSince1970] <= [received.timestamp timeIntervalSince1970];
}
NSInteger latestRegisteredMsgSerial = [[latestRegisteredIdParts objectAtIndex:1] integerValue];
NSInteger latestRegisteredIndex = [[latestRegisteredIdParts objectAtIndex:2] integerValue];

if (receivedMsgSerial > latestRegisteredMsgSerial) {
return YES;
}
else if (receivedMsgSerial == latestRegisteredMsgSerial && receivedIndex > latestRegisteredIndex) {
return YES;
}

[_logger debug:__FILE__ line:__LINE__ message:@"Presence member \"%@\" with action %@ has been ignored", received.memberKey, ARTPresenceActionToStr(received.action)];
return NO;
}

return !received.timestamp ||
[latest.timestamp timeIntervalSince1970] <= [received.timestamp timeIntervalSince1970];
}

- (void)cleanUpAbsentMembers {
NSSet<NSString *> *filteredMembers = [_members keysOfEntriesPassingTest:^BOOL(NSString *key, ARTPresenceMessage *message, BOOL *stop) {
return message.action == ARTPresenceAbsent;
Expand Down Expand Up @@ -206,18 +170,21 @@ - (void)reset {
}

- (void)startSync {
[_logger debug:__FILE__ line:__LINE__ message:@"%p PresenceMap sync started", self];
_syncSessionId++;
_syncState = ARTPresenceSyncStarted;
[_syncEventEmitter emit:[ARTEvent newWithPresenceSyncState:_syncState] with:nil];
}

- (void)endSync {
[_logger verbose:__FILE__ line:__LINE__ message:@"%p PresenceMap sync ending", self];
[self cleanUpAbsentMembers];
[self leaveMembersNotPresentInSync];
_syncState = ARTPresenceSyncEnded;
[self reenterLocalMembersMissingFromSync];
[_syncEventEmitter emit:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncEnded] with:[_members allValues]];
[_syncEventEmitter off];
[_logger debug:__FILE__ line:__LINE__ message:@"%p PresenceMap sync ended", self];
}

- (void)failsSync:(ARTErrorInfo *)error {
Expand All @@ -228,18 +195,11 @@ - (void)failsSync:(ARTErrorInfo *)error {
}

- (void)onceSyncEnds:(void (^)(NSArray<ARTPresenceMessage *> *))callback {
if (self.syncInProgress) {
[_syncEventEmitter once:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncEnded] callback:callback];
}
else {
callback([_members allValues]);
}
[_syncEventEmitter once:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncEnded] callback:callback];
}

- (void)onceSyncFails:(void (^)(ARTErrorInfo *))callback {
if (self.syncInProgress) {
[_syncEventEmitter once:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncFailed] callback:callback];
}
[_syncEventEmitter once:[ARTEvent newWithPresenceSyncState:ARTPresenceSyncFailed] callback:callback];
}

- (BOOL)syncComplete {
Expand Down
9 changes: 9 additions & 0 deletions Source/ARTPresenceMessage+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,13 @@

@property (readwrite, assign, nonatomic) NSUInteger syncSessionId;

/**
Returns whether this presenceMessage is synthesized, i.e. was not actually sent by the connection (usually means a leave event sent 15s after a disconnection). This is useful because synthesized messages cannot be compared for newness by id lexicographically - RTP2b1.
*/
- (BOOL)isSynthesized;

- (nonnull NSArray<NSString *> *)parseId;
- (NSInteger)msgSerialFromId;
- (NSInteger)indexFromId;

@end
2 changes: 2 additions & 0 deletions Source/ARTPresenceMessage.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ NS_ASSUME_NONNULL_BEGIN

- (BOOL)isEqualToPresenceMessage:(nonnull ARTPresenceMessage *)presence;

- (BOOL)isNewerThan:(ARTPresenceMessage *)latest __attribute__((warn_unused_result));

@end

#pragma mark - ARTEvent
Expand Down
53 changes: 52 additions & 1 deletion Source/ARTPresenceMessage.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

#import "ARTPresenceMessage+Private.h"

NSString *const ARTPresenceMessageException = @"ARTPresenceMessageException";
NSString *const ARTAblyMessageInvalidPresenceId = @"Received presence message id is invalid %@";

@implementation ARTPresenceMessage

- (instancetype)init {
Expand All @@ -31,7 +34,7 @@ - (NSString *)description {
NSMutableString *description = [[super description] mutableCopy];
[description deleteCharactersInRange:NSMakeRange(description.length - (description.length>2 ? 2:0), 2)];
[description appendFormat:@",\n"];
[description appendFormat:@" action: %lu\n", (unsigned long)self.action];
[description appendFormat:@" action: %lu,\n", (unsigned long)self.action];
[description appendFormat:@" syncSessionId: %lu\n", (unsigned long)self.syncSessionId];
[description appendFormat:@"}"];
return description;
Expand All @@ -52,6 +55,54 @@ - (BOOL)isEqualToPresenceMessage:(ARTPresenceMessage *)presence {
return haveEqualConnectionId && haveEqualCliendId;
}

- (NSArray<NSString *> *)parseId {
if (!self.id) {
return nil;
}
NSArray<NSString *> *idParts = [self.id componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@":"]];
if (idParts.count != 3) {
[ARTException raise:ARTPresenceMessageException format:ARTAblyMessageInvalidPresenceId, self.id];
}
return idParts;
}

- (BOOL)isSynthesized {
NSString *connectionId = [[self parseId] objectAtIndex:0];
return ![connectionId isEqualToString:self.connectionId];
}

- (NSInteger)msgSerialFromId {
NSInteger msgSerial = [[[self parseId] objectAtIndex:1] integerValue];
return msgSerial;
}

- (NSInteger)indexFromId {
NSInteger index = [[[self parseId] objectAtIndex:2] integerValue];
return index;
}

- (BOOL)isNewerThan:(ARTPresenceMessage *)latest {
if (latest == nil) {
return YES;
}

if ([self isSynthesized] || [latest isSynthesized]) {
return !self.timestamp || [latest.timestamp timeIntervalSince1970] <= [self.timestamp timeIntervalSince1970];
}

NSInteger currentMsgSerial = [self msgSerialFromId];
NSInteger currentIndex = [self indexFromId];
NSInteger latestMsgSerial = [latest msgSerialFromId];
NSInteger latestIndex = [latest indexFromId];

if (currentMsgSerial == latestMsgSerial) {
return currentIndex > latestIndex;
}
else {
return currentMsgSerial > latestMsgSerial;
}
}

#pragma mark - NSObject

- (BOOL)isEqual:(id)object {
Expand Down
6 changes: 0 additions & 6 deletions Source/ARTRealtime.m
Original file line number Diff line number Diff line change
Expand Up @@ -550,12 +550,6 @@ - (ARTEventListener *)transitionSideEffects:(ARTConnectionStateChange *)stateCha

if ([self shouldSendEvents]) {
[self sendQueuedMessages];
// For every Channel
for (ARTRealtimeChannel* channel in self.channels.nosyncIterable) {
if (channel.state_nosync == ARTRealtimeChannelSuspended) {
[channel _attach:nil];
}
Copy link
Contributor Author

@ricardopereira ricardopereira Mar 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this fix in this PR because the RTP17b test was failing because of this. Discussion here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ricardopereira With this removal, does the library comply with RTN15c3? "when a resume request results in a CONNECTED with a new connectionId ... The client library should initiate an attach for channels that are in the SUSPENDED state. For all channels in the ATTACHING or ATTACHED state, the client library should fail any previously queued messages for that channel and initiate a new attach..."

cf my comment in slack "you shouldn't be sending an attached after a successful resume, only for an unsuccessful one" -- you still need to do the latter.

(From that test log it looks like the connection moved to suspended, but it's not obvious why from the log, the resume was successful so I can't see any reason for it to do so)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this removal, does the library comply with RTN15c3?

@SimonWoolf Yes it does. When I implemented RTN15c3, I forgot to remove that bit because the new implementation already handle that case. You can see that the channel will reattach

if (![message.connectionId isEqualToString:self.connection.id_nosync]) {
[self.logger warn:@"R:%p ARTRealtime: connection has reconnected, but resume failed. Reattaching any attached channels", self];
// Reattach all channels
for (ARTRealtimeChannel *channel in self.channels.nosyncIterable) {
[channel reattachWithReason:message.error callback:nil];
}
_resuming = false;
}
and it does check the SUSPENDED channel state
case ARTRealtimeChannelSuspended:
[self.realtime.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) attached or suspended and will reattach", _realtime, self, self.name];
break;
case ARTRealtimeChannelAttaching:
[self.realtime.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) already attaching", _realtime, self, self.name];
if (callback) [_attachedEventEmitter once:callback];
return;
default:
break;
}
[self internalAttach:callback withReason:reason storeErrorInfo:false];

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(From that test log it looks like the connection moved to suspended, but it's not obvious why from the log, the resume was successful so I can't see any reason for it to do so)

@SimonWoolf That's because the test forces to suspend:

// Inject an additional member into the myMember set, then force a suspended state
client.simulateSuspended(beforeSuspension: { done in
channel.presenceMap.localMembers.add(additionalMember)
done()
})
expect(client.connection.state).to(equal(.suspended))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to remove that bit because the new implementation already handle that case

👍

Though looking at reattachWithReason, it looks to me like it's reattaching channels in every state except attaching. So (1) it's starting an attach for channels in initialized, detached, or failed, which it really shouldn't be; and (2) it isn't initiating a new attach for a channel in the attaching state (we've just started a completely new connection, so any previously-in-progress attach needs to be started again). Or am I misreading it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SimonWoolf You're not misreading. It seems so, it's not respecting RTN15c3. Issue has been created: #847

}
} else if (![self shouldQueueEvents]) {
ARTStatus *channelStatus = status;
if (!channelStatus) {
Expand Down
2 changes: 2 additions & 0 deletions Source/ARTRealtimeChannel+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)broadcastPresence:(ARTPresenceMessage *)pm;
- (void)detachChannel:(ARTStatus *)status;

- (void)sync;
- (void)sync:(nullable void (^)(ARTErrorInfo *_Nullable))callback;
- (void)requestContinueSync;

@end
Expand Down
70 changes: 62 additions & 8 deletions Source/ARTRealtimeChannel.m
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,57 @@ - (void)internalPostMessages:(id)data callback:(void (^)(ARTErrorInfo *__art_nul
} ART_TRY_OR_MOVE_TO_FAILED_END
}

- (void)sync {
[self sync:nil];
}

- (void)sync:(void (^)(ARTErrorInfo *__art_nullable error))callback {
if (callback) {
void (^userCallback)(ARTErrorInfo *__art_nullable error) = callback;
callback = ^(ARTErrorInfo *__art_nullable error) {
ART_EXITING_ABLY_CODE(self->_realtime.rest);
dispatch_async(self->_userQueue, ^{
userCallback(error);
});
};
}

ART_TRY_OR_MOVE_TO_FAILED_START(_realtime) {
switch (self.state) {
case ARTRealtimeChannelInitialized:
case ARTRealtimeChannelDetaching:
case ARTRealtimeChannelDetached: {
ARTErrorInfo *error = [ARTErrorInfo createWithCode:40000 message:@"unable to sync to channel; not attached"];
[self.logger logWithError:error];
if (callback) callback(error);
return;
}
default:
break;
}

[self.logger verbose:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) requesting a sync operation", _realtime, self, self.name];

ARTProtocolMessage *msg = [[ARTProtocolMessage alloc] init];
msg.action = ARTProtocolMessageSync;
msg.channel = self.name;
msg.channelSerial = self.presenceMap.syncChannelSerial;

[self.presenceMap startSync];
[self.realtime send:msg sentCallback:^(ARTErrorInfo *error) {
if (error) {
[self.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) SYNC request failed with %@", self->_realtime, self, self.name, error];
[self.presenceMap endSync];
callback(error);
}
else {
[self.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) SYNC requested with success", self->_realtime, self, self.name];
callback(nil);
}
} ackCallback:nil];
} ART_TRY_OR_MOVE_TO_FAILED_END
}

- (void)requestContinueSync {
ART_TRY_OR_MOVE_TO_FAILED_START(_realtime) {
[self.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) requesting to continue sync operation after reconnect using msgSerial %lld and channelSerial %@", _realtime, self, self.name, self.presenceMap.syncMsgSerial, self.presenceMap.syncChannelSerial];
Expand All @@ -174,9 +225,13 @@ - (void)requestContinueSync {
msg.channelSerial = self.presenceMap.syncChannelSerial;
msg.channel = self.name;

[self.realtime send:msg sentCallback:nil ackCallback:^(ARTStatus *status) {
[self.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) continue sync, status is %@", self->_realtime, self, self.name, status];
}];
[self.presenceMap startSync];
[self.realtime send:msg sentCallback:^(ARTErrorInfo *error) {
[self.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) continue sync, error is %@", self->_realtime, self, self.name, error];
if (error) {
[self.presenceMap endSync];
}
} ackCallback:nil];
} ART_TRY_OR_MOVE_TO_FAILED_END
}

Expand Down Expand Up @@ -537,7 +592,7 @@ - (void)emit:(ARTChannelEvent)event with:(ARTChannelStateChange *)data {

- (void)transition:(ARTRealtimeChannelState)state status:(ARTStatus *)status {
ART_TRY_OR_MOVE_TO_FAILED_START(_realtime) {
[self.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) channel state transitions to %tu - %@", _realtime, self, self.name, state, ARTRealtimeChannelStateToStr(state)];
[self.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) channel state transitions from %tu - %@ to %tu - %@", _realtime, self, self.name, self.state_nosync, ARTRealtimeChannelStateToStr(self.state_nosync), state, ARTRealtimeChannelStateToStr(state)];
ARTChannelStateChange *stateChange = [[ARTChannelStateChange alloc] initWithCurrent:state previous:self.state_nosync event:(ARTChannelEvent)state reason:status.errorInfo];
self.state = state;

Expand Down Expand Up @@ -640,7 +695,6 @@ - (void)setAttached:(ARTProtocolMessage *)message {

if (message.hasPresence) {
[self.presenceMap startSync];
[self.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) PresenceMap sync started", _realtime, self, self.name];
}
else if ([self.presenceMap.members count] > 0 || [self.presenceMap.localMembers count] > 0) {
if (!message.resumed) {
Expand Down Expand Up @@ -779,11 +833,11 @@ - (void)onPresence:(ARTProtocolMessage *)message {
[self.logger error:@"R:%p C:%p (%@) %@", _realtime, self, self.name, errorInfo.message];
}
}

if (!presence.timestamp) {
presence.timestamp = message.timestamp;
}

if (!presence.id) {
presence.id = [NSString stringWithFormat:@"%@:%d", message.id, i];
}
Expand All @@ -804,7 +858,6 @@ - (void)onSync:(ARTProtocolMessage *)message {

if (!self.presenceMap.syncInProgress) {
[self.presenceMap startSync];
[self.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) PresenceMap sync started", _realtime, self, self.name];
}

for (int i=0; i<[message.presence count]; i++) {
Expand All @@ -816,6 +869,7 @@ - (void)onSync:(ARTProtocolMessage *)message {

if ([self isLastChannelSerial:message.channelSerial]) {
[self.presenceMap endSync];
self.presenceMap.syncChannelSerial = nil;
[self.logger debug:__FILE__ line:__LINE__ message:@"R:%p C:%p (%@) PresenceMap sync ended", _realtime, self, self.name];
}
} ART_TRY_OR_MOVE_TO_FAILED_END
Expand Down
Loading