Files
firebase-ios-sdk/Firestore/Source/Local/FSTMemoryPersistence.mm
T
Greg Soltis 16a5210b3c Merging the LRU branch (#1600)
* Switch to reference delegates for garbage collection (#1410)

* Bump sequence number on resume token refresh

* Style

* Fix comment formatting

* Add FSTReferenceDelegate definition and documentation

* Add methods to return nil for delegates, wire up inMemoryPins

* Add hook for removing a reference

* Start work on reference delegates

* Fix up tests to support adding documents at a sequence number

* Implement removing references

* Remove from target when dropped from local view

* Fix warning

* Add hooks for removal from mutation queue

* Add hooks for limbo document updates

* Style

* Drop commented-out code

* Fixup after merging master

* Start importing delegate implementations

* Memory LRU tests pass

* Make memory lru work, start fixing up lru tests. All with hidden sequence numbers

* Most LRU tests passing w/ memory

* Big LRU test passes w/ memory

* Leveldb LRU tests pass

* All passing except local store tests

* All tests pass

* Fix localstore tests

* Revert removeQueryData change

* Remove collectGarbage calls

* Remove NoOp GC

* More cleanup

* Refactor FSTHelpers

* Cleanup and commenting

* Comments and cleanup

* Comments

* Drop nullable for reference delegates, and old persistence method on spec tests

* Fix missing sequence bump, remove unneeded targetID

* Style

* Drop commented-out code

* Satisfy the linters and copyrighters, etc.

* Drop more commented-out code

* Fix import -> include, NSUInteger -> int

* Start work on test comments

* FSTDocumentKey -> DocumentKey

* Sample of LRU test refactor (#1424)

* Sample of test refactor

* Drop errant 'struct' keyword

* More test refactoring

* Make gc a member variable too

* Tests refactored to use member variables

* Assign delegates procedurally, rather than passing in a block

* Fix up FSTLevelDB a little

* listens -> listenTargets

* Rename GCEnabled

* remove isSentinel

* reword comment

* Add case for non-eager GC to test

* Style

* Cleaning the lint trap

* Clean up LRU tests (#1433)

* Add copious helpers and some docs to lru tests

* Style

* Method name refactor, add comment

* Drop FSTGarbageCollector and FSTEagerGarbageCollector (#1440)

* Drop FSTGarbageCollector and FSTEagerGarbageCollector

* Drop unused containsKey from FSTLevelDBMutationQueue

* Style

* Include sequence number in FSTQueryData hash and isEqual (#1568)

* Include sequence number in FSTQueryData hash and isEqual methods

* Style

* Switch to util::Hash

* Fix preserving query cache metadata (#1569)

* Add failing test

* Move the restart tests to the leveldb test suite

* Style

* Add new persistence test helper

* Fix test failure

* Review feedback

* Style
2018-07-27 13:58:11 -07:00

369 lines
11 KiB
Plaintext

/*
* Copyright 2017 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 "Firestore/Source/Local/FSTMemoryPersistence.h"
#include <memory>
#include <unordered_map>
#include <unordered_set>
#import "Firestore/Source/Core/FSTListenSequence.h"
#import "Firestore/Source/Local/FSTMemoryMutationQueue.h"
#import "Firestore/Source/Local/FSTMemoryQueryCache.h"
#import "Firestore/Source/Local/FSTMemoryRemoteDocumentCache.h"
#import "Firestore/Source/Local/FSTReferenceSet.h"
#include "absl/memory/memory.h"
#include "Firestore/core/src/firebase/firestore/auth/user.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
using firebase::firestore::auth::HashUser;
using firebase::firestore::auth::User;
using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::DocumentKeyHash;
using MutationQueues = std::unordered_map<User, FSTMemoryMutationQueue *, HashUser>;
NS_ASSUME_NONNULL_BEGIN
@interface FSTMemoryPersistence ()
@property(nonatomic, readonly) MutationQueues &mutationQueues;
@property(nonatomic, assign, getter=isStarted) BOOL started;
// Make this property writable so we can wire up a delegate.
@property(nonatomic, strong) id<FSTReferenceDelegate> referenceDelegate;
@end
@implementation FSTMemoryPersistence {
/**
* The FSTQueryCache representing the persisted cache of queries.
*
* Note that this is retained here to make it easier to write tests affecting both the in-memory
* and LevelDB-backed persistence layers. Tests can create a new FSTLocalStore wrapping this
* FSTPersistence instance and this will make the in-memory persistence layer behave as if it
* were actually persisting values.
*/
FSTMemoryQueryCache *_queryCache;
/** The FSTRemoteDocumentCache representing the persisted cache of remote documents. */
FSTMemoryRemoteDocumentCache *_remoteDocumentCache;
FSTTransactionRunner _transactionRunner;
id<FSTReferenceDelegate> _referenceDelegate;
}
+ (instancetype)persistenceWithEagerGC {
FSTMemoryPersistence *persistence = [[FSTMemoryPersistence alloc] init];
persistence.referenceDelegate =
[[FSTMemoryEagerReferenceDelegate alloc] initWithPersistence:persistence];
return persistence;
}
+ (instancetype)persistenceWithLRUGC {
FSTMemoryPersistence *persistence = [[FSTMemoryPersistence alloc] init];
persistence.referenceDelegate =
[[FSTMemoryLRUReferenceDelegate alloc] initWithPersistence:persistence];
return persistence;
}
- (instancetype)init {
if (self = [super init]) {
_queryCache = [[FSTMemoryQueryCache alloc] initWithPersistence:self];
_remoteDocumentCache = [[FSTMemoryRemoteDocumentCache alloc] init];
}
return self;
}
- (void)setReferenceDelegate:(id<FSTReferenceDelegate>)referenceDelegate {
_referenceDelegate = referenceDelegate;
id delegate = _referenceDelegate;
if ([delegate conformsToProtocol:@protocol(FSTTransactional)]) {
_transactionRunner.SetBackingPersistence((id<FSTTransactional>)_referenceDelegate);
}
}
- (BOOL)start:(NSError **)error {
// No durable state to read on startup.
HARD_ASSERT(!self.isStarted, "FSTMemoryPersistence double-started!");
self.started = YES;
return YES;
}
- (void)shutdown {
// No durable state to ensure is closed on shutdown.
HARD_ASSERT(self.isStarted, "FSTMemoryPersistence shutdown without start!");
self.started = NO;
}
- (id<FSTReferenceDelegate>)referenceDelegate {
return _referenceDelegate;
}
- (FSTListenSequenceNumber)currentSequenceNumber {
return [_referenceDelegate currentSequenceNumber];
}
- (const FSTTransactionRunner &)run {
return _transactionRunner;
}
- (id<FSTMutationQueue>)mutationQueueForUser:(const User &)user {
id<FSTMutationQueue> queue = _mutationQueues[user];
if (!queue) {
queue = [[FSTMemoryMutationQueue alloc] initWithPersistence:self];
_mutationQueues[user] = queue;
}
return queue;
}
- (id<FSTQueryCache>)queryCache {
return _queryCache;
}
- (id<FSTRemoteDocumentCache>)remoteDocumentCache {
return _remoteDocumentCache;
}
@end
@implementation FSTMemoryLRUReferenceDelegate {
// This delegate should have the same lifetime as the persistence layer, but mark as
// weak to avoid retain cycle.
__weak FSTMemoryPersistence *_persistence;
std::unordered_map<DocumentKey, FSTListenSequenceNumber, DocumentKeyHash> _sequenceNumbers;
FSTReferenceSet *_additionalReferences;
FSTLRUGarbageCollector *_gc;
FSTListenSequence *_listenSequence;
FSTListenSequenceNumber _currentSequenceNumber;
}
- (instancetype)initWithPersistence:(FSTMemoryPersistence *)persistence {
if (self = [super init]) {
_persistence = persistence;
_gc =
[[FSTLRUGarbageCollector alloc] initWithQueryCache:[_persistence queryCache] delegate:self];
_currentSequenceNumber = kFSTListenSequenceNumberInvalid;
// Theoretically this is always 0, since this is all in-memory...
FSTListenSequenceNumber highestSequenceNumber =
_persistence.queryCache.highestListenSequenceNumber;
_listenSequence = [[FSTListenSequence alloc] initStartingAfter:highestSequenceNumber];
}
return self;
}
- (FSTLRUGarbageCollector *)gc {
return _gc;
}
- (FSTListenSequenceNumber)currentSequenceNumber {
HARD_ASSERT(_currentSequenceNumber != kFSTListenSequenceNumberInvalid,
"Asking for a sequence number outside of a transaction");
return _currentSequenceNumber;
}
- (void)addInMemoryPins:(FSTReferenceSet *)set {
// Technically can't assert this, due to restartWithNoopGarbageCollector (for now...)
// FSTAssert(_additionalReferences == nil, @"Overwriting additional references");
_additionalReferences = set;
}
- (void)removeTarget:(FSTQueryData *)queryData {
FSTQueryData *updated = [queryData queryDataByReplacingSnapshotVersion:queryData.snapshotVersion
resumeToken:queryData.resumeToken
sequenceNumber:_currentSequenceNumber];
[_persistence.queryCache updateQueryData:updated];
}
- (void)limboDocumentUpdated:(const DocumentKey &)key {
_sequenceNumbers[key] = self.currentSequenceNumber;
}
- (void)startTransaction:(absl::string_view)label {
_currentSequenceNumber = [_listenSequence next];
}
- (void)commitTransaction {
_currentSequenceNumber = kFSTListenSequenceNumberInvalid;
}
- (void)enumerateTargetsUsingBlock:(void (^)(FSTQueryData *queryData, BOOL *stop))block {
return [_persistence.queryCache enumerateTargetsUsingBlock:block];
}
- (void)enumerateMutationsUsingBlock:
(void (^)(const DocumentKey &key, FSTListenSequenceNumber sequenceNumber, BOOL *stop))block {
BOOL stop = NO;
for (auto it = _sequenceNumbers.begin(); !stop && it != _sequenceNumbers.end(); ++it) {
FSTListenSequenceNumber sequenceNumber = it->second;
const DocumentKey &key = it->first;
if (![_persistence.queryCache containsKey:key]) {
block(key, sequenceNumber, &stop);
}
}
}
- (int)removeTargetsThroughSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
liveQueries:(NSDictionary<NSNumber *, FSTQueryData *> *)liveQueries {
return [_persistence.queryCache removeQueriesThroughSequenceNumber:sequenceNumber
liveQueries:liveQueries];
}
- (int)removeOrphanedDocumentsThroughSequenceNumber:(FSTListenSequenceNumber)upperBound {
return [(FSTMemoryRemoteDocumentCache *)_persistence.remoteDocumentCache
removeOrphanedDocuments:self
throughSequenceNumber:upperBound];
}
- (void)addReference:(const DocumentKey &)key {
_sequenceNumbers[key] = self.currentSequenceNumber;
}
- (void)removeReference:(const DocumentKey &)key {
_sequenceNumbers[key] = self.currentSequenceNumber;
}
- (BOOL)mutationQueuesContainKey:(const DocumentKey &)key {
const MutationQueues &queues = [_persistence mutationQueues];
for (auto it = queues.begin(); it != queues.end(); ++it) {
if ([it->second containsKey:key]) {
return YES;
}
}
return NO;
}
- (void)removeMutationReference:(const DocumentKey &)key {
_sequenceNumbers[key] = self.currentSequenceNumber;
}
- (BOOL)isPinnedAtSequenceNumber:(FSTListenSequenceNumber)upperBound
document:(const DocumentKey &)key {
if ([self mutationQueuesContainKey:key]) {
return YES;
}
if ([_additionalReferences containsKey:key]) {
return YES;
}
if ([_persistence.queryCache containsKey:key]) {
return YES;
}
auto it = _sequenceNumbers.find(key);
if (it != _sequenceNumbers.end() && it->second > upperBound) {
return YES;
}
return NO;
}
@end
@implementation FSTMemoryEagerReferenceDelegate {
std::unique_ptr<std::unordered_set<DocumentKey, DocumentKeyHash>> _orphaned;
// This delegate should have the same lifetime as the persistence layer, but mark as
// weak to avoid retain cycle.
__weak FSTMemoryPersistence *_persistence;
FSTReferenceSet *_additionalReferences;
}
- (instancetype)initWithPersistence:(FSTMemoryPersistence *)persistence {
if (self = [super init]) {
_persistence = persistence;
}
return self;
}
- (FSTListenSequenceNumber)currentSequenceNumber {
return kFSTListenSequenceNumberInvalid;
}
- (void)addInMemoryPins:(FSTReferenceSet *)set {
// We should be able to assert that _additionalReferences is nil, but due to restarts in spec
// tests it would fail.
_additionalReferences = set;
}
- (void)removeTarget:(FSTQueryData *)queryData {
for (const DocumentKey &docKey :
[_persistence.queryCache matchingKeysForTargetID:queryData.targetID]) {
_orphaned->insert(docKey);
}
[_persistence.queryCache removeQueryData:queryData];
}
- (void)addReference:(const DocumentKey &)key {
_orphaned->erase(key);
}
- (void)removeReference:(const DocumentKey &)key {
_orphaned->insert(key);
}
- (void)removeMutationReference:(const DocumentKey &)key {
_orphaned->insert(key);
}
- (BOOL)isReferenced:(const DocumentKey &)key {
if ([[_persistence queryCache] containsKey:key]) {
return YES;
}
if ([self mutationQueuesContainKey:key]) {
return YES;
}
if ([_additionalReferences containsKey:key]) {
return YES;
}
return NO;
}
- (void)limboDocumentUpdated:(const DocumentKey &)key {
if ([self isReferenced:key]) {
_orphaned->erase(key);
} else {
_orphaned->insert(key);
}
}
- (void)startTransaction:(__unused absl::string_view)label {
_orphaned = absl::make_unique<std::unordered_set<DocumentKey, DocumentKeyHash>>();
}
- (BOOL)mutationQueuesContainKey:(const DocumentKey &)key {
const MutationQueues &queues = [_persistence mutationQueues];
for (auto it = queues.begin(); it != queues.end(); ++it) {
if ([it->second containsKey:key]) {
return YES;
}
}
return NO;
}
- (void)commitTransaction {
for (auto it = _orphaned->begin(); it != _orphaned->end(); ++it) {
const DocumentKey key = *it;
if (![self isReferenced:key]) {
[[_persistence remoteDocumentCache] removeEntryForKey:key];
}
}
_orphaned.reset();
}
@end
NS_ASSUME_NONNULL_END