FSTLRUGarbageCollectorTests.mm 30 KB

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