| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- /*
- * 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 "FirebaseDatabase/Sources/Persistence/FPersistenceManager.h"
- #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
- #import "FirebaseDatabase/Sources/Core/View/FCacheNode.h"
- #import "FirebaseDatabase/Sources/FClock.h"
- #import "FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.h"
- #import "FirebaseDatabase/Sources/Persistence/FPruneForest.h"
- #import "FirebaseDatabase/Sources/Persistence/FTrackedQuery.h"
- #import "FirebaseDatabase/Sources/Persistence/FTrackedQueryManager.h"
- #import "FirebaseDatabase/Sources/Snapshot/FIndexedNode.h"
- #import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
- @interface FPersistenceManager ()
- @property(nonatomic, strong) id<FStorageEngine> storageEngine;
- @property(nonatomic, strong) id<FCachePolicy> cachePolicy;
- @property(nonatomic, strong) FTrackedQueryManager *trackedQueryManager;
- @property(nonatomic) NSUInteger serverCacheUpdatesSinceLastPruneCheck;
- @end
- @implementation FPersistenceManager
- - (id)initWithStorageEngine:(id<FStorageEngine>)storageEngine
- cachePolicy:(id<FCachePolicy>)cachePolicy {
- self = [super init];
- if (self != nil) {
- self->_storageEngine = storageEngine;
- self->_cachePolicy = cachePolicy;
- self->_trackedQueryManager = [[FTrackedQueryManager alloc]
- initWithStorageEngine:self.storageEngine
- clock:[FSystemClock clock]];
- }
- return self;
- }
- - (void)close {
- [self.storageEngine close];
- self.storageEngine = nil;
- self.trackedQueryManager = nil;
- }
- - (void)saveUserOverwrite:(id<FNode>)node
- atPath:(FPath *)path
- writeId:(NSUInteger)writeId {
- [self.storageEngine saveUserOverwrite:node atPath:path writeId:writeId];
- }
- - (void)saveUserMerge:(FCompoundWrite *)merge
- atPath:(FPath *)path
- writeId:(NSUInteger)writeId {
- [self.storageEngine saveUserMerge:merge atPath:path writeId:writeId];
- }
- - (void)removeUserWrite:(NSUInteger)writeId {
- [self.storageEngine removeUserWrite:writeId];
- }
- - (void)removeAllUserWrites {
- [self.storageEngine removeAllUserWrites];
- }
- - (NSArray *)userWrites {
- return [self.storageEngine userWrites];
- }
- - (FCacheNode *)serverCacheForQuery:(FQuerySpec *)query {
- NSSet *trackedKeys;
- BOOL complete;
- // TODO[offline]: Should we use trackedKeys to find out if this location is
- // a child of a complete query?
- if ([self.trackedQueryManager isQueryComplete:query]) {
- complete = YES;
- FTrackedQuery *trackedQuery =
- [self.trackedQueryManager findTrackedQuery:query];
- if (!query.loadsAllData && trackedQuery.isComplete) {
- trackedKeys = [self.storageEngine
- trackedQueryKeysForQuery:trackedQuery.queryId];
- } else {
- trackedKeys = nil;
- }
- } else {
- complete = NO;
- trackedKeys =
- [self.trackedQueryManager knownCompleteChildrenAtPath:query.path];
- }
- id<FNode> node;
- if (trackedKeys != nil) {
- node = [self.storageEngine serverCacheForKeys:trackedKeys
- atPath:query.path];
- } else {
- node = [self.storageEngine serverCacheAtPath:query.path];
- }
- FIndexedNode *indexedNode = [FIndexedNode indexedNodeWithNode:node
- index:query.index];
- return [[FCacheNode alloc] initWithIndexedNode:indexedNode
- isFullyInitialized:complete
- isFiltered:(trackedKeys != nil)];
- }
- - (void)updateServerCacheWithNode:(id<FNode>)node forQuery:(FQuerySpec *)query {
- BOOL merge = !query.loadsAllData;
- [self.storageEngine updateServerCache:node atPath:query.path merge:merge];
- [self setQueryComplete:query];
- [self doPruneCheckAfterServerUpdate];
- }
- - (void)updateServerCacheWithMerge:(FCompoundWrite *)merge
- atPath:(FPath *)path {
- [self.storageEngine updateServerCacheWithMerge:merge atPath:path];
- [self doPruneCheckAfterServerUpdate];
- }
- - (void)applyUserMerge:(FCompoundWrite *)merge
- toServerCacheAtPath:(FPath *)path {
- // TODO[offline]: rework this to be more efficient
- [merge enumerateWrites:^(FPath *relativePath, id<FNode> node, BOOL *stop) {
- [self applyUserWrite:node toServerCacheAtPath:[path child:relativePath]];
- }];
- }
- - (void)applyUserWrite:(id<FNode>)write toServerCacheAtPath:(FPath *)path {
- // This is a hack to guess whether we already cached this because we got a
- // server data update for this write via an existing active default query.
- // If we didn't, then we'll manually cache this and add a tracked query to
- // mark it complete and keep it cached. Unfortunately this is just a guess
- // and it's possible that we *did* get an update (e.g. via a filtered query)
- // and by overwriting the cache here, we'll actually store an incorrect
- // value (e.g. in the case that we wrote a ServerValue.TIMESTAMP and the
- // server resolved it to a different value).
- // TODO[offline]: Consider reworking.
- if (![self.trackedQueryManager hasActiveDefaultQueryAtPath:path]) {
- [self.storageEngine updateServerCache:write atPath:path merge:NO];
- [self.trackedQueryManager ensureCompleteTrackedQueryAtPath:path];
- }
- }
- - (void)setQueryComplete:(FQuerySpec *)query {
- if (query.loadsAllData) {
- [self.trackedQueryManager setQueriesCompleteAtPath:query.path];
- } else {
- [self.trackedQueryManager setQueryComplete:query];
- }
- }
- - (void)setQueryActive:(FQuerySpec *)spec {
- [self.trackedQueryManager setQueryActive:spec];
- }
- - (void)setQueryInactive:(FQuerySpec *)spec {
- [self.trackedQueryManager setQueryInactive:spec];
- }
- - (void)doPruneCheckAfterServerUpdate {
- self.serverCacheUpdatesSinceLastPruneCheck++;
- if ([self.cachePolicy
- shouldCheckCacheSize:self.serverCacheUpdatesSinceLastPruneCheck]) {
- FFDebug(@"I-RDB078001", @"Reached prune check threshold. Checking...");
- NSDate *date = [NSDate date];
- self.serverCacheUpdatesSinceLastPruneCheck = 0;
- BOOL canPrune = YES;
- NSUInteger cacheSize =
- [self.storageEngine serverCacheEstimatedSizeInBytes];
- FFDebug(@"I-RDB078002", @"Server cache size: %lu",
- (unsigned long)cacheSize);
- while (canPrune &&
- [self.cachePolicy
- shouldPruneCacheWithSize:cacheSize
- numberOfTrackedQueries:self.trackedQueryManager
- .numberOfPrunableQueries]) {
- FPruneForest *pruneForest =
- [self.trackedQueryManager pruneOldQueries:self.cachePolicy];
- if (pruneForest.prunesAnything) {
- [self.storageEngine pruneCache:pruneForest
- atPath:[FPath empty]];
- } else {
- canPrune = NO;
- }
- cacheSize = [self.storageEngine serverCacheEstimatedSizeInBytes];
- FFDebug(@"I-RDB078003", @"Cache size after pruning: %lu",
- (unsigned long)cacheSize);
- }
- FFDebug(@"I-RDB078004", @"Pruning round took %fms",
- [date timeIntervalSinceNow] * -1000);
- }
- }
- - (void)setTrackedQueryKeys:(NSSet *)keys forQuery:(FQuerySpec *)query {
- NSAssert(!query.loadsAllData,
- @"We should only track keys for filtered queries");
- FTrackedQuery *trackedQuery =
- [self.trackedQueryManager findTrackedQuery:query];
- NSAssert(trackedQuery.isActive,
- @"We only expect tracked keys for currently-active queries.");
- [self.storageEngine setTrackedQueryKeys:keys
- forQueryId:trackedQuery.queryId];
- }
- - (void)updateTrackedQueryKeysWithAddedKeys:(NSSet *)added
- removedKeys:(NSSet *)removed
- forQuery:(FQuerySpec *)query {
- NSAssert(!query.loadsAllData,
- @"We should only track keys for filtered queries");
- FTrackedQuery *trackedQuery =
- [self.trackedQueryManager findTrackedQuery:query];
- NSAssert(trackedQuery.isActive,
- @"We only expect tracked keys for currently-active queries.");
- [self.storageEngine
- updateTrackedQueryKeysWithAddedKeys:added
- removedKeys:removed
- forQueryId:trackedQuery.queryId];
- }
- @end
|