diff --git a/Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.m b/Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.m new file mode 100644 index 00000000000..da4dc11819b --- /dev/null +++ b/Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.m @@ -0,0 +1,312 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "Firestore/Source/Core/FSTTimestamp.h" +#import "Firestore/Source/Local/FSTMemoryMutationQueue.h" +#import "Firestore/Source/Local/FSTMemoryQueryCache.h" +#import "Firestore/Source/Local/FSTMemoryRemoteDocumentCache.h" +#import "Firestore/Source/Local/FSTQueryData.h" +#import "Firestore/Source/Local/FSTWriteGroup.h" +#import "Firestore/Source/Model/FSTDocument.h" +#import "Firestore/Source/Model/FSTFieldValue.h" +#import "Firestore/Source/Model/FSTMutation.h" + +#import "Firestore/Example/Tests/Util/FSTHelpers.h" +#import "Firestore/Source/Local/FSTLRUGarbageCollector.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FSTLRUGarbageCollectorTests : XCTestCase +@end + +@implementation FSTLRUGarbageCollectorTests { + FSTListenSequenceNumber _previousSequenceNumber; + FSTTargetID _previousTargetID; + NSUInteger _previousDocNum; + FSTObjectValue *_testValue; +} + +- (void)setUp { + [super setUp]; + + _previousSequenceNumber = 1000; + _previousTargetID = 500; + _previousDocNum = 10; + _testValue = FSTTestObjectValue(@{ @"baz" : @YES, @"ok" : @"fine" }); +} + +- (FSTQueryData *)nextTestQuery { + FSTTargetID targetID = ++_previousTargetID; + FSTListenSequenceNumber listenSequenceNumber = ++_previousSequenceNumber; + FSTQuery *query = FSTTestQuery([NSString stringWithFormat:@"path%i", targetID]); + return [[FSTQueryData alloc] initWithQuery:query + targetID:targetID + listenSequenceNumber:listenSequenceNumber + purpose:FSTQueryPurposeListen]; +} + +- (FSTDocumentKey *)nextTestDocKey { + NSString *path = [NSString stringWithFormat:@"docs/doc_%lu", (unsigned long)++_previousDocNum]; + return FSTTestDocKey(path); +} + +- (FSTDocument *)nextTestDocument { + FSTDocumentKey *key = [self nextTestDocKey]; + FSTTestSnapshotVersion version = 2; + BOOL hasMutations = NO; + return [FSTDocument documentWithData:_testValue + key:key + version:FSTTestVersion(version) + hasLocalMutations:hasMutations]; +} + +- (void)testPickSequenceNumberPercentile { + const int numTestCases = 5; + // 0 - number of queries to cache, 1 - number expected to be calculated as 10% + struct Case { + int queries; + int expected; + }; + struct Case testCases[numTestCases] = {{0, 0}, {10, 1}, {9, 0}, {50, 5}, {49, 4}}; + + for (int i = 0; i < numTestCases; i++) { + // Fill the query cache. + FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"]; + int numQueries = testCases[i].queries; + int expectedTenthPercentile = testCases[i].expected; + FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init]; + for (int j = 0; j < numQueries; j++) { + [queryCache addQueryData:[self nextTestQuery] group:group]; + } + + FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache]; + FSTListenSequenceNumber tenth = [gc queryCountForPercentile:10]; + XCTAssertEqual(expectedTenthPercentile, tenth, @"Total query count: %i", numQueries); + } +} + +- (void)testSequenceNumberForQueryCount { + // Sequence numbers in this test start at 1001 and are incremented by one. + + // No queries... should get invalid sequence number (-1) + { + FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init]; + FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache]; + FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:0]; + XCTAssertEqual(kFSTListenSequenceNumberInvalid, highestToCollect); + } + + // 50 queries, want 10. Should get 1010. + { + _previousSequenceNumber = 1000; + FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init]; + FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache]; + FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"]; + for (int i = 0; i < 50; i++) { + [queryCache addQueryData:[self nextTestQuery] group:group]; + } + FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:10]; + XCTAssertEqual(1010, highestToCollect); + } + + // 50 queries, 9 with 1001, incrementing from there. Should get 1002. + { + _previousSequenceNumber = 1000; + FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init]; + FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache]; + FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"]; + for (int i = 0; i < 9; i++) { + [queryCache addQueryData:[self nextTestQuery] group:group]; + _previousSequenceNumber = 1000; + } + _previousSequenceNumber = 1001; + for (int i = 9; i < 50; i++) { + [queryCache addQueryData:[self nextTestQuery] group:group]; + } + FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:10]; + XCTAssertEqual(1002, highestToCollect); + } + + // 50 queries, 11 with 1001, incrementing from there. Should get 1001. + { + _previousSequenceNumber = 1000; + FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init]; + FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache]; + FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"]; + for (int i = 0; i < 11; i++) { + [queryCache addQueryData:[self nextTestQuery] group:group]; + _previousSequenceNumber = 1000; + } + _previousSequenceNumber = 1001; + for (int i = 11; i < 50; i++) { + [queryCache addQueryData:[self nextTestQuery] group:group]; + } + FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:10]; + XCTAssertEqual(1001, highestToCollect); + } + + // A mutated doc at 1000, 50 queries 1001-1050. Should get 1009. + { + _previousSequenceNumber = 1000; + FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init]; + FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache]; + FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"]; + FSTDocumentKey *key = [self nextTestDocKey]; + FSTDocumentKeySet *set = [[FSTDocumentKeySet keySet] setByAddingObject:key]; + [queryCache addMutatedDocuments:set atSequenceNumber:1000 group:group]; + for (int i = 0; i < 50; i++) { + [queryCache addQueryData:[self nextTestQuery] group:group]; + } + FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:10]; + XCTAssertEqual(1009, highestToCollect); + } + + // Add mutated docs, then add one of them to a query target so it doesn't get GC'd. + // Expect 1002. + { + _previousSequenceNumber = 1000; + FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init]; + FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache]; + FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"]; + FSTDocument *docInQuery = [self nextTestDocument]; + FSTDocumentKeySet *set = [[FSTDocumentKeySet keySet] setByAddingObject:docInQuery.key]; + FSTDocumentKeySet *docInQuerySet = set; + for (int i = 0; i < 8; i++) { + set = [set setByAddingObject:[self nextTestDocKey]]; + } + // Adding 9 doc keys at 1000. If we remove one of them, we'll have room for two actual queries. + [queryCache addMutatedDocuments:set atSequenceNumber:1000 group:group]; + for (int i = 0; i < 49; i++) { + [queryCache addQueryData:[self nextTestQuery] group:group]; + } + FSTQueryData *queryData = [self nextTestQuery]; + [queryCache addQueryData:queryData group:group]; + // This should bump one document out of the mutated documents cache. + [queryCache addMatchingKeys:docInQuerySet forTargetID:queryData.targetID group:group]; + + // This should catch the remaining 8 documents, plus the first two queries we added. + FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:10]; + XCTAssertEqual(1002, highestToCollect); + } +} + +- (void)testRemoveQueriesUpThroughSequenceNumber { + FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init]; + FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache]; + FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"]; + NSMutableDictionary *liveQueries = [[NSMutableDictionary alloc] init]; + for (int i = 0; i < 100; i++) { + FSTQueryData *queryData = [self nextTestQuery]; + // Mark odd queries as live so we can test filtering out live queries. + if (queryData.targetID % 2 == 1) { + liveQueries[@(queryData.targetID)] = queryData; + } + [queryCache addQueryData:queryData group:group]; + } + + // GC up through 1015, which is 15%. + // Expect to have GC'd 7 targets (even values of 1001-1015). + FSTWriteGroup *gcGroup = [FSTWriteGroup groupWithAction:@"gc"]; + NSUInteger removed = + [gc removeQueriesUpThroughSequenceNumber:1015 liveQueries:liveQueries group:gcGroup]; + XCTAssertEqual(7, removed); +} + +- (void)testRemoveOrphanedDocuments { + FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init]; + FSTMemoryRemoteDocumentCache *documentCache = [[FSTMemoryRemoteDocumentCache alloc] init]; + FSTMemoryMutationQueue *mutationQueue = [[FSTMemoryMutationQueue alloc] init]; + FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache]; + FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"]; + [mutationQueue startWithGroup:group]; + + // Add docs to mutation queue, as well as keep some queries. verify that correct documents are + // removed. + NSMutableSet *toBeRetained = [NSMutableSet set]; + + NSMutableArray *mutations = [NSMutableArray arrayWithCapacity:2]; + // Add two documents to first target, and register a mutation on the second one + { + FSTQueryData *queryData = [self nextTestQuery]; + [queryCache addQueryData:queryData group:group]; + FSTDocumentKeySet *keySet = [FSTImmutableSortedSet keySet]; + FSTDocument *doc1 = [self nextTestDocument]; + [documentCache addEntry:doc1 group:group]; + keySet = [keySet setByAddingObject:doc1.key]; + [toBeRetained addObject:doc1.key]; + FSTDocument *doc2 = [self nextTestDocument]; + [documentCache addEntry:doc2 group:group]; + keySet = [keySet setByAddingObject:doc2.key]; + [toBeRetained addObject:doc2.key]; + [queryCache addMatchingKeys:keySet forTargetID:queryData.targetID group:group]; + + FSTObjectValue *newValue = [[FSTObjectValue alloc] initWithDictionary:@{@"foo" : @"@bar"}]; + [mutations addObject:[[FSTSetMutation alloc] initWithKey:doc2.key + value:newValue + precondition:[FSTPrecondition none]]]; + } + + // Add one document to the second target + { + FSTQueryData *queryData = [self nextTestQuery]; + [queryCache addQueryData:queryData group:group]; + FSTDocumentKeySet *keySet = [FSTImmutableSortedSet keySet]; + FSTDocument *doc1 = [self nextTestDocument]; + [documentCache addEntry:doc1 group:group]; + keySet = [keySet setByAddingObject:doc1.key]; + [toBeRetained addObject:doc1.key]; + [queryCache addMatchingKeys:keySet forTargetID:queryData.targetID group:group]; + } + + { + FSTDocument *doc1 = [self nextTestDocument]; + [mutations addObject:[[FSTSetMutation alloc] initWithKey:doc1.key + value:doc1.data + precondition:[FSTPrecondition none]]]; + [documentCache addEntry:doc1 group:group]; + [toBeRetained addObject:doc1.key]; + } + + FSTTimestamp *writeTime = [FSTTimestamp timestamp]; + [mutationQueue addMutationBatchWithWriteTime:writeTime mutations:mutations group:group]; + + // Now add the docs we expect to get resolved. + NSUInteger expectedRemoveCount = 5; + NSMutableSet *toBeRemoved = [NSMutableSet setWithCapacity:expectedRemoveCount]; + for (int i = 0; i < expectedRemoveCount; i++) { + FSTDocument *doc = [self nextTestDocument]; + [toBeRemoved addObject:doc.key]; + [documentCache addEntry:doc group:group]; + } + + FSTWriteGroup *gcGroup = [FSTWriteGroup groupWithAction:@"gc"]; + NSUInteger removed = + [gc removeOrphanedDocuments:documentCache mutationQueue:mutationQueue group:gcGroup]; + + XCTAssertEqual(expectedRemoveCount, removed); + for (FSTDocumentKey *key in toBeRemoved) { + XCTAssertNil([documentCache entryForKey:key]); + } + for (FSTDocumentKey *key in toBeRetained) { + XCTAssertNotNil([documentCache entryForKey:key], @"Missing document %@", key); + } +} + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Firestore/Protos/objc/firestore/local/Target.pbobjc.h b/Firestore/Protos/objc/firestore/local/Target.pbobjc.h index 0672a6e4482..37707df3aec 100644 --- a/Firestore/Protos/objc/firestore/local/Target.pbobjc.h +++ b/Firestore/Protos/objc/firestore/local/Target.pbobjc.h @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google + * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Firestore/Protos/objc/firestore/local/Target.pbobjc.m b/Firestore/Protos/objc/firestore/local/Target.pbobjc.m index 567c86d8474..2c414725b0e 100644 --- a/Firestore/Protos/objc/firestore/local/Target.pbobjc.m +++ b/Firestore/Protos/objc/firestore/local/Target.pbobjc.m @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google + * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Firestore/Source/Local/FSTLRUGarbageCollector.h b/Firestore/Source/Local/FSTLRUGarbageCollector.h new file mode 100644 index 00000000000..f74ec33d1f2 --- /dev/null +++ b/Firestore/Source/Local/FSTLRUGarbageCollector.h @@ -0,0 +1,29 @@ +#import + +#import "Firestore/Source/Core/FSTTypes.h" +#import "Firestore/Source/Local/FSTQueryData.h" +#import "Firestore/Source/Local/FSTRemoteDocumentCache.h" +#import "Firestore/Source/Local/FSTWriteGroup.h" + +@protocol FSTQueryCache; + +extern const FSTListenSequenceNumber kFSTListenSequenceNumberInvalid; + +@interface FSTLRUGarbageCollector : NSObject + +- (instancetype)initWithQueryCache:(id)queryCache; + +- (NSUInteger)queryCountForPercentile:(NSUInteger)percentile; + +- (FSTListenSequenceNumber)sequenceNumberForQueryCount:(NSUInteger)queryCount; + +- (NSUInteger)removeQueriesUpThroughSequenceNumber:(FSTListenSequenceNumber)sequenceNumber + liveQueries: + (NSDictionary *)liveQueries + group:(FSTWriteGroup *)group; + +- (NSUInteger)removeOrphanedDocuments:(id)remoteDocumentCache + mutationQueue:(id)mutationQueue + group:(FSTWriteGroup *)group; + +@end \ No newline at end of file diff --git a/Firestore/Source/Local/FSTLRUGarbageCollector.mm b/Firestore/Source/Local/FSTLRUGarbageCollector.mm new file mode 100644 index 00000000000..93186716c8a --- /dev/null +++ b/Firestore/Source/Local/FSTLRUGarbageCollector.mm @@ -0,0 +1,100 @@ +#import "Firestore/Source/Local/FSTLRUGarbageCollector.h" + +#import + +#import "Firestore/Source/Local/FSTMutationQueue.h" +#import "Firestore/Source/Local/FSTQueryCache.h" + +const FSTListenSequenceNumber kFSTListenSequenceNumberInvalid = -1; + +class RollingSequenceNumberBuffer { + public: + RollingSequenceNumberBuffer(NSUInteger max_elements) + : max_elements_(max_elements), queue_(std::priority_queue()) { + } + + RollingSequenceNumberBuffer(const RollingSequenceNumberBuffer &other) = delete; + RollingSequenceNumberBuffer(RollingSequenceNumberBuffer &other) = delete; + + RollingSequenceNumberBuffer &operator=(const RollingSequenceNumberBuffer &other) = delete; + RollingSequenceNumberBuffer &operator=(RollingSequenceNumberBuffer &other) = delete; + + void AddElement(FSTListenSequenceNumber sequence_number) { + if (queue_.size() < max_elements_) { + queue_.push(sequence_number); + } else { + FSTListenSequenceNumber highestValue = queue_.top(); + if (sequence_number < highestValue) { + queue_.pop(); + queue_.push(sequence_number); + } + } + } + + FSTListenSequenceNumber max_value() const { + return queue_.top(); + } + + std::size_t size() const { + return queue_.size(); + } + + private: + std::priority_queue queue_; + const NSUInteger max_elements_; +}; + +@interface FSTLRUGarbageCollector () + +@property(nonatomic, strong, readonly) id queryCache; + +@end + +@implementation FSTLRUGarbageCollector { +} + +- (instancetype)initWithQueryCache:(id)queryCache { + self = [super init]; + if (self) { + _queryCache = queryCache; + } + return self; +} + +- (NSUInteger)queryCountForPercentile:(NSUInteger)percentile { + NSUInteger totalCount = [self.queryCache count]; + NSUInteger setSize = (NSUInteger)((percentile / 100.0f) * totalCount); + return setSize; +} + +- (FSTListenSequenceNumber)sequenceNumberForQueryCount:(NSUInteger)queryCount { + if (queryCount == 0) { + return kFSTListenSequenceNumberInvalid; + } + RollingSequenceNumberBuffer buffer(queryCount); + RollingSequenceNumberBuffer *ptr_to_buffer = &buffer; + [self.queryCache + enumerateSequenceNumbersUsingBlock:^(FSTListenSequenceNumber sequenceNumber, BOOL *stop) { + ptr_to_buffer->AddElement(sequenceNumber); + }]; + return buffer.max_value(); +} + +- (NSUInteger)removeQueriesUpThroughSequenceNumber:(FSTListenSequenceNumber)sequenceNumber + liveQueries: + (NSDictionary *)liveQueries + group:(FSTWriteGroup *)group { + return [self.queryCache removeQueriesThroughSequenceNumber:sequenceNumber + liveQueries:liveQueries + group:group]; +} + +- (NSUInteger)removeOrphanedDocuments:(id)remoteDocumentCache + mutationQueue:(id)mutationQueue + group:(FSTWriteGroup *)group { + return [remoteDocumentCache removeOrphanedDocuments:self.queryCache + mutationQueue:mutationQueue + group:group]; +} + +@end \ No newline at end of file diff --git a/Firestore/Source/Local/FSTLocalStore.mm b/Firestore/Source/Local/FSTLocalStore.mm index 8a383e5f1db..00e63e4db40 100644 --- a/Firestore/Source/Local/FSTLocalStore.mm +++ b/Firestore/Source/Local/FSTLocalStore.mm @@ -231,6 +231,9 @@ - (FSTMaybeDocumentDictionary *)acknowledgeBatchWithResult:(FSTMutationBatchResu [self releaseBatchResults:@[ batchResult ] group:group remoteDocuments:remoteDocuments]; [remoteDocuments applyToWriteGroup:group]; + [self.queryCache addMutatedDocuments:affected + atSequenceNumber:[self.listenSequence next] + group:group]; } [self.persistence commitGroup:group]; @@ -423,6 +426,8 @@ - (void)releaseQuery:(FSTQuery *)query { FSTAssert(queryData, @"Tried to release nonexistent query: %@", query); [self.localViewReferences removeReferencesForID:queryData.targetID]; + // TODO(gsoltis): kill this if statement, give GC a reference to query cache, + // call removeQueryData on GC, not queryCache. if (self.garbageCollector.isEager) { [self.queryCache removeQueryData:queryData group:group]; } diff --git a/Firestore/Source/Local/FSTMemoryQueryCache.mm b/Firestore/Source/Local/FSTMemoryQueryCache.mm index 56d5699c6c2..7462797b1bc 100644 --- a/Firestore/Source/Local/FSTMemoryQueryCache.mm +++ b/Firestore/Source/Local/FSTMemoryQueryCache.mm @@ -20,6 +20,7 @@ #import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Local/FSTQueryData.h" #import "Firestore/Source/Local/FSTReferenceSet.h" +#import "Firestore/Source/Model/FSTDocumentKey.h" NS_ASSUME_NONNULL_BEGIN @@ -28,6 +29,9 @@ @interface FSTMemoryQueryCache () /** Maps a query to the data about that query. */ @property(nonatomic, strong, readonly) NSMutableDictionary *queries; +@property(nonatomic, strong, readonly) + NSMutableDictionary *mutatedDocumentSequenceNumbers; + /** A ordered bidirectional mapping between documents and the remote target IDs. */ @property(nonatomic, strong, readonly) FSTReferenceSet *references; @@ -46,6 +50,7 @@ @implementation FSTMemoryQueryCache { - (instancetype)init { if (self = [super init]) { _queries = [NSMutableDictionary dictionary]; + _mutatedDocumentSequenceNumbers = [NSMutableDictionary dictionary]; _references = [[FSTReferenceSet alloc] init]; _lastRemoteSnapshotVersion = [FSTSnapshotVersion noVersion]; } @@ -113,11 +118,54 @@ - (nullable FSTQueryData *)queryDataForQuery:(FSTQuery *)query { return self.queries[query]; } +- (void)enumerateSequenceNumbersUsingBlock:(void (^)(FSTListenSequenceNumber sequenceNumber, + BOOL *stop))block { + [self.queries + enumerateKeysAndObjectsUsingBlock:^(FSTQuery *key, FSTQueryData *queryData, BOOL *stop) { + block(queryData.sequenceNumber, stop); + }]; + [self.mutatedDocumentSequenceNumbers + enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey *key, NSNumber *sequenceNumber, + BOOL *stop) { + block([sequenceNumber longLongValue], stop); + }]; +} + +- (NSUInteger)removeQueriesThroughSequenceNumber:(FSTListenSequenceNumber)sequenceNumber + liveQueries: + (NSDictionary *)liveQueries + group:(__unused FSTWriteGroup *)group { + NSMutableArray *toRemove = [NSMutableArray array]; + [self.queries + enumerateKeysAndObjectsUsingBlock:^(FSTQuery *query, FSTQueryData *queryData, BOOL *stop) { + if (queryData.sequenceNumber <= sequenceNumber) { + if (liveQueries[@(queryData.targetID)] == nil) { + [toRemove addObject:query]; + } + } + }]; + [self.queries removeObjectsForKeys:toRemove]; + return [toRemove count]; +} + #pragma mark Reference tracking +- (void)addMutatedDocuments:(FSTDocumentKeySet *)keys + atSequenceNumber:(FSTListenSequenceNumber)sequenceNumber + group:(__unused FSTWriteGroup *)group { + NSNumber *seqNum = @(sequenceNumber); + for (FSTDocumentKey *key in [keys objectEnumerator]) { + self.mutatedDocumentSequenceNumbers[key] = seqNum; + } +} + - (void)addMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID group:(__unused FSTWriteGroup *)group { + // We're adding docs to a target, we no longer care that they were mutated. + for (FSTDocumentKey *key in [keys objectEnumerator]) { + [self.mutatedDocumentSequenceNumbers removeObjectForKey:key]; + } [self.references addReferencesToKeys:keys forID:targetID]; } diff --git a/Firestore/Source/Local/FSTMemoryRemoteDocumentCache.mm b/Firestore/Source/Local/FSTMemoryRemoteDocumentCache.mm index 9bbc0478369..05e71326263 100644 --- a/Firestore/Source/Local/FSTMemoryRemoteDocumentCache.mm +++ b/Firestore/Source/Local/FSTMemoryRemoteDocumentCache.mm @@ -17,6 +17,8 @@ #import "Firestore/Source/Local/FSTMemoryRemoteDocumentCache.h" #import "Firestore/Source/Core/FSTQuery.h" +#import "Firestore/Source/Local/FSTMutationQueue.h" +#import "Firestore/Source/Local/FSTQueryCache.h" #import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Model/FSTDocumentDictionary.h" #import "Firestore/Source/Model/FSTDocumentKey.h" @@ -79,6 +81,22 @@ - (FSTDocumentDictionary *)documentsMatchingQuery:(FSTQuery *)query { return result; } +- (NSUInteger)removeOrphanedDocuments:(id)queryCache + mutationQueue:(id)mutationQueue + group:(FSTWriteGroup *)group { + NSUInteger count = 0; + FSTMaybeDocumentDictionary *updatedDocs = self.docs; + for (FSTDocumentKey *docKey in [self.docs keyEnumerator]) { + if ([queryCache containsKey:docKey] || [mutationQueue containsKey:docKey]) { + continue; + } + updatedDocs = [updatedDocs dictionaryByRemovingObjectForKey:docKey]; + count++; + } + self.docs = updatedDocs; + return count; +} + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Local/FSTQueryCache.h b/Firestore/Source/Local/FSTQueryCache.h index 5c43de4a9c3..74782901f2d 100644 --- a/Firestore/Source/Local/FSTQueryCache.h +++ b/Firestore/Source/Local/FSTQueryCache.h @@ -98,6 +98,18 @@ NS_ASSUME_NONNULL_BEGIN /** Removes the cached entry for the given query data (no-op if no entry exists). */ - (void)removeQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group; +- (void)enumerateSequenceNumbersUsingBlock:(void (^)(FSTListenSequenceNumber sequenceNumber, + BOOL *stop))block; + +- (NSUInteger)removeQueriesThroughSequenceNumber:(FSTListenSequenceNumber)sequenceNumber + liveQueries: + (NSDictionary *)liveQueries + group:(FSTWriteGroup *)group; + +- (void)addMutatedDocuments:(FSTDocumentKeySet *)keys + atSequenceNumber:(FSTListenSequenceNumber)sequenceNumber + group:(FSTWriteGroup *)group; + /** Returns the number of targets cached. */ - (int32_t)count; diff --git a/Firestore/Source/Local/FSTRemoteDocumentCache.h b/Firestore/Source/Local/FSTRemoteDocumentCache.h index fa42ce5fc41..c8e64d55cfd 100644 --- a/Firestore/Source/Local/FSTRemoteDocumentCache.h +++ b/Firestore/Source/Local/FSTRemoteDocumentCache.h @@ -22,6 +22,8 @@ @class FSTMaybeDocument; @class FSTQuery; @class FSTWriteGroup; +@protocol FSTMutationQueue; +@protocol FSTQueryCache; NS_ASSUME_NONNULL_BEGIN @@ -71,6 +73,9 @@ NS_ASSUME_NONNULL_BEGIN */ - (FSTDocumentDictionary *)documentsMatchingQuery:(FSTQuery *)query; +- (NSUInteger)removeOrphanedDocuments:(id)queryCache + mutationQueue:(id)mutationQueue + group:(FSTWriteGroup *)group; @end NS_ASSUME_NONNULL_END