FTrackedQueryManager.m 13 KB

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