FSTLRUGarbageCollectorTests.mm 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. /*
  2. * Copyright 2018 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 "Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.h"
  17. #import <XCTest/XCTest.h>
  18. #include <unordered_set>
  19. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  20. #import "Firestore/Source/Core/FSTTypes.h"
  21. #import "Firestore/Source/Local/FSTLRUGarbageCollector.h"
  22. #import "Firestore/Source/Local/FSTMutationQueue.h"
  23. #import "Firestore/Source/Local/FSTPersistence.h"
  24. #import "Firestore/Source/Local/FSTQueryCache.h"
  25. #import "Firestore/Source/Local/FSTRemoteDocumentCache.h"
  26. #import "Firestore/Source/Model/FSTDocument.h"
  27. #import "Firestore/Source/Model/FSTFieldValue.h"
  28. #import "Firestore/Source/Model/FSTMutation.h"
  29. #import "Firestore/Source/Util/FSTClasses.h"
  30. #include "Firestore/core/src/firebase/firestore/auth/user.h"
  31. #include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
  32. #include "Firestore/core/src/firebase/firestore/model/precondition.h"
  33. #include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
  34. #include "absl/strings/str_cat.h"
  35. using firebase::firestore::auth::User;
  36. using firebase::firestore::model::DocumentKey;
  37. using firebase::firestore::model::DocumentKeyHash;
  38. using firebase::firestore::model::DocumentKeySet;
  39. using firebase::firestore::model::Precondition;
  40. namespace testutil = firebase::firestore::testutil;
  41. NS_ASSUME_NONNULL_BEGIN
  42. @implementation FSTLRUGarbageCollectorTests {
  43. FSTTargetID _previousTargetID;
  44. int _previousDocNum;
  45. FSTObjectValue *_testValue;
  46. FSTObjectValue *_bigObjectValue;
  47. id<FSTPersistence> _persistence;
  48. id<FSTQueryCache> _queryCache;
  49. id<FSTRemoteDocumentCache> _documentCache;
  50. id<FSTMutationQueue> _mutationQueue;
  51. FSTLRUGarbageCollector *_gc;
  52. FSTListenSequenceNumber _initialSequenceNumber;
  53. User _user;
  54. }
  55. - (void)setUp {
  56. [super setUp];
  57. _previousTargetID = 500;
  58. _previousDocNum = 10;
  59. _testValue = FSTTestObjectValue(@{ @"baz" : @YES, @"ok" : @"fine" });
  60. NSString *bigString = [@"" stringByPaddingToLength:4096 withString:@"a" startingAtIndex:0];
  61. _bigObjectValue = FSTTestObjectValue(@{@"BigProperty" : bigString});
  62. _user = User("user");
  63. }
  64. - (BOOL)isTestBaseClass {
  65. return ([self class] == [FSTLRUGarbageCollectorTests class]);
  66. }
  67. - (void)newTestResources {
  68. HARD_ASSERT(_persistence == nil, "Persistence already created");
  69. _persistence = [self newPersistence];
  70. _queryCache = [_persistence queryCache];
  71. _documentCache = [_persistence remoteDocumentCache];
  72. _mutationQueue = [_persistence mutationQueueForUser:_user];
  73. _initialSequenceNumber = _persistence.run("start querycache", [&]() -> FSTListenSequenceNumber {
  74. [_mutationQueue start];
  75. _gc = ((id<FSTLRUDelegate>)_persistence.referenceDelegate).gc;
  76. return _persistence.currentSequenceNumber;
  77. });
  78. }
  79. - (id<FSTPersistence>)newPersistence {
  80. @throw FSTAbstractMethodException(); // NOLINT
  81. }
  82. #pragma mark - helpers
  83. - (FSTListenSequenceNumber)sequenceNumberForQueryCount:(int)queryCount {
  84. return _persistence.run("gc", [&]() -> FSTListenSequenceNumber {
  85. return [_gc sequenceNumberForQueryCount:queryCount];
  86. });
  87. }
  88. - (int)queryCountForPercentile:(int)percentile {
  89. return _persistence.run("query count",
  90. [&]() -> int { return [_gc queryCountForPercentile:percentile]; });
  91. }
  92. - (int)removeQueriesThroughSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
  93. liveQueries:(NSDictionary<NSNumber *, FSTQueryData *> *)liveQueries {
  94. return _persistence.run("gc", [&]() -> int {
  95. return [_gc removeQueriesUpThroughSequenceNumber:sequenceNumber liveQueries:liveQueries];
  96. });
  97. }
  98. // Removes documents that are not part of a target or a mutation and have a sequence number
  99. // less than or equal to the given sequence number.
  100. - (int)removeOrphanedDocumentsThroughSequenceNumber:(FSTListenSequenceNumber)sequenceNumber {
  101. return _persistence.run("gc", [&]() -> int {
  102. return [_gc removeOrphanedDocumentsThroughSequenceNumber:sequenceNumber];
  103. });
  104. }
  105. - (FSTQueryData *)nextTestQuery {
  106. FSTTargetID targetID = ++_previousTargetID;
  107. FSTListenSequenceNumber listenSequenceNumber = _persistence.currentSequenceNumber;
  108. FSTQuery *query = FSTTestQuery(absl::StrCat("path", targetID));
  109. return [[FSTQueryData alloc] initWithQuery:query
  110. targetID:targetID
  111. listenSequenceNumber:listenSequenceNumber
  112. purpose:FSTQueryPurposeListen];
  113. }
  114. - (FSTQueryData *)addNextQueryInTransaction {
  115. FSTQueryData *queryData = [self nextTestQuery];
  116. [_queryCache addQueryData:queryData];
  117. return queryData;
  118. }
  119. - (void)updateTargetInTransaction:(FSTQueryData *)queryData {
  120. NSData *token = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
  121. FSTQueryData *updated =
  122. [queryData queryDataByReplacingSnapshotVersion:queryData.snapshotVersion
  123. resumeToken:token
  124. sequenceNumber:_persistence.currentSequenceNumber];
  125. [_queryCache updateQueryData:updated];
  126. }
  127. - (FSTQueryData *)addNextQuery {
  128. return _persistence.run("adding query",
  129. [&]() -> FSTQueryData * { return [self addNextQueryInTransaction]; });
  130. }
  131. // Simulates a document being mutated and then having that mutation ack'd.
  132. // Since the document is not in a mutation queue any more, there is
  133. // potentially nothing keeping it live. We mark it with the current sequence number
  134. // so it can be collected later.
  135. - (DocumentKey)markADocumentEligibleForGC {
  136. DocumentKey key = [self nextTestDocKey];
  137. [self markDocumentEligibleForGC:key];
  138. return key;
  139. }
  140. - (void)markDocumentEligibleForGC:(const DocumentKey &)docKey {
  141. _persistence.run("Removing mutation reference",
  142. [&]() { [self markDocumentEligibleForGCInTransaction:docKey]; });
  143. }
  144. - (DocumentKey)markADocumentEligibleForGCInTransaction {
  145. DocumentKey key = [self nextTestDocKey];
  146. [self markDocumentEligibleForGCInTransaction:key];
  147. return key;
  148. }
  149. - (void)markDocumentEligibleForGCInTransaction:(const DocumentKey &)docKey {
  150. [_persistence.referenceDelegate removeMutationReference:docKey];
  151. }
  152. - (void)addDocument:(const DocumentKey &)docKey toTarget:(FSTTargetID)targetId {
  153. [_queryCache addMatchingKeys:DocumentKeySet{docKey} forTargetID:targetId];
  154. }
  155. - (void)removeDocument:(const DocumentKey &)docKey fromTarget:(FSTTargetID)targetId {
  156. [_queryCache removeMatchingKeys:DocumentKeySet{docKey} forTargetID:targetId];
  157. }
  158. /**
  159. * Used to insert a document into the remote document cache. Use of this method should
  160. * be paired with some explanation for why it is in the cache, for instance:
  161. * - added to a target
  162. * - now has or previously had a pending mutation
  163. */
  164. - (FSTDocument *)cacheADocumentInTransaction {
  165. FSTDocument *doc = [self nextTestDocument];
  166. [_documentCache addEntry:doc];
  167. return doc;
  168. }
  169. - (FSTSetMutation *)mutationForDocument:(const DocumentKey &)docKey {
  170. return [[FSTSetMutation alloc] initWithKey:docKey
  171. value:_testValue
  172. precondition:Precondition::None()];
  173. }
  174. - (DocumentKey)nextTestDocKey {
  175. return testutil::Key("docs/doc_" + std::to_string(++_previousDocNum));
  176. }
  177. - (FSTDocument *)nextTestDocumentWithValue:(FSTObjectValue *)value {
  178. DocumentKey key = [self nextTestDocKey];
  179. FSTTestSnapshotVersion version = 2;
  180. BOOL hasMutations = NO;
  181. return [FSTDocument documentWithData:value
  182. key:key
  183. version:testutil::Version(version)
  184. hasLocalMutations:hasMutations];
  185. }
  186. - (FSTDocument *)nextTestDocument {
  187. return [self nextTestDocumentWithValue:_testValue];
  188. }
  189. #pragma mark - tests
  190. - (void)testPickSequenceNumberPercentile {
  191. if ([self isTestBaseClass]) return;
  192. const int numTestCases = 5;
  193. struct Case {
  194. // number of queries to cache
  195. int queries;
  196. // number expected to be calculated as 10%
  197. int expected;
  198. };
  199. struct Case testCases[numTestCases] = {{0, 0}, {10, 1}, {9, 0}, {50, 5}, {49, 4}};
  200. for (int i = 0; i < numTestCases; i++) {
  201. // Fill the query cache.
  202. int numQueries = testCases[i].queries;
  203. int expectedTenthPercentile = testCases[i].expected;
  204. [self newTestResources];
  205. for (int j = 0; j < numQueries; j++) {
  206. [self addNextQuery];
  207. }
  208. int tenth = [self queryCountForPercentile:10];
  209. XCTAssertEqual(expectedTenthPercentile, tenth, @"Total query count: %i", numQueries);
  210. [_persistence shutdown];
  211. _persistence = nil;
  212. }
  213. }
  214. - (void)testSequenceNumberNoQueries {
  215. if ([self isTestBaseClass]) return;
  216. // No queries... should get invalid sequence number (-1)
  217. [self newTestResources];
  218. XCTAssertEqual(kFSTListenSequenceNumberInvalid, [self sequenceNumberForQueryCount:0]);
  219. [_persistence shutdown];
  220. }
  221. - (void)testSequenceNumberForFiftyQueries {
  222. if ([self isTestBaseClass]) return;
  223. // Add 50 queries sequentially, aim to collect 10 of them.
  224. // The sequence number to collect should be 10 past the initial sequence number.
  225. [self newTestResources];
  226. for (int i = 0; i < 50; i++) {
  227. [self addNextQuery];
  228. }
  229. XCTAssertEqual(_initialSequenceNumber + 10, [self sequenceNumberForQueryCount:10]);
  230. [_persistence shutdown];
  231. }
  232. - (void)testSequenceNumberForMultipleQueriesInATransaction {
  233. if ([self isTestBaseClass]) return;
  234. // 50 queries, 9 with one transaction, incrementing from there. Should get second sequence number.
  235. [self newTestResources];
  236. _persistence.run("9 queries in a batch", [&]() {
  237. for (int i = 0; i < 9; i++) {
  238. [self addNextQueryInTransaction];
  239. }
  240. });
  241. for (int i = 9; i < 50; i++) {
  242. [self addNextQuery];
  243. }
  244. XCTAssertEqual(2 + _initialSequenceNumber, [self sequenceNumberForQueryCount:10]);
  245. [_persistence shutdown];
  246. }
  247. // Ensure that even if all of the queries are added in a single transaction, we still
  248. // pick a sequence number and GC. In this case, the initial transaction contains all of the
  249. // targets that will get GC'd, since they account for more than the first 10 targets.
  250. - (void)testAllCollectedQueriesInSingleTransaction {
  251. if ([self isTestBaseClass]) return;
  252. // 50 queries, 11 with one transaction, incrementing from there. Should get first sequence number.
  253. [self newTestResources];
  254. _persistence.run("11 queries in a transaction", [&]() {
  255. for (int i = 0; i < 11; i++) {
  256. [self addNextQueryInTransaction];
  257. }
  258. });
  259. for (int i = 11; i < 50; i++) {
  260. [self addNextQuery];
  261. }
  262. // We expect to GC the targets from the first transaction, since they account for
  263. // at least the first 10 of the targets.
  264. XCTAssertEqual(1 + _initialSequenceNumber, [self sequenceNumberForQueryCount:10]);
  265. [_persistence shutdown];
  266. }
  267. - (void)testSequenceNumbersWithMutationAndSequentialQueries {
  268. if ([self isTestBaseClass]) return;
  269. // Remove a mutated doc reference, marking it as eligible for GC.
  270. // Then add 50 queries. Should get 10 past initial (9 queries).
  271. [self newTestResources];
  272. [self markADocumentEligibleForGC];
  273. for (int i = 0; i < 50; i++) {
  274. [self addNextQuery];
  275. }
  276. XCTAssertEqual(10 + _initialSequenceNumber, [self sequenceNumberForQueryCount:10]);
  277. [_persistence shutdown];
  278. }
  279. - (void)testSequenceNumbersWithMutationsInQueries {
  280. if ([self isTestBaseClass]) return;
  281. // Add mutated docs, then add one of them to a query target so it doesn't get GC'd.
  282. // Expect 3 past the initial value: the mutations not part of a query, and two queries
  283. [self newTestResources];
  284. FSTDocument *docInQuery = [self nextTestDocument];
  285. _persistence.run("mark mutations", [&]() {
  286. // Adding 9 doc keys in a transaction. If we remove one of them, we'll have room for two actual
  287. // queries.
  288. [self markDocumentEligibleForGCInTransaction:docInQuery.key];
  289. for (int i = 0; i < 8; i++) {
  290. [self markADocumentEligibleForGCInTransaction];
  291. }
  292. });
  293. for (int i = 0; i < 49; i++) {
  294. [self addNextQuery];
  295. }
  296. _persistence.run("query with mutation", [&]() {
  297. FSTQueryData *queryData = [self addNextQueryInTransaction];
  298. // This should keep the document from getting GC'd, since it is no longer orphaned.
  299. [self addDocument:docInQuery.key toTarget:queryData.targetID];
  300. });
  301. // This should catch the remaining 8 documents, plus the first two queries we added.
  302. XCTAssertEqual(3 + _initialSequenceNumber, [self sequenceNumberForQueryCount:10]);
  303. [_persistence shutdown];
  304. }
  305. - (void)testRemoveQueriesUpThroughSequenceNumber {
  306. if ([self isTestBaseClass]) return;
  307. [self newTestResources];
  308. NSMutableDictionary<NSNumber *, FSTQueryData *> *liveQueries = [[NSMutableDictionary alloc] init];
  309. for (int i = 0; i < 100; i++) {
  310. FSTQueryData *queryData = [self addNextQuery];
  311. // Mark odd queries as live so we can test filtering out live queries.
  312. if (queryData.targetID % 2 == 1) {
  313. liveQueries[@(queryData.targetID)] = queryData;
  314. }
  315. }
  316. // GC up through 20th query, which is 20%.
  317. // Expect to have GC'd 10 targets, since every other target is live
  318. int removed =
  319. [self removeQueriesThroughSequenceNumber:20 + _initialSequenceNumber liveQueries:liveQueries];
  320. XCTAssertEqual(10, removed);
  321. // Make sure we removed the even targets with targetID <= 20.
  322. _persistence.run("verify remaining targets are > 20 or odd", [&]() {
  323. [_queryCache enumerateTargetsUsingBlock:^(FSTQueryData *queryData, BOOL *stop) {
  324. XCTAssertTrue(queryData.targetID > 20 || queryData.targetID % 2 == 1);
  325. }];
  326. });
  327. [_persistence shutdown];
  328. }
  329. - (void)testRemoveOrphanedDocuments {
  330. if ([self isTestBaseClass]) return;
  331. [self newTestResources];
  332. // Track documents we expect to be retained so we can verify post-GC.
  333. // This will contain documents associated with targets that survive GC, as well
  334. // as any documents with pending mutations.
  335. std::unordered_set<DocumentKey, DocumentKeyHash> expectedRetained;
  336. // we add two mutations later, for now track them in an array.
  337. NSMutableArray *mutations = [NSMutableArray arrayWithCapacity:2];
  338. // Add a target and add two documents to it. The documents are expected to be
  339. // retained, since their membership in the target keeps them alive.
  340. _persistence.run("add a target and add two documents to it", [&]() {
  341. // Add two documents to first target, queue a mutation on the second document
  342. FSTQueryData *queryData = [self addNextQueryInTransaction];
  343. FSTDocument *doc1 = [self cacheADocumentInTransaction];
  344. [self addDocument:doc1.key toTarget:queryData.targetID];
  345. expectedRetained.insert(doc1.key);
  346. FSTDocument *doc2 = [self cacheADocumentInTransaction];
  347. [self addDocument:doc2.key toTarget:queryData.targetID];
  348. expectedRetained.insert(doc2.key);
  349. [mutations addObject:[self mutationForDocument:doc2.key]];
  350. });
  351. // Add a second query and register a third document on it
  352. _persistence.run("second query", [&]() {
  353. FSTQueryData *queryData = [self addNextQueryInTransaction];
  354. FSTDocument *doc3 = [self cacheADocumentInTransaction];
  355. expectedRetained.insert(doc3.key);
  356. [self addDocument:doc3.key toTarget:queryData.targetID];
  357. });
  358. // cache another document and prepare a mutation on it.
  359. _persistence.run("queue a mutation", [&]() {
  360. FSTDocument *doc4 = [self cacheADocumentInTransaction];
  361. [mutations addObject:[self mutationForDocument:doc4.key]];
  362. expectedRetained.insert(doc4.key);
  363. });
  364. // Insert the mutations. These operations don't have a sequence number, they just
  365. // serve to keep the mutated documents from being GC'd while the mutations are outstanding.
  366. _persistence.run("actually register the mutations", [&]() {
  367. FIRTimestamp *writeTime = [FIRTimestamp timestamp];
  368. [_mutationQueue addMutationBatchWithWriteTime:writeTime mutations:mutations];
  369. });
  370. // Mark 5 documents eligible for GC. This simulates documents that were mutated then ack'd.
  371. // Since they were ack'd, they are no longer in a mutation queue, and there is nothing keeping
  372. // them alive.
  373. std::unordered_set<DocumentKey, DocumentKeyHash> toBeRemoved;
  374. _persistence.run("add orphaned docs (previously mutated, then ack'd)", [&]() {
  375. for (int i = 0; i < 5; i++) {
  376. FSTDocument *doc = [self cacheADocumentInTransaction];
  377. toBeRemoved.insert(doc.key);
  378. [self markDocumentEligibleForGCInTransaction:doc.key];
  379. }
  380. });
  381. // We expect only the orphaned documents, those not in a mutation or a target, to be
  382. // removed.
  383. // use a large sequence number to remove as much as possible
  384. int removed = [self removeOrphanedDocumentsThroughSequenceNumber:1000];
  385. XCTAssertEqual(toBeRemoved.size(), removed);
  386. _persistence.run("verify", [&]() {
  387. for (const DocumentKey &key : toBeRemoved) {
  388. XCTAssertNil([_documentCache entryForKey:key]);
  389. XCTAssertFalse([_queryCache containsKey:key]);
  390. }
  391. for (const DocumentKey &key : expectedRetained) {
  392. XCTAssertNotNil([_documentCache entryForKey:key], @"Missing document %s",
  393. key.ToString().c_str());
  394. }
  395. });
  396. [_persistence shutdown];
  397. }
  398. // TODO(gsoltis): write a test that includes limbo documents
  399. - (void)testRemoveTargetsThenGC {
  400. if ([self isTestBaseClass]) return;
  401. // Create 3 targets, add docs to all of them
  402. // Leave oldest target alone, it is still live
  403. // Remove newest target
  404. // Blind write 2 documents
  405. // Add one of the blind write docs to oldest target (preserves it)
  406. // Remove some documents from middle target (bumps sequence number)
  407. // Add some documents from newest target to oldest target (preserves them)
  408. // Update a doc from middle target
  409. // Remove middle target
  410. // Do a blind write
  411. // GC up to but not including the removal of the middle target
  412. //
  413. // Expect:
  414. // All docs in oldest target are still around
  415. // One blind write is gone, the first one not added to oldest target
  416. // Documents removed from middle target are gone, except ones added to oldest target
  417. // Documents from newest target are gone, except
  418. [self newTestResources];
  419. // Through the various steps, track which documents we expect to be removed vs
  420. // documents we expect to be retained.
  421. std::unordered_set<DocumentKey, DocumentKeyHash> expectedRetained;
  422. std::unordered_set<DocumentKey, DocumentKeyHash> expectedRemoved;
  423. // Add oldest target, 5 documents, and add those documents to the target.
  424. // This target will not be removed, so all documents that are part of it will
  425. // be retained.
  426. FSTQueryData *oldestTarget =
  427. _persistence.run("Add oldest target and docs", [&]() -> FSTQueryData * {
  428. FSTQueryData *queryData = [self addNextQueryInTransaction];
  429. for (int i = 0; i < 5; i++) {
  430. FSTDocument *doc = [self cacheADocumentInTransaction];
  431. expectedRetained.insert(doc.key);
  432. [self addDocument:doc.key toTarget:queryData.targetID];
  433. }
  434. return queryData;
  435. });
  436. // Add middle target and docs. Some docs will be removed from this target later,
  437. // which we track here.
  438. DocumentKeySet middleDocsToRemove;
  439. // This will be the document in this target that gets an update later
  440. DocumentKey middleDocToUpdate;
  441. FSTQueryData *middleTarget =
  442. _persistence.run("Add middle target and docs", [&]() -> FSTQueryData * {
  443. FSTQueryData *middleTarget = [self addNextQueryInTransaction];
  444. // these docs will be removed from this target later, triggering a bump
  445. // to their sequence numbers. Since they will not be a part of the target, we
  446. // expect them to be removed.
  447. for (int i = 0; i < 2; i++) {
  448. FSTDocument *doc = [self cacheADocumentInTransaction];
  449. expectedRemoved.insert(doc.key);
  450. [self addDocument:doc.key toTarget:middleTarget.targetID];
  451. middleDocsToRemove = middleDocsToRemove.insert(doc.key);
  452. }
  453. // these docs stay in this target and only this target. There presence in this
  454. // target prevents them from being GC'd, so they are also expected to be retained.
  455. for (int i = 2; i < 4; i++) {
  456. FSTDocument *doc = [self cacheADocumentInTransaction];
  457. expectedRetained.insert(doc.key);
  458. [self addDocument:doc.key toTarget:middleTarget.targetID];
  459. }
  460. // This doc stays in this target, but gets updated.
  461. {
  462. FSTDocument *doc = [self cacheADocumentInTransaction];
  463. expectedRetained.insert(doc.key);
  464. [self addDocument:doc.key toTarget:middleTarget.targetID];
  465. middleDocToUpdate = doc.key;
  466. }
  467. return middleTarget;
  468. });
  469. // Add the newest target and add 5 documents to it. Some of those documents will
  470. // additionally be added to the oldest target, which will cause those documents to
  471. // be retained. The remaining documents are expected to be removed, since this target
  472. // will be removed.
  473. DocumentKeySet newestDocsToAddToOldest;
  474. _persistence.run("Add newest target and docs", [&]() {
  475. FSTQueryData *newestTarget = [self addNextQueryInTransaction];
  476. // These documents are only in this target. They are expected to be removed
  477. // because this target will also be removed.
  478. for (int i = 0; i < 3; i++) {
  479. FSTDocument *doc = [self cacheADocumentInTransaction];
  480. expectedRemoved.insert(doc.key);
  481. [self addDocument:doc.key toTarget:newestTarget.targetID];
  482. }
  483. // docs to add to the oldest target in addition to this target. They will be retained
  484. for (int i = 3; i < 5; i++) {
  485. FSTDocument *doc = [self cacheADocumentInTransaction];
  486. expectedRetained.insert(doc.key);
  487. [self addDocument:doc.key toTarget:newestTarget.targetID];
  488. newestDocsToAddToOldest = newestDocsToAddToOldest.insert(doc.key);
  489. }
  490. });
  491. // 2 doc writes, add one of them to the oldest target.
  492. _persistence.run("2 doc writes, add one of them to the oldest target", [&]() {
  493. // write two docs and have them ack'd by the server. can skip mutation queue
  494. // and set them in document cache. Add potentially orphaned first, also add one
  495. // doc to a target.
  496. FSTDocument *doc1 = [self cacheADocumentInTransaction];
  497. [self markDocumentEligibleForGCInTransaction:doc1.key];
  498. [self updateTargetInTransaction:oldestTarget];
  499. [self addDocument:doc1.key toTarget:oldestTarget.targetID];
  500. // doc1 should be retained by being added to oldestTarget.
  501. expectedRetained.insert(doc1.key);
  502. FSTDocument *doc2 = [self cacheADocumentInTransaction];
  503. [self markDocumentEligibleForGCInTransaction:doc2.key];
  504. // nothing is keeping doc2 around, it should be removed
  505. expectedRemoved.insert(doc2.key);
  506. });
  507. // Remove some documents from the middle target.
  508. _persistence.run("Remove some documents from the middle target", [&]() {
  509. [self updateTargetInTransaction:middleTarget];
  510. for (const DocumentKey &docKey : middleDocsToRemove) {
  511. [self removeDocument:docKey fromTarget:middleTarget.targetID];
  512. }
  513. });
  514. // Add a couple docs from the newest target to the oldest (preserves them past the point where
  515. // newest was removed)
  516. // upperBound is the sequence number right before middleTarget is updated, then removed.
  517. FSTListenSequenceNumber upperBound = _persistence.run(
  518. "Add a couple docs from the newest target to the oldest", [&]() -> FSTListenSequenceNumber {
  519. [self updateTargetInTransaction:oldestTarget];
  520. for (const DocumentKey &docKey : newestDocsToAddToOldest) {
  521. [self addDocument:docKey toTarget:oldestTarget.targetID];
  522. }
  523. return _persistence.currentSequenceNumber;
  524. });
  525. // Update a doc in the middle target
  526. _persistence.run("Update a doc in the middle target", [&]() {
  527. FSTTestSnapshotVersion version = 3;
  528. FSTDocument *doc = [FSTDocument documentWithData:_testValue
  529. key:middleDocToUpdate
  530. version:testutil::Version(version)
  531. hasLocalMutations:NO];
  532. [_documentCache addEntry:doc];
  533. [self updateTargetInTransaction:middleTarget];
  534. });
  535. // middleTarget removed here, no update needed
  536. // Write a doc and get an ack, not part of a target.
  537. _persistence.run("Write a doc and get an ack, not part of a target", [&]() {
  538. FSTDocument *doc = [self cacheADocumentInTransaction];
  539. // Mark it as eligible for GC, but this is after our upper bound for what we will collect.
  540. [self markDocumentEligibleForGCInTransaction:doc.key];
  541. // This should be retained, it's too new to get removed.
  542. expectedRetained.insert(doc.key);
  543. });
  544. // Finally, do the garbage collection, up to but not including the removal of middleTarget
  545. NSDictionary<NSNumber *, FSTQueryData *> *liveQueries =
  546. @{ @(oldestTarget.targetID) : oldestTarget };
  547. int queriesRemoved = [self removeQueriesThroughSequenceNumber:upperBound liveQueries:liveQueries];
  548. XCTAssertEqual(1, queriesRemoved, @"Expected to remove newest target");
  549. int docsRemoved = [self removeOrphanedDocumentsThroughSequenceNumber:upperBound];
  550. XCTAssertEqual(expectedRemoved.size(), docsRemoved);
  551. _persistence.run("verify results", [&]() {
  552. for (const DocumentKey &key : expectedRemoved) {
  553. XCTAssertNil([_documentCache entryForKey:key], @"Did not expect to find %s in document cache",
  554. key.ToString().c_str());
  555. XCTAssertFalse([_queryCache containsKey:key], @"Did not expect to find %s in queryCache",
  556. key.ToString().c_str());
  557. }
  558. for (const DocumentKey &key : expectedRetained) {
  559. XCTAssertNotNil([_documentCache entryForKey:key], @"Expected to find %s in document cache",
  560. key.ToString().c_str());
  561. }
  562. });
  563. [_persistence shutdown];
  564. }
  565. @end
  566. NS_ASSUME_NONNULL_END