Skip to content

Commit

Permalink
MXBackgroundService: Keep all cached sync responses until there are p…
Browse files Browse the repository at this point in the history
…rocessed by MXSession

element-hq/element-ios#4074

So that we will not lose e2ee keys received by the push extension
  • Loading branch information
manuroe committed Apr 9, 2021
1 parent ff6331c commit 82a0fbe
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 75 deletions.
27 changes: 16 additions & 11 deletions MatrixSDK/Background/MXBackgroundSyncService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -548,18 +548,23 @@ public enum MXBackgroundSyncServiceError: Error {
outdatedStore = true
}

let cachedSyncResponseSyncToken = syncResponseStoreManager.syncToken()
if let cachedSyncResponseSyncToken = cachedSyncResponseSyncToken, upToDateEventStreamToken != cachedSyncResponseSyncToken {
// syncResponseStore has obsolete data. Reset it
NSLog("[MXBackgroundSyncService] updateBackgroundServiceStoresIfNeeded: Update MXSyncResponseStoreManager. Wrong sync token: \(String(describing: cachedSyncResponseSyncToken)) instead of \(String(describing: upToDateEventStreamToken))")
outdatedStore = true
}

if outdatedStore {
// TODO: Do not clear data. We will lose e2ee keys
NSLog("[MXBackgroundSyncService] updateBackgroundServiceStoresIfNeeded: Reset MXSyncResponseStoreManager. Its sync token was \(String(describing: cachedSyncResponseSyncToken))")
syncResponseStoreManager.resetData()
if let cachedSyncResponseSyncToken = syncResponseStoreManager.syncToken() {
if upToDateEventStreamToken != cachedSyncResponseSyncToken {
// syncResponseStore has obsolete data. Reset it
NSLog("[MXBackgroundSyncService] updateBackgroundServiceStoresIfNeeded: Update MXSyncResponseStoreManager. Wrong sync token: \(String(describing: cachedSyncResponseSyncToken)) instead of \(String(describing: upToDateEventStreamToken))")
outdatedStore = true
}

if outdatedStore {
NSLog("[MXBackgroundSyncService] updateBackgroundServiceStoresIfNeeded: Mark MXSyncResponseStoreManager data as outdated. Its sync token was \(String(describing: cachedSyncResponseSyncToken))")
syncResponseStoreManager.markDataOutdated()
}
}

if syncResponseStoreManager.syncResponseStore.syncResponseIds.count == 0 {
// To avoid dead lock between processes, we write to the cryptoStore only from only one process.
// If there is no cached sync responses, it means they have been consumed by MXSession. Now is the
// right time to clean the cryptoStore.
NSLog("[MXBackgroundSyncService] updateBackgroundServiceStoresIfNeeded: Reset MXBackgroundCryptoStore")
cryptoStore.reset()
}
Expand Down
17 changes: 14 additions & 3 deletions MatrixSDK/Background/Store/MXSyncResponseStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,25 @@ public enum MXSyncResponseStoreError: Error {
func updateSyncResponse(withId id: String, syncResponse: MXCachedSyncResponse)
func deleteSyncResponse(withId id: String)

// All ids of stored sync responses.
// Sync responses are stored in chunks to save RAM when processing it
// The array order is chronological
/// All ids of valid stored sync responses.
/// Sync responses are stored in chunks to save RAM when processing it
/// The array order is chronological
var syncResponseIds: [String] { get }

/// Mark as outdated some stored sync responses
func markOutdated(syncResponseIds: [String])
/// All outdated sync responses
var outdatedSyncResponseIds: [String] { get }

/// User account data
var accountData: [String : Any]? { get set }

/// Delete all data in the store
func deleteData()
}

extension MXSyncResponseStore {
var allSyncResponseIds : [String] {
outdatedSyncResponseIds + syncResponseIds
}
}
13 changes: 7 additions & 6 deletions MatrixSDK/Background/Store/MXSyncResponseStoreManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,14 @@ public class MXSyncResponseStoreManager: NSObject {
return syncResponse
}

public func resetData() {
NSLog("[MXSyncResponseStoreManager] resetData. The sync token was \(String(describing: syncToken))")
public func markDataOutdated() {
let syncResponseIds = syncResponseStore.syncResponseIds
if syncResponseIds.count == 0 {
return
}

// Delete all the store
// TODO: Don't do that. We loose ephemeral data we will never received again in /sync requests like e2ee keys.
// It will be fixed in https://github.com/vector-im/element-ios/issues/4074
syncResponseStore.deleteData()
NSLog("[MXSyncResponseStoreManager] markDataOutdated \(syncResponseIds.count) cached sync responses. The sync token was \(String(describing: syncToken()))")
syncResponseStore.markOutdated(syncResponseIds: syncResponseIds)
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ struct MXSyncResponseStoreMetaDataModel {
/// User account data
var accountData: [String : Any]?

/// All cached sync responses, chronologically ordered
/// All valid cached sync responses, chronologically ordered
var syncResponseIds: [String] = []

/// All obsolote cached sync responses, chronologically ordered
var outdatedSyncResponseIds: [String] = []
}


Expand All @@ -38,6 +41,7 @@ extension MXSyncResponseStoreMetaDataModel: Codable {
case version
case accountData
case syncResponseIds
case outdatedSyncResponseIds
}

init(from decoder: Decoder) throws {
Expand All @@ -48,6 +52,7 @@ extension MXSyncResponseStoreMetaDataModel: Codable {
accountData = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
}
syncResponseIds = try values.decode([String].self, forKey: .syncResponseIds)
outdatedSyncResponseIds = try values.decode([String].self, forKey: .outdatedSyncResponseIds)
}

func encode(to encoder: Encoder) throws {
Expand All @@ -59,5 +64,6 @@ extension MXSyncResponseStoreMetaDataModel: Codable {
try container.encodeIfPresent(data, forKey: .accountData)
}
try container.encode(syncResponseIds, forKey: .syncResponseIds)
try container.encode(outdatedSyncResponseIds, forKey: .outdatedSyncResponseIds)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,21 +173,14 @@ public class MXSyncResponseFileStore: NSObject {

private func addSyncResponseId(id: String) {
var metadata = readMetaData()

var syncResponseIds = metadata.syncResponseIds
syncResponseIds.append(id)

metadata.syncResponseIds = syncResponseIds
metadata.syncResponseIds.append(id)
saveMetaData(metadata)
}

private func deleteSyncResponseId(id: String) {
var metadata = readMetaData()

var syncResponseIds = metadata.syncResponseIds
syncResponseIds.removeAll(where: { $0 == id })

metadata.syncResponseIds = syncResponseIds
metadata.syncResponseIds.removeAll(where: { $0 == id })
metadata.outdatedSyncResponseIds.removeAll(where: { $0 == id })
saveMetaData(metadata)
}
}
Expand Down Expand Up @@ -231,6 +224,21 @@ extension MXSyncResponseFileStore: MXSyncResponseStore {
readMetaData().syncResponseIds
}

public var outdatedSyncResponseIds: [String] {
readMetaData().outdatedSyncResponseIds
}

public func markOutdated(syncResponseIds: [String]) {
var metadata = readMetaData()
syncResponseIds.forEach { syncResponseId in
if let index = metadata.syncResponseIds.firstIndex(of: syncResponseId) {
metadata.syncResponseIds.remove(at: index)
metadata.outdatedSyncResponseIds.append(syncResponseId)
}
}
saveMetaData(metadata)
}


public var accountData: [String : Any]? {
get {
Expand All @@ -245,7 +253,7 @@ extension MXSyncResponseFileStore: MXSyncResponseStore {


public func deleteData() {
let syncResponseIds = self.syncResponseIds
let syncResponseIds = self.allSyncResponseIds
syncResponseIds.forEach { id in
deleteSyncResponse(withId: id)
}
Expand Down
110 changes: 67 additions & 43 deletions MatrixSDK/MXSession.m
Original file line number Diff line number Diff line change
Expand Up @@ -1684,69 +1684,93 @@ - (void)handleToDeviceEvent:(MXEvent *)event

- (void)handleBackgroundSyncCacheIfRequiredWithCompletion:(void (^)(void))completion
{
NSLog(@"[MXSession] handleBackgroundSyncCacheIfRequired: state %tu", _state);

MXSyncResponseFileStore *syncResponseStore = [[MXSyncResponseFileStore alloc] initWithCredentials:self.credentials];
MXSyncResponseStoreManager *syncResponseStoreManager = [[MXSyncResponseStoreManager alloc] initWithSyncResponseStore:syncResponseStore];

NSString *syncResponseStoreSyncToken = syncResponseStoreManager.syncToken;
if (syncResponseStoreSyncToken)
NSString *eventStreamToken = _store.eventStreamToken;

NSMutableArray<NSString *> *outdatedSyncResponseIds = [syncResponseStore.outdatedSyncResponseIds mutableCopy];
NSArray<NSString *> *syncResponseIds = syncResponseStore.syncResponseIds;

NSLog(@"[MXSession] handleBackgroundSyncCacheIfRequired: state %tu. outdatedSyncResponseIds: %@. syncResponseIds: %@. syncResponseStoreSyncToken: %@",
_state, @(outdatedSyncResponseIds.count), @(syncResponseIds.count) , syncResponseStoreSyncToken);

if (![syncResponseStoreSyncToken isEqualToString:eventStreamToken])
{
NSLog(@"[MXSession] handleBackgroundSyncCacheIfRequired: ");
[outdatedSyncResponseIds addObjectsFromArray:syncResponseIds];
syncResponseIds = @[];
}

if (outdatedSyncResponseIds.count == 0 && syncResponseIds.count == 0)
{
NSString *eventStreamToken = _store.eventStreamToken;
if ([syncResponseStoreSyncToken isEqualToString:eventStreamToken])
if (completion)
{
// sync response really continues from where the session left
NSArray<NSString *> *syncResponseIds = syncResponseStore.syncResponseIds;
NSLog(@"[MXSession] handleBackgroundSyncCacheIfRequired: Handle %@ cached sync responses from stream token %@", @(syncResponseIds.count), eventStreamToken);

dispatch_group_t dispatchGroup = dispatch_group_create();

for (NSString *syncResponseId in syncResponseIds)
completion();
}
return;
}

MXAsyncTaskQueue *asyncTaskQueue = [[MXAsyncTaskQueue alloc] initWithDispatchQueue:dispatch_get_main_queue() label:@"MXAsyncTaskQueue-MXSession"];

for (NSString *syncResponseId in outdatedSyncResponseIds)
{
@autoreleasepool {
MXCachedSyncResponse *cachedSyncResponse = [syncResponseStore syncResponseWithId:syncResponseId error:nil];
if (cachedSyncResponse)
{
@autoreleasepool {
MXCachedSyncResponse *cachedSyncResponse = [syncResponseStore syncResponseWithId:syncResponseId error:nil];
if (cachedSyncResponse)
{
dispatch_group_enter(dispatchGroup);
[self handleSyncResponse:cachedSyncResponse.syncResponse
completion:^{
dispatch_group_leave(dispatchGroup);
}];
}
}
[asyncTaskQueue asyncWithExecute:^(void (^ taskCompleted)(void)) {
[self handleOutdatedSyncResponse:cachedSyncResponse.syncResponse
completion:^{
taskCompleted();
}];
}];
}

dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
[syncResponseStore deleteData];

if (completion)
{
completion();
}
});
}
else
{
NSLog(@"[MXSession] handleBackgroundSyncCacheIfRequired: Ignore cache: MXSession stream token: %@. MXBackgroundSyncService cache stream token: %@", eventStreamToken, syncResponseStoreSyncToken);

// this sync response will break the continuity in session, ignore & remove it
[syncResponseStore deleteData];

if (completion)
}

for (NSString *syncResponseId in syncResponseIds)
{
@autoreleasepool {
MXCachedSyncResponse *cachedSyncResponse = [syncResponseStore syncResponseWithId:syncResponseId error:nil];
if (cachedSyncResponse)
{
completion();
[asyncTaskQueue asyncWithExecute:^(void (^ taskCompleted)(void)) {
[self handleSyncResponse:cachedSyncResponse.syncResponse
completion:^{
taskCompleted();
}];
}];
}
}
}
else
{

[asyncTaskQueue asyncWithExecute:^(void (^ taskCompleted)(void)) {
[syncResponseStore deleteData];

if (completion)
{
completion();
}
}];
}

- (void)handleOutdatedSyncResponse:(MXSyncResponse *)syncResponse
completion:(void (^)(void))completion
{
NSLog(@"[MXSession] handleOutdatedSyncResponse: %tu joined rooms, %tu invited rooms, %tu left rooms, %tu toDevice events.", syncResponse.rooms.join.count, syncResponse.rooms.invite.count, syncResponse.rooms.leave.count, syncResponse.toDevice.events.count);

// Handle only to_device events. They are sent only once by the homeserver
for (MXEvent *toDeviceEvent in syncResponse.toDevice.events)
{
[self handleToDeviceEvent:toDeviceEvent];
}

completion();
}


#pragma mark - Options
- (void)enableVoIPWithCallStack:(id<MXCallStack>)callStack
{
Expand Down

0 comments on commit 82a0fbe

Please sign in to comment.