FTrackedQueryManager.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <FirebaseCore/FIRLogger.h>
  17. #import "FTrackedQueryManager.h"
  18. #import "FImmutableTree.h"
  19. #import "FLevelDBStorageEngine.h"
  20. #import "FUtilities.h"
  21. #import "FTrackedQuery.h"
  22. #import "FPruneForest.h"
  23. #import "FClock.h"
  24. #import "FUtilities.h"
  25. #import "FCachePolicy.h"
  26. @interface FTrackedQueryManager ()
  27. @property (nonatomic, strong) FImmutableTree *trackedQueryTree;
  28. @property (nonatomic, strong) id<FStorageEngine> storageEngine;
  29. @property (nonatomic, strong) id<FClock> clock;
  30. @property (nonatomic) NSUInteger currentQueryId;
  31. @end
  32. @implementation FTrackedQueryManager
  33. - (id)initWithStorageEngine:(id<FStorageEngine>)storageEngine clock:(id<FClock>)clock {
  34. self = [super init];
  35. if (self != nil) {
  36. self->_storageEngine = storageEngine;
  37. self->_clock = clock;
  38. self->_trackedQueryTree = [FImmutableTree empty];
  39. NSTimeInterval lastUse = [clock currentTime];
  40. NSArray *trackedQueries = [self.storageEngine loadTrackedQueries];
  41. [trackedQueries enumerateObjectsUsingBlock:^(FTrackedQuery *trackedQuery, NSUInteger idx, BOOL *stop) {
  42. self.currentQueryId = MAX(trackedQuery.queryId + 1, self.currentQueryId);
  43. if (trackedQuery.isActive) {
  44. trackedQuery = [[trackedQuery setActiveState:NO] updateLastUse:lastUse];
  45. FFDebug(@"I-RDB081001", @"Setting active query %lu from previous app start inactive", (unsigned long)trackedQuery.queryId);
  46. [self.storageEngine saveTrackedQuery:trackedQuery];
  47. }
  48. [self cacheTrackedQuery:trackedQuery];
  49. }];
  50. }
  51. return self;
  52. }
  53. + (void)assertValidTrackedQuery:(FQuerySpec *)query {
  54. NSAssert(!query.loadsAllData || query.isDefault, @"Can't have tracked non-default query that loads all data");
  55. }
  56. + (FQuerySpec *)normalizeQuery:(FQuerySpec *)query {
  57. return query.loadsAllData ? [FQuerySpec defaultQueryAtPath:query.path] : query;
  58. }
  59. - (FTrackedQuery *)findTrackedQuery:(FQuerySpec *)query {
  60. query = [FTrackedQueryManager normalizeQuery:query];
  61. NSDictionary *set = [self.trackedQueryTree valueAtPath:query.path];
  62. return set[query.params];
  63. }
  64. - (void)removeTrackedQuery:(FQuerySpec *)query {
  65. query = [FTrackedQueryManager normalizeQuery:query];
  66. FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
  67. NSAssert(trackedQuery, @"Tracked query must exist to be removed!");
  68. [self.storageEngine removeTrackedQuery:trackedQuery.queryId];
  69. NSMutableDictionary *trackedQueries = [self.trackedQueryTree valueAtPath:query.path];
  70. [trackedQueries removeObjectForKey:query.params];
  71. }
  72. - (void)setQueryActive:(FQuerySpec *)query {
  73. [self setQueryActive:YES forQuery:query];
  74. }
  75. - (void)setQueryInactive:(FQuerySpec *)query {
  76. [self setQueryActive:NO forQuery:query];
  77. }
  78. - (void)setQueryActive:(BOOL)isActive forQuery:(FQuerySpec *)query {
  79. query = [FTrackedQueryManager normalizeQuery:query];
  80. FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
  81. // Regardless of whether it's now active or no langer active, we update the lastUse time
  82. NSTimeInterval lastUse = [self.clock currentTime];
  83. if (trackedQuery != nil) {
  84. trackedQuery = [[trackedQuery updateLastUse:lastUse] setActiveState:isActive];
  85. [self.storageEngine saveTrackedQuery:trackedQuery];
  86. } else {
  87. NSAssert(isActive, @"If we're setting the query to inactive, we should already be tracking it!");
  88. trackedQuery = [[FTrackedQuery alloc] initWithId:self.currentQueryId++
  89. query:query
  90. lastUse:lastUse
  91. isActive:isActive];
  92. [self.storageEngine saveTrackedQuery:trackedQuery];
  93. }
  94. [self cacheTrackedQuery:trackedQuery];
  95. }
  96. - (void)setQueryComplete:(FQuerySpec *)query {
  97. query = [FTrackedQueryManager normalizeQuery:query];
  98. FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
  99. if (!trackedQuery) {
  100. // We might have removed a query and pruned it before we got the complete message from the server...
  101. FFWarn(@"I-RDB081002", @"Trying to set a query complete that is not tracked!");
  102. } else if (!trackedQuery.isComplete) {
  103. trackedQuery = [trackedQuery setComplete];
  104. [self.storageEngine saveTrackedQuery:trackedQuery];
  105. [self cacheTrackedQuery:trackedQuery];
  106. } else {
  107. // Nothing to do, already marked complete
  108. }
  109. }
  110. - (void)setQueriesCompleteAtPath:(FPath *)path {
  111. [[self.trackedQueryTree subtreeAtPath:path] forEach:^(FPath *childPath, NSDictionary *trackedQueries) {
  112. [trackedQueries enumerateKeysAndObjectsUsingBlock:^(FQueryParams *parms, FTrackedQuery *trackedQuery, BOOL *stop) {
  113. if (!trackedQuery.isComplete) {
  114. FTrackedQuery *newTrackedQuery = [trackedQuery setComplete];
  115. [self.storageEngine saveTrackedQuery:newTrackedQuery];
  116. [self cacheTrackedQuery:newTrackedQuery];
  117. }
  118. }];
  119. }];
  120. }
  121. - (BOOL)isQueryComplete:(FQuerySpec *)query {
  122. if ([self isIncludedInDefaultCompleteQuery:query]) {
  123. return YES;
  124. } else if (query.loadsAllData) {
  125. // We didn't find a default complete query, so must not be complete.
  126. return NO;
  127. } else {
  128. NSDictionary *trackedQueries = [self.trackedQueryTree valueAtPath:query.path];
  129. return [trackedQueries[query.params] isComplete];
  130. }
  131. }
  132. - (BOOL)hasActiveDefaultQueryAtPath:(FPath *)path {
  133. return [self.trackedQueryTree rootMostValueOnPath:path matching:^BOOL(NSDictionary *trackedQueries) {
  134. return [trackedQueries[[FQueryParams defaultInstance]] isActive];
  135. }] != nil;
  136. }
  137. - (void)ensureCompleteTrackedQueryAtPath:(FPath *)path {
  138. FQuerySpec *query = [FQuerySpec defaultQueryAtPath:path];
  139. if (![self isIncludedInDefaultCompleteQuery:query]) {
  140. FTrackedQuery *trackedQuery = [self findTrackedQuery:query];
  141. if (trackedQuery == nil) {
  142. trackedQuery = [[FTrackedQuery alloc] initWithId:self.currentQueryId++
  143. query:query
  144. lastUse:[self.clock currentTime]
  145. isActive:NO
  146. isComplete:YES];
  147. } else {
  148. NSAssert(!trackedQuery.isComplete, @"This should have been handled above!");
  149. trackedQuery = [trackedQuery setComplete];
  150. }
  151. [self.storageEngine saveTrackedQuery:trackedQuery];
  152. [self cacheTrackedQuery:trackedQuery];
  153. }
  154. }
  155. - (BOOL)isIncludedInDefaultCompleteQuery:(FQuerySpec *)query {
  156. return [self.trackedQueryTree findRootMostMatchingPath:query.path predicate:^BOOL(NSDictionary *trackedQueries) {
  157. return [trackedQueries[[FQueryParams defaultInstance]] isComplete];
  158. }] != nil;
  159. }
  160. - (void)cacheTrackedQuery:(FTrackedQuery *)query {
  161. [FTrackedQueryManager assertValidTrackedQuery:query.query];
  162. NSMutableDictionary *trackedDict = [self.trackedQueryTree valueAtPath:query.query.path];
  163. if (trackedDict == nil) {
  164. trackedDict = [NSMutableDictionary dictionary];
  165. self.trackedQueryTree = [self.trackedQueryTree setValue:trackedDict atPath:query.query.path];
  166. }
  167. trackedDict[query.query.params] = query;
  168. }
  169. - (NSUInteger) numberOfQueriesToPrune:(id<FCachePolicy>)cachePolicy prunableCount:(NSUInteger)numPrunable {
  170. NSUInteger numPercent = (NSUInteger)ceilf(numPrunable * [cachePolicy percentOfQueriesToPruneAtOnce]);
  171. NSUInteger maxToKeep = [cachePolicy maxNumberOfQueriesToKeep];
  172. NSUInteger numMax = (numPrunable > maxToKeep) ? numPrunable - maxToKeep : 0;
  173. // Make sure we get below number of max queries to prune
  174. return MAX(numMax, numPercent);
  175. }
  176. - (FPruneForest *)pruneOldQueries:(id<FCachePolicy>)cachePolicy {
  177. NSMutableArray *pruneableQueries = [NSMutableArray array];
  178. NSMutableArray *unpruneableQueries = [NSMutableArray array];
  179. [self.trackedQueryTree forEach:^(FPath *path, NSDictionary *trackedQueries) {
  180. [trackedQueries enumerateKeysAndObjectsUsingBlock:^(FQueryParams *params, FTrackedQuery *trackedQuery, BOOL *stop) {
  181. if (!trackedQuery.isActive) {
  182. [pruneableQueries addObject:trackedQuery];
  183. } else {
  184. [unpruneableQueries addObject:trackedQuery];
  185. }
  186. }];
  187. }];
  188. [pruneableQueries sortUsingComparator:^NSComparisonResult(FTrackedQuery *q1, FTrackedQuery *q2) {
  189. if (q1.lastUse < q2.lastUse) {
  190. return NSOrderedAscending;
  191. } else if (q1.lastUse > q2.lastUse) {
  192. return NSOrderedDescending;
  193. } else {
  194. return NSOrderedSame;
  195. }
  196. }];
  197. __block FPruneForest *pruneForest = [FPruneForest empty];
  198. NSUInteger numToPrune = [self numberOfQueriesToPrune:cachePolicy prunableCount:pruneableQueries.count];
  199. // TODO: do in transaction
  200. for (NSUInteger i = 0; i < numToPrune; i++) {
  201. FTrackedQuery *toPrune = pruneableQueries[i];
  202. pruneForest = [pruneForest prunePath:toPrune.query.path];
  203. [self removeTrackedQuery:toPrune.query];
  204. }
  205. // Keep the rest of the prunable queries
  206. for (NSUInteger i = numToPrune; i < pruneableQueries.count; i++) {
  207. FTrackedQuery *toKeep = pruneableQueries[i];
  208. pruneForest = [pruneForest keepPath:toKeep.query.path];
  209. }
  210. // Also keep unprunable queries
  211. [unpruneableQueries enumerateObjectsUsingBlock:^(FTrackedQuery *toKeep, NSUInteger idx, BOOL *stop) {
  212. pruneForest = [pruneForest keepPath:toKeep.query.path];
  213. }];
  214. return pruneForest;
  215. }
  216. - (NSUInteger)numberOfPrunableQueries {
  217. __block NSUInteger count = 0;
  218. [self.trackedQueryTree forEach:^(FPath *path, NSDictionary *trackedQueries) {
  219. [trackedQueries enumerateKeysAndObjectsUsingBlock:^(FQueryParams *params, FTrackedQuery *trackedQuery, BOOL *stop) {
  220. if (!trackedQuery.isActive) {
  221. count++;
  222. }
  223. }];
  224. }];
  225. return count;
  226. }
  227. - (NSSet *)filteredQueryIdsAtPath:(FPath *)path {
  228. NSDictionary *queries = [self.trackedQueryTree valueAtPath:path];
  229. if (queries) {
  230. NSMutableSet *ids = [NSMutableSet set];
  231. [queries enumerateKeysAndObjectsUsingBlock:^(FQueryParams *params, FTrackedQuery *query, BOOL *stop) {
  232. if (!query.query.loadsAllData) {
  233. [ids addObject:@(query.queryId)];
  234. }
  235. }];
  236. return ids;
  237. } else {
  238. return [NSSet set];
  239. }
  240. }
  241. - (NSSet *)knownCompleteChildrenAtPath:(FPath *)path {
  242. NSAssert(![self isQueryComplete:[FQuerySpec defaultQueryAtPath:path]], @"Path is fully complete");
  243. NSMutableSet *completeChildren = [NSMutableSet set];
  244. // First, get complete children from any queries at this location.
  245. NSSet *queryIds = [self filteredQueryIdsAtPath:path];
  246. [queryIds enumerateObjectsUsingBlock:^(NSNumber *queryId, BOOL *stop) {
  247. NSSet *keys = [self.storageEngine trackedQueryKeysForQuery:[queryId unsignedIntegerValue]];
  248. [completeChildren unionSet:keys];
  249. }];
  250. // Second, get any complete default queries immediately below us.
  251. [[[self.trackedQueryTree subtreeAtPath:path] children] enumerateKeysAndObjectsUsingBlock:^(NSString *childKey, FImmutableTree *childTree, BOOL *stop) {
  252. if ([childTree.value[[FQueryParams defaultInstance]] isComplete]) {
  253. [completeChildren addObject:childKey];
  254. }
  255. }];
  256. return completeChildren;
  257. }
  258. - (void)verifyCache {
  259. NSArray *storedTrackedQueries = [self.storageEngine loadTrackedQueries];
  260. NSMutableArray *trackedQueries = [NSMutableArray array];
  261. [self.trackedQueryTree forEach:^(FPath *path, NSDictionary *queryDict) {
  262. [trackedQueries addObjectsFromArray:queryDict.allValues];
  263. }];
  264. NSComparator comparator = ^NSComparisonResult(FTrackedQuery *q1, FTrackedQuery *q2) {
  265. if (q1.queryId < q2.queryId) {
  266. return NSOrderedAscending;
  267. } else if (q1.queryId > q2.queryId) {
  268. return NSOrderedDescending;
  269. } else {
  270. return NSOrderedSame;
  271. }
  272. };
  273. [trackedQueries sortUsingComparator:comparator];
  274. storedTrackedQueries = [storedTrackedQueries sortedArrayUsingComparator:comparator];
  275. if (![trackedQueries isEqualToArray:storedTrackedQueries]) {
  276. [NSException raise:NSInternalInconsistencyException format:@"Tracked queries and queries stored on disk don't match"];
  277. }
  278. }
  279. @end