FSTLRUGarbageCollectorTests.mm 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  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_map>
  19. #include <unordered_set>
  20. #include <utility>
  21. #include <vector>
  22. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  23. #import "Firestore/Source/Local/FSTPersistence.h"
  24. #import "Firestore/Source/Util/FSTClasses.h"
  25. #include "Firestore/core/include/firebase/firestore/timestamp.h"
  26. #include "Firestore/core/src/firebase/firestore/auth/user.h"
  27. #include "Firestore/core/src/firebase/firestore/core/query.h"
  28. #include "Firestore/core/src/firebase/firestore/local/lru_garbage_collector.h"
  29. #include "Firestore/core/src/firebase/firestore/local/mutation_queue.h"
  30. #include "Firestore/core/src/firebase/firestore/local/query_cache.h"
  31. #include "Firestore/core/src/firebase/firestore/local/query_data.h"
  32. #include "Firestore/core/src/firebase/firestore/local/reference_set.h"
  33. #include "Firestore/core/src/firebase/firestore/local/remote_document_cache.h"
  34. #include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
  35. #include "Firestore/core/src/firebase/firestore/model/mutation.h"
  36. #include "Firestore/core/src/firebase/firestore/model/precondition.h"
  37. #include "Firestore/core/src/firebase/firestore/model/set_mutation.h"
  38. #include "Firestore/core/src/firebase/firestore/model/types.h"
  39. #include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
  40. #include "absl/strings/str_cat.h"
  41. namespace core = firebase::firestore::core;
  42. namespace testutil = firebase::firestore::testutil;
  43. using firebase::Timestamp;
  44. using firebase::firestore::auth::User;
  45. using firebase::firestore::local::LruGarbageCollector;
  46. using firebase::firestore::local::LruParams;
  47. using firebase::firestore::local::LruResults;
  48. using firebase::firestore::local::MutationQueue;
  49. using firebase::firestore::local::QueryCache;
  50. using firebase::firestore::local::QueryData;
  51. using firebase::firestore::local::QueryPurpose;
  52. using firebase::firestore::local::ReferenceSet;
  53. using firebase::firestore::local::RemoteDocumentCache;
  54. using firebase::firestore::model::Document;
  55. using firebase::firestore::model::DocumentKey;
  56. using firebase::firestore::model::DocumentKeyHash;
  57. using firebase::firestore::model::DocumentKeySet;
  58. using firebase::firestore::model::DocumentState;
  59. using firebase::firestore::model::ListenSequenceNumber;
  60. using firebase::firestore::model::Mutation;
  61. using firebase::firestore::model::ObjectValue;
  62. using firebase::firestore::model::Precondition;
  63. using firebase::firestore::model::SetMutation;
  64. using firebase::firestore::model::TargetId;
  65. using testutil::Key;
  66. using testutil::Query;
  67. using testutil::Version;
  68. NS_ASSUME_NONNULL_BEGIN
  69. @implementation FSTLRUGarbageCollectorTests {
  70. TargetId _previousTargetID;
  71. int _previousDocNum;
  72. ObjectValue _testValue;
  73. ObjectValue _bigObjectValue;
  74. id<FSTPersistence> _persistence;
  75. QueryCache *_queryCache;
  76. RemoteDocumentCache *_documentCache;
  77. MutationQueue *_mutationQueue;
  78. id<FSTLRUDelegate> _lruDelegate;
  79. LruGarbageCollector *_gc;
  80. ListenSequenceNumber _initialSequenceNumber;
  81. User _user;
  82. ReferenceSet _additionalReferences;
  83. }
  84. - (void)setUp {
  85. [super setUp];
  86. _previousTargetID = 500;
  87. _previousDocNum = 10;
  88. _testValue = FSTTestObjectValue(@{@"baz" : @YES, @"ok" : @"fine"});
  89. NSString *bigString = [@"" stringByPaddingToLength:4096 withString:@"a" startingAtIndex:0];
  90. _bigObjectValue = FSTTestObjectValue(@{@"BigProperty" : bigString});
  91. _user = User("user");
  92. }
  93. - (BOOL)isTestBaseClass {
  94. return ([self class] == [FSTLRUGarbageCollectorTests class]);
  95. }
  96. - (void)newTestResourcesWithLruParams:(LruParams)lruParams {
  97. HARD_ASSERT(_persistence == nil, "Persistence already created");
  98. _persistence = [self newPersistenceWithLruParams:lruParams];
  99. [_persistence.referenceDelegate addInMemoryPins:&_additionalReferences];
  100. _queryCache = [_persistence queryCache];
  101. _documentCache = [_persistence remoteDocumentCache];
  102. _mutationQueue = [_persistence mutationQueueForUser:_user];
  103. _lruDelegate = (id<FSTLRUDelegate>)_persistence.referenceDelegate;
  104. _initialSequenceNumber = _persistence.run("start querycache", [&]() -> ListenSequenceNumber {
  105. _mutationQueue->Start();
  106. _gc = _lruDelegate.gc;
  107. return _persistence.currentSequenceNumber;
  108. });
  109. }
  110. - (void)newTestResources {
  111. [self newTestResourcesWithLruParams:LruParams::Default()];
  112. }
  113. - (id<FSTPersistence>)newPersistenceWithLruParams:(LruParams)lruParams {
  114. @throw FSTAbstractMethodException(); // NOLINT
  115. }
  116. - (BOOL)sentinelExists:(const DocumentKey &)key {
  117. @throw FSTAbstractMethodException(); // NOLINT
  118. }
  119. - (void)expectSentinelRemoved:(const DocumentKey &)key {
  120. XCTAssertFalse([self sentinelExists:key]);
  121. }
  122. #pragma mark - helpers
  123. - (ListenSequenceNumber)sequenceNumberForQueryCount:(int)queryCount {
  124. return _persistence.run(
  125. "gc", [&]() -> ListenSequenceNumber { return _gc->SequenceNumberForQueryCount(queryCount); });
  126. }
  127. - (int)queryCountForPercentile:(int)percentile {
  128. return _persistence.run("query count",
  129. [&]() -> int { return _gc->QueryCountForPercentile(percentile); });
  130. }
  131. - (int)removeQueriesThroughSequenceNumber:(ListenSequenceNumber)sequenceNumber
  132. liveQueries:
  133. (const std::unordered_map<TargetId, QueryData> &)liveQueries {
  134. return _persistence.run("gc",
  135. [&]() -> int { return _gc->RemoveTargets(sequenceNumber, liveQueries); });
  136. }
  137. // Removes documents that are not part of a target or a mutation and have a sequence number
  138. // less than or equal to the given sequence number.
  139. - (int)removeOrphanedDocumentsThroughSequenceNumber:(ListenSequenceNumber)sequenceNumber {
  140. return _persistence.run("gc",
  141. [&]() -> int { return _gc->RemoveOrphanedDocuments(sequenceNumber); });
  142. }
  143. - (QueryData)nextTestQuery {
  144. TargetId targetID = ++_previousTargetID;
  145. ListenSequenceNumber listenSequenceNumber = _persistence.currentSequenceNumber;
  146. core::Query query = Query(absl::StrCat("path", targetID));
  147. return QueryData(std::move(query), targetID, listenSequenceNumber, QueryPurpose::Listen);
  148. }
  149. - (QueryData)addNextQueryInTransaction {
  150. QueryData queryData = [self nextTestQuery];
  151. _queryCache->AddTarget(queryData);
  152. return queryData;
  153. }
  154. - (void)updateTargetInTransaction:(const QueryData &)queryData {
  155. QueryData updated = queryData.Copy(queryData.snapshot_version(), testutil::ResumeToken(7),
  156. _persistence.currentSequenceNumber);
  157. _queryCache->UpdateTarget(updated);
  158. }
  159. - (QueryData)addNextQuery {
  160. return _persistence.run("adding query", [&] { return [self addNextQueryInTransaction]; });
  161. }
  162. // Simulates a document being mutated and then having that mutation ack'd.
  163. // Since the document is not in a mutation queue any more, there is
  164. // potentially nothing keeping it live. We mark it with the current sequence number
  165. // so it can be collected later.
  166. - (DocumentKey)markADocumentEligibleForGC {
  167. DocumentKey key = [self nextTestDocKey];
  168. [self markDocumentEligibleForGC:key];
  169. return key;
  170. }
  171. - (void)markDocumentEligibleForGC:(const DocumentKey &)docKey {
  172. _persistence.run("Removing mutation reference",
  173. [&]() { [self markDocumentEligibleForGCInTransaction:docKey]; });
  174. }
  175. - (DocumentKey)markADocumentEligibleForGCInTransaction {
  176. DocumentKey key = [self nextTestDocKey];
  177. [self markDocumentEligibleForGCInTransaction:key];
  178. return key;
  179. }
  180. - (void)markDocumentEligibleForGCInTransaction:(const DocumentKey &)docKey {
  181. [_persistence.referenceDelegate removeMutationReference:docKey];
  182. }
  183. - (void)addDocument:(const DocumentKey &)docKey toTarget:(TargetId)targetId {
  184. _queryCache->AddMatchingKeys(DocumentKeySet{docKey}, targetId);
  185. }
  186. - (void)removeDocument:(const DocumentKey &)docKey fromTarget:(TargetId)targetId {
  187. _queryCache->RemoveMatchingKeys(DocumentKeySet{docKey}, targetId);
  188. }
  189. /**
  190. * Used to insert a document into the remote document cache. Use of this method should
  191. * be paired with some explanation for why it is in the cache, for instance:
  192. * - added to a target
  193. * - now has or previously had a pending mutation
  194. */
  195. - (Document)cacheADocumentInTransaction {
  196. Document doc = [self nextTestDocument];
  197. _documentCache->Add(doc);
  198. return doc;
  199. }
  200. - (SetMutation)mutationForDocument:(const DocumentKey &)docKey {
  201. return SetMutation(docKey, _testValue, Precondition::None());
  202. }
  203. - (DocumentKey)nextTestDocKey {
  204. return Key("docs/doc_" + std::to_string(++_previousDocNum));
  205. }
  206. - (Document)nextTestDocumentWithValue:(ObjectValue)value {
  207. DocumentKey key = [self nextTestDocKey];
  208. FSTTestSnapshotVersion version = 2;
  209. return Document(std::move(value), std::move(key), Version(version), DocumentState::kSynced);
  210. }
  211. - (Document)nextTestDocument {
  212. return [self nextTestDocumentWithValue:_testValue];
  213. }
  214. #pragma mark - tests
  215. - (void)testPickSequenceNumberPercentile {
  216. if ([self isTestBaseClass]) return;
  217. const int numTestCases = 5;
  218. struct Case {
  219. // number of queries to cache
  220. int queries;
  221. // number expected to be calculated as 10%
  222. int expected;
  223. };
  224. struct Case testCases[numTestCases] = {{0, 0}, {10, 1}, {9, 0}, {50, 5}, {49, 4}};
  225. for (int i = 0; i < numTestCases; i++) {
  226. // Fill the query cache.
  227. int numQueries = testCases[i].queries;
  228. int expectedTenthPercentile = testCases[i].expected;
  229. [self newTestResources];
  230. for (int j = 0; j < numQueries; j++) {
  231. [self addNextQuery];
  232. }
  233. int tenth = [self queryCountForPercentile:10];
  234. XCTAssertEqual(expectedTenthPercentile, tenth, @"Total query count: %i", numQueries);
  235. [_persistence shutdown];
  236. _persistence = nil;
  237. }
  238. }
  239. - (void)testSequenceNumberNoQueries {
  240. if ([self isTestBaseClass]) return;
  241. // No queries... should get invalid sequence number (-1)
  242. [self newTestResources];
  243. XCTAssertEqual(local::kListenSequenceNumberInvalid, [self sequenceNumberForQueryCount:0]);
  244. [_persistence shutdown];
  245. }
  246. - (void)testSequenceNumberForFiftyQueries {
  247. if ([self isTestBaseClass]) return;
  248. // Add 50 queries sequentially, aim to collect 10 of them.
  249. // The sequence number to collect should be 10 past the initial sequence number.
  250. [self newTestResources];
  251. for (int i = 0; i < 50; i++) {
  252. [self addNextQuery];
  253. }
  254. XCTAssertEqual(_initialSequenceNumber + 10, [self sequenceNumberForQueryCount:10]);
  255. [_persistence shutdown];
  256. }
  257. - (void)testSequenceNumberForMultipleQueriesInATransaction {
  258. if ([self isTestBaseClass]) return;
  259. // 50 queries, 9 with one transaction, incrementing from there. Should get second sequence number.
  260. [self newTestResources];
  261. _persistence.run("9 queries in a batch", [&]() {
  262. for (int i = 0; i < 9; i++) {
  263. [self addNextQueryInTransaction];
  264. }
  265. });
  266. for (int i = 9; i < 50; i++) {
  267. [self addNextQuery];
  268. }
  269. XCTAssertEqual(2 + _initialSequenceNumber, [self sequenceNumberForQueryCount:10]);
  270. [_persistence shutdown];
  271. }
  272. // Ensure that even if all of the queries are added in a single transaction, we still
  273. // pick a sequence number and GC. In this case, the initial transaction contains all of the
  274. // targets that will get GC'd, since they account for more than the first 10 targets.
  275. - (void)testAllCollectedQueriesInSingleTransaction {
  276. if ([self isTestBaseClass]) return;
  277. // 50 queries, 11 with one transaction, incrementing from there. Should get first sequence number.
  278. [self newTestResources];
  279. _persistence.run("11 queries in a transaction", [&]() {
  280. for (int i = 0; i < 11; i++) {
  281. [self addNextQueryInTransaction];
  282. }
  283. });
  284. for (int i = 11; i < 50; i++) {
  285. [self addNextQuery];
  286. }
  287. // We expect to GC the targets from the first transaction, since they account for
  288. // at least the first 10 of the targets.
  289. XCTAssertEqual(1 + _initialSequenceNumber, [self sequenceNumberForQueryCount:10]);
  290. [_persistence shutdown];
  291. }
  292. - (void)testSequenceNumbersWithMutationAndSequentialQueries {
  293. if ([self isTestBaseClass]) return;
  294. // Remove a mutated doc reference, marking it as eligible for GC.
  295. // Then add 50 queries. Should get 10 past initial (9 queries).
  296. [self newTestResources];
  297. [self markADocumentEligibleForGC];
  298. for (int i = 0; i < 50; i++) {
  299. [self addNextQuery];
  300. }
  301. XCTAssertEqual(10 + _initialSequenceNumber, [self sequenceNumberForQueryCount:10]);
  302. [_persistence shutdown];
  303. }
  304. - (void)testSequenceNumbersWithMutationsInQueries {
  305. if ([self isTestBaseClass]) return;
  306. // Add mutated docs, then add one of them to a query target so it doesn't get GC'd.
  307. // Expect 3 past the initial value: the mutations not part of a query, and two queries
  308. [self newTestResources];
  309. Document docInQuery = [self nextTestDocument];
  310. _persistence.run("mark mutations", [&]() {
  311. // Adding 9 doc keys in a transaction. If we remove one of them, we'll have room for two actual
  312. // queries.
  313. [self markDocumentEligibleForGCInTransaction:docInQuery.key()];
  314. for (int i = 0; i < 8; i++) {
  315. [self markADocumentEligibleForGCInTransaction];
  316. }
  317. });
  318. for (int i = 0; i < 49; i++) {
  319. [self addNextQuery];
  320. }
  321. _persistence.run("query with mutation", [&]() {
  322. QueryData queryData = [self addNextQueryInTransaction];
  323. // This should keep the document from getting GC'd, since it is no longer orphaned.
  324. [self addDocument:docInQuery.key() toTarget:queryData.target_id()];
  325. });
  326. // This should catch the remaining 8 documents, plus the first two queries we added.
  327. XCTAssertEqual(3 + _initialSequenceNumber, [self sequenceNumberForQueryCount:10]);
  328. [_persistence shutdown];
  329. }
  330. - (void)testRemoveQueriesUpThroughSequenceNumber {
  331. if ([self isTestBaseClass]) return;
  332. [self newTestResources];
  333. std::unordered_map<TargetId, QueryData> liveQueries;
  334. for (int i = 0; i < 100; i++) {
  335. QueryData queryData = [self addNextQuery];
  336. // Mark odd queries as live so we can test filtering out live queries.
  337. if (queryData.target_id() % 2 == 1) {
  338. liveQueries[queryData.target_id()] = queryData;
  339. }
  340. }
  341. // GC up through 20th query, which is 20%.
  342. // Expect to have GC'd 10 targets, since every other target is live
  343. int removed = [self removeQueriesThroughSequenceNumber:20 + _initialSequenceNumber
  344. liveQueries:liveQueries];
  345. XCTAssertEqual(10, removed);
  346. // Make sure we removed the even targets with targetID <= 20.
  347. _persistence.run("verify remaining targets are > 20 or odd", [&]() {
  348. _queryCache->EnumerateTargets([&](const QueryData &queryData) {
  349. XCTAssertTrue(queryData.target_id() > 20 || queryData.target_id() % 2 == 1);
  350. });
  351. });
  352. [_persistence shutdown];
  353. }
  354. - (void)testRemoveOrphanedDocuments {
  355. if ([self isTestBaseClass]) return;
  356. [self newTestResources];
  357. // Track documents we expect to be retained so we can verify post-GC.
  358. // This will contain documents associated with targets that survive GC, as well
  359. // as any documents with pending mutations.
  360. std::unordered_set<DocumentKey, DocumentKeyHash> expectedRetained;
  361. // we add two mutations later, for now track them in an array.
  362. std::vector<Mutation> mutations;
  363. // Add a target and add two documents to it. The documents are expected to be
  364. // retained, since their membership in the target keeps them alive.
  365. _persistence.run("add a target and add two documents to it", [&]() {
  366. // Add two documents to first target, queue a mutation on the second document
  367. QueryData queryData = [self addNextQueryInTransaction];
  368. Document doc1 = [self cacheADocumentInTransaction];
  369. [self addDocument:doc1.key() toTarget:queryData.target_id()];
  370. expectedRetained.insert(doc1.key());
  371. Document doc2 = [self cacheADocumentInTransaction];
  372. [self addDocument:doc2.key() toTarget:queryData.target_id()];
  373. expectedRetained.insert(doc2.key());
  374. mutations.push_back([self mutationForDocument:doc2.key()]);
  375. });
  376. // Add a second query and register a third document on it
  377. _persistence.run("second query", [&]() {
  378. QueryData queryData = [self addNextQueryInTransaction];
  379. Document doc3 = [self cacheADocumentInTransaction];
  380. expectedRetained.insert(doc3.key());
  381. [self addDocument:doc3.key() toTarget:queryData.target_id()];
  382. });
  383. // cache another document and prepare a mutation on it.
  384. _persistence.run("queue a mutation", [&]() {
  385. Document doc4 = [self cacheADocumentInTransaction];
  386. mutations.push_back([self mutationForDocument:doc4.key()]);
  387. expectedRetained.insert(doc4.key());
  388. });
  389. // Insert the mutations. These operations don't have a sequence number, they just
  390. // serve to keep the mutated documents from being GC'd while the mutations are outstanding.
  391. _persistence.run("actually register the mutations", [&]() {
  392. Timestamp writeTime = Timestamp::Now();
  393. _mutationQueue->AddMutationBatch(writeTime, {}, std::move(mutations));
  394. });
  395. // Mark 5 documents eligible for GC. This simulates documents that were mutated then ack'd.
  396. // Since they were ack'd, they are no longer in a mutation queue, and there is nothing keeping
  397. // them alive.
  398. std::unordered_set<DocumentKey, DocumentKeyHash> toBeRemoved;
  399. _persistence.run("add orphaned docs (previously mutated, then ack'd)", [&]() {
  400. for (int i = 0; i < 5; i++) {
  401. Document doc = [self cacheADocumentInTransaction];
  402. toBeRemoved.insert(doc.key());
  403. [self markDocumentEligibleForGCInTransaction:doc.key()];
  404. }
  405. });
  406. // We expect only the orphaned documents, those not in a mutation or a target, to be
  407. // removed.
  408. // use a large sequence number to remove as much as possible
  409. int removed = [self removeOrphanedDocumentsThroughSequenceNumber:1000];
  410. XCTAssertEqual(toBeRemoved.size(), removed);
  411. _persistence.run("verify", [&]() {
  412. for (const DocumentKey &key : toBeRemoved) {
  413. XCTAssertEqual(_documentCache->Get(key), absl::nullopt);
  414. XCTAssertFalse(_queryCache->Contains(key));
  415. }
  416. for (const DocumentKey &key : expectedRetained) {
  417. XCTAssertNotEqual(_documentCache->Get(key), absl::nullopt, @"Missing document %s",
  418. key.ToString().c_str());
  419. }
  420. });
  421. [_persistence shutdown];
  422. }
  423. // TODO(gsoltis): write a test that includes limbo documents
  424. - (void)testRemoveTargetsThenGC {
  425. if ([self isTestBaseClass]) return;
  426. // Create 3 targets, add docs to all of them
  427. // Leave oldest target alone, it is still live
  428. // Remove newest target
  429. // Blind write 2 documents
  430. // Add one of the blind write docs to oldest target (preserves it)
  431. // Remove some documents from middle target (bumps sequence number)
  432. // Add some documents from newest target to oldest target (preserves them)
  433. // Update a doc from middle target
  434. // Remove middle target
  435. // Do a blind write
  436. // GC up to but not including the removal of the middle target
  437. //
  438. // Expect:
  439. // All docs in oldest target are still around
  440. // One blind write is gone, the first one not added to oldest target
  441. // Documents removed from middle target are gone, except ones added to oldest target
  442. // Documents from newest target are gone, except
  443. [self newTestResources];
  444. // Through the various steps, track which documents we expect to be removed vs
  445. // documents we expect to be retained.
  446. std::unordered_set<DocumentKey, DocumentKeyHash> expectedRetained;
  447. std::unordered_set<DocumentKey, DocumentKeyHash> expectedRemoved;
  448. // Add oldest target, 5 documents, and add those documents to the target.
  449. // This target will not be removed, so all documents that are part of it will
  450. // be retained.
  451. QueryData oldestTarget = _persistence.run("Add oldest target and docs", [&] {
  452. QueryData queryData = [self addNextQueryInTransaction];
  453. for (int i = 0; i < 5; i++) {
  454. Document doc = [self cacheADocumentInTransaction];
  455. expectedRetained.insert(doc.key());
  456. [self addDocument:doc.key() toTarget:queryData.target_id()];
  457. }
  458. return queryData;
  459. });
  460. // Add middle target and docs. Some docs will be removed from this target later,
  461. // which we track here.
  462. DocumentKeySet middleDocsToRemove;
  463. // This will be the document in this target that gets an update later
  464. DocumentKey middleDocToUpdate;
  465. QueryData middleTarget = _persistence.run("Add middle target and docs", [&] {
  466. QueryData middleTarget = [self addNextQueryInTransaction];
  467. // these docs will be removed from this target later, triggering a bump
  468. // to their sequence numbers. Since they will not be a part of the target, we
  469. // expect them to be removed.
  470. for (int i = 0; i < 2; i++) {
  471. Document doc = [self cacheADocumentInTransaction];
  472. expectedRemoved.insert(doc.key());
  473. [self addDocument:doc.key() toTarget:middleTarget.target_id()];
  474. middleDocsToRemove = middleDocsToRemove.insert(doc.key());
  475. }
  476. // these docs stay in this target and only this target. There presence in this
  477. // target prevents them from being GC'd, so they are also expected to be retained.
  478. for (int i = 2; i < 4; i++) {
  479. Document doc = [self cacheADocumentInTransaction];
  480. expectedRetained.insert(doc.key());
  481. [self addDocument:doc.key() toTarget:middleTarget.target_id()];
  482. }
  483. // This doc stays in this target, but gets updated.
  484. {
  485. Document doc = [self cacheADocumentInTransaction];
  486. expectedRetained.insert(doc.key());
  487. [self addDocument:doc.key() toTarget:middleTarget.target_id()];
  488. middleDocToUpdate = doc.key();
  489. }
  490. return middleTarget;
  491. });
  492. // Add the newest target and add 5 documents to it. Some of those documents will
  493. // additionally be added to the oldest target, which will cause those documents to
  494. // be retained. The remaining documents are expected to be removed, since this target
  495. // will be removed.
  496. DocumentKeySet newestDocsToAddToOldest;
  497. _persistence.run("Add newest target and docs", [&]() {
  498. QueryData newestTarget = [self addNextQueryInTransaction];
  499. // These documents are only in this target. They are expected to be removed
  500. // because this target will also be removed.
  501. for (int i = 0; i < 3; i++) {
  502. Document doc = [self cacheADocumentInTransaction];
  503. expectedRemoved.insert(doc.key());
  504. [self addDocument:doc.key() toTarget:newestTarget.target_id()];
  505. }
  506. // docs to add to the oldest target in addition to this target. They will be retained
  507. for (int i = 3; i < 5; i++) {
  508. Document doc = [self cacheADocumentInTransaction];
  509. expectedRetained.insert(doc.key());
  510. [self addDocument:doc.key() toTarget:newestTarget.target_id()];
  511. newestDocsToAddToOldest = newestDocsToAddToOldest.insert(doc.key());
  512. }
  513. });
  514. // 2 doc writes, add one of them to the oldest target.
  515. _persistence.run("2 doc writes, add one of them to the oldest target", [&]() {
  516. // write two docs and have them ack'd by the server. can skip mutation queue
  517. // and set them in document cache. Add potentially orphaned first, also add one
  518. // doc to a target.
  519. Document doc1 = [self cacheADocumentInTransaction];
  520. [self markDocumentEligibleForGCInTransaction:doc1.key()];
  521. [self updateTargetInTransaction:oldestTarget];
  522. [self addDocument:doc1.key() toTarget:oldestTarget.target_id()];
  523. // doc1 should be retained by being added to oldestTarget.
  524. expectedRetained.insert(doc1.key());
  525. Document doc2 = [self cacheADocumentInTransaction];
  526. [self markDocumentEligibleForGCInTransaction:doc2.key()];
  527. // nothing is keeping doc2 around, it should be removed
  528. expectedRemoved.insert(doc2.key());
  529. });
  530. // Remove some documents from the middle target.
  531. _persistence.run("Remove some documents from the middle target", [&]() {
  532. [self updateTargetInTransaction:middleTarget];
  533. for (const DocumentKey &docKey : middleDocsToRemove) {
  534. [self removeDocument:docKey fromTarget:middleTarget.target_id()];
  535. }
  536. });
  537. // Add a couple docs from the newest target to the oldest (preserves them past the point where
  538. // newest was removed)
  539. // upperBound is the sequence number right before middleTarget is updated, then removed.
  540. ListenSequenceNumber upperBound = _persistence.run(
  541. "Add a couple docs from the newest target to the oldest", [&]() -> ListenSequenceNumber {
  542. [self updateTargetInTransaction:oldestTarget];
  543. for (const DocumentKey &docKey : newestDocsToAddToOldest) {
  544. [self addDocument:docKey toTarget:oldestTarget.target_id()];
  545. }
  546. return _persistence.currentSequenceNumber;
  547. });
  548. // Update a doc in the middle target
  549. _persistence.run("Update a doc in the middle target", [&]() {
  550. FSTTestSnapshotVersion version = 3;
  551. Document doc(ObjectValue(_testValue), middleDocToUpdate, Version(version),
  552. DocumentState::kSynced);
  553. _documentCache->Add(doc);
  554. [self updateTargetInTransaction:middleTarget];
  555. });
  556. // middleTarget removed here, no update needed
  557. // Write a doc and get an ack, not part of a target.
  558. _persistence.run("Write a doc and get an ack, not part of a target", [&]() {
  559. Document doc = [self cacheADocumentInTransaction];
  560. // Mark it as eligible for GC, but this is after our upper bound for what we will collect.
  561. [self markDocumentEligibleForGCInTransaction:doc.key()];
  562. // This should be retained, it's too new to get removed.
  563. expectedRetained.insert(doc.key());
  564. });
  565. // Finally, do the garbage collection, up to but not including the removal of middleTarget
  566. std::unordered_map<TargetId, QueryData> liveQueries{{oldestTarget.target_id(), oldestTarget}};
  567. int queriesRemoved = [self removeQueriesThroughSequenceNumber:upperBound liveQueries:liveQueries];
  568. XCTAssertEqual(1, queriesRemoved, @"Expected to remove newest target");
  569. int docsRemoved = [self removeOrphanedDocumentsThroughSequenceNumber:upperBound];
  570. XCTAssertEqual(expectedRemoved.size(), docsRemoved);
  571. _persistence.run("verify results", [&]() {
  572. for (const DocumentKey &key : expectedRemoved) {
  573. XCTAssertEqual(_documentCache->Get(key), absl::nullopt,
  574. @"Did not expect to find %s in document cache", key.ToString().c_str());
  575. XCTAssertFalse(_queryCache->Contains(key), @"Did not expect to find %s in queryCache",
  576. key.ToString().c_str());
  577. [self expectSentinelRemoved:key];
  578. }
  579. for (const DocumentKey &key : expectedRetained) {
  580. XCTAssertNotEqual(_documentCache->Get(key), absl::nullopt,
  581. @"Expected to find %s in document cache", key.ToString().c_str());
  582. }
  583. });
  584. [_persistence shutdown];
  585. }
  586. - (void)testGetsSize {
  587. if ([self isTestBaseClass]) return;
  588. [self newTestResources];
  589. size_t initialSize = _gc->CalculateByteSize();
  590. _persistence.run("fill cache", [&]() {
  591. // Simulate a bunch of ack'd mutations
  592. for (int i = 0; i < 50; i++) {
  593. Document doc = [self cacheADocumentInTransaction];
  594. [self markDocumentEligibleForGCInTransaction:doc.key()];
  595. }
  596. });
  597. size_t finalSize = _gc->CalculateByteSize();
  598. XCTAssertGreaterThan(finalSize, initialSize);
  599. [_persistence shutdown];
  600. }
  601. - (void)testDisabled {
  602. if ([self isTestBaseClass]) return;
  603. LruParams params = LruParams::Disabled();
  604. [self newTestResourcesWithLruParams:params];
  605. _persistence.run("fill cache", [&]() {
  606. // Simulate a bunch of ack'd mutations
  607. for (int i = 0; i < 500; i++) {
  608. Document doc = [self cacheADocumentInTransaction];
  609. [self markDocumentEligibleForGCInTransaction:doc.key()];
  610. }
  611. });
  612. LruResults results = _persistence.run("GC", [&]() -> LruResults { return _gc->Collect({}); });
  613. XCTAssertFalse(results.did_run);
  614. [_persistence shutdown];
  615. }
  616. - (void)testCacheTooSmall {
  617. if ([self isTestBaseClass]) return;
  618. LruParams params = LruParams::Default();
  619. [self newTestResourcesWithLruParams:params];
  620. _persistence.run("fill cache", [&]() {
  621. // Simulate a bunch of ack'd mutations
  622. for (int i = 0; i < 50; i++) {
  623. Document doc = [self cacheADocumentInTransaction];
  624. [self markDocumentEligibleForGCInTransaction:doc.key()];
  625. }
  626. });
  627. int cacheSize = (int)_gc->CalculateByteSize();
  628. // Verify that we don't have enough in our cache to warrant collection
  629. XCTAssertLessThan(cacheSize, params.min_bytes_threshold);
  630. // Try collection and verify that it didn't run
  631. LruResults results = _persistence.run("GC", [&]() -> LruResults { return _gc->Collect({}); });
  632. XCTAssertFalse(results.did_run);
  633. [_persistence shutdown];
  634. }
  635. - (void)testGCRan {
  636. if ([self isTestBaseClass]) return;
  637. LruParams params = LruParams::Default();
  638. // Set a low threshold so we will definitely run
  639. params.min_bytes_threshold = 100;
  640. [self newTestResourcesWithLruParams:params];
  641. // Add 100 targets and 10 documents to each
  642. for (int i = 0; i < 100; i++) {
  643. // Use separate transactions so that each target and associated documents get their own
  644. // sequence number.
  645. _persistence.run("Add a target and some documents", [&]() {
  646. QueryData queryData = [self addNextQueryInTransaction];
  647. for (int j = 0; j < 10; j++) {
  648. Document doc = [self cacheADocumentInTransaction];
  649. [self addDocument:doc.key() toTarget:queryData.target_id()];
  650. }
  651. });
  652. }
  653. // Mark nothing as live, so everything is eligible.
  654. LruResults results = _persistence.run("GC", [&]() -> LruResults { return _gc->Collect({}); });
  655. // By default, we collect 10% of the sequence numbers. Since we added 100 targets,
  656. // that should be 10 targets with 10 documents each, for a total of 100 documents.
  657. XCTAssertTrue(results.did_run);
  658. XCTAssertEqual(10, results.targets_removed);
  659. XCTAssertEqual(100, results.documents_removed);
  660. [_persistence shutdown];
  661. }
  662. @end
  663. NS_ASSUME_NONNULL_END