mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
* 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
369 lines
11 KiB
Plaintext
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
|