FSTSyncEngine.mm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "Firestore/Source/Core/FSTSyncEngine.h"
  17. #import <GRPCClient/GRPCCall.h>
  18. #include <map>
  19. #include <set>
  20. #include <unordered_map>
  21. #include <utility>
  22. #import "FIRFirestoreErrors.h"
  23. #import "Firestore/Source/Core/FSTQuery.h"
  24. #import "Firestore/Source/Core/FSTTransaction.h"
  25. #import "Firestore/Source/Core/FSTView.h"
  26. #import "Firestore/Source/Core/FSTViewSnapshot.h"
  27. #import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
  28. #import "Firestore/Source/Local/FSTLocalStore.h"
  29. #import "Firestore/Source/Local/FSTLocalViewChanges.h"
  30. #import "Firestore/Source/Local/FSTLocalWriteResult.h"
  31. #import "Firestore/Source/Local/FSTQueryData.h"
  32. #import "Firestore/Source/Local/FSTReferenceSet.h"
  33. #import "Firestore/Source/Model/FSTDocument.h"
  34. #import "Firestore/Source/Model/FSTDocumentSet.h"
  35. #import "Firestore/Source/Model/FSTMutationBatch.h"
  36. #import "Firestore/Source/Remote/FSTRemoteEvent.h"
  37. #import "Firestore/Source/Util/FSTDispatchQueue.h"
  38. #include "Firestore/core/src/firebase/firestore/auth/user.h"
  39. #include "Firestore/core/src/firebase/firestore/core/target_id_generator.h"
  40. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  41. #include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
  42. #include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
  43. #include "Firestore/core/src/firebase/firestore/util/log.h"
  44. using firebase::firestore::auth::HashUser;
  45. using firebase::firestore::auth::User;
  46. using firebase::firestore::core::TargetIdGenerator;
  47. using firebase::firestore::model::DocumentKey;
  48. using firebase::firestore::model::SnapshotVersion;
  49. using firebase::firestore::model::TargetId;
  50. using firebase::firestore::model::DocumentKeySet;
  51. NS_ASSUME_NONNULL_BEGIN
  52. // Limbo documents don't use persistence, and are eagerly GC'd. So, listens for them don't need
  53. // real sequence numbers.
  54. static const FSTListenSequenceNumber kIrrelevantSequenceNumber = -1;
  55. #pragma mark - FSTQueryView
  56. /**
  57. * FSTQueryView contains all of the info that FSTSyncEngine needs to track for a particular
  58. * query and view.
  59. */
  60. @interface FSTQueryView : NSObject
  61. - (instancetype)initWithQuery:(FSTQuery *)query
  62. targetID:(FSTTargetID)targetID
  63. resumeToken:(NSData *)resumeToken
  64. view:(FSTView *)view NS_DESIGNATED_INITIALIZER;
  65. - (instancetype)init NS_UNAVAILABLE;
  66. /** The query itself. */
  67. @property(nonatomic, strong, readonly) FSTQuery *query;
  68. /** The targetID created by the client that is used in the watch stream to identify this query. */
  69. @property(nonatomic, assign, readonly) FSTTargetID targetID;
  70. /**
  71. * An identifier from the datastore backend that indicates the last state of the results that
  72. * was received. This can be used to indicate where to continue receiving new doc changes for the
  73. * query.
  74. */
  75. @property(nonatomic, copy, readonly) NSData *resumeToken;
  76. /**
  77. * The view is responsible for computing the final merged truth of what docs are in the query.
  78. * It gets notified of local and remote changes, and applies the query filters and limits to
  79. * determine the most correct possible results.
  80. */
  81. @property(nonatomic, strong, readonly) FSTView *view;
  82. @end
  83. @implementation FSTQueryView
  84. - (instancetype)initWithQuery:(FSTQuery *)query
  85. targetID:(FSTTargetID)targetID
  86. resumeToken:(NSData *)resumeToken
  87. view:(FSTView *)view {
  88. if (self = [super init]) {
  89. _query = query;
  90. _targetID = targetID;
  91. _resumeToken = resumeToken;
  92. _view = view;
  93. }
  94. return self;
  95. }
  96. @end
  97. #pragma mark - FSTSyncEngine
  98. @interface FSTSyncEngine ()
  99. /** The local store, used to persist mutations and cached documents. */
  100. @property(nonatomic, strong, readonly) FSTLocalStore *localStore;
  101. /** The remote store for sending writes, watches, etc. to the backend. */
  102. @property(nonatomic, strong, readonly) FSTRemoteStore *remoteStore;
  103. /** FSTQueryViews for all active queries, indexed by query. */
  104. @property(nonatomic, strong, readonly)
  105. NSMutableDictionary<FSTQuery *, FSTQueryView *> *queryViewsByQuery;
  106. /** FSTQueryViews for all active queries, indexed by target ID. */
  107. @property(nonatomic, strong, readonly)
  108. NSMutableDictionary<NSNumber *, FSTQueryView *> *queryViewsByTarget;
  109. /** Used to track any documents that are currently in limbo. */
  110. @property(nonatomic, strong, readonly) FSTReferenceSet *limboDocumentRefs;
  111. /** The garbage collector used to collect documents that are no longer in limbo. */
  112. @property(nonatomic, strong, readonly) FSTEagerGarbageCollector *limboCollector;
  113. @end
  114. @implementation FSTSyncEngine {
  115. /** Used for creating the FSTTargetIDs for the listens used to resolve limbo documents. */
  116. TargetIdGenerator _targetIdGenerator;
  117. /** Stores user completion blocks, indexed by user and FSTBatchID. */
  118. std::unordered_map<User, NSMutableDictionary<NSNumber *, FSTVoidErrorBlock> *, HashUser>
  119. _mutationCompletionBlocks;
  120. /**
  121. * When a document is in limbo, we create a special listen to resolve it. This maps the
  122. * DocumentKey of each limbo document to the TargetId of the listen resolving it.
  123. */
  124. std::map<DocumentKey, TargetId> _limboTargetsByKey;
  125. /** The inverse of _limboTargetsByKey, a map of TargetId to the key of the limbo doc. */
  126. std::map<TargetId, DocumentKey> _limboKeysByTarget;
  127. User _currentUser;
  128. }
  129. - (instancetype)initWithLocalStore:(FSTLocalStore *)localStore
  130. remoteStore:(FSTRemoteStore *)remoteStore
  131. initialUser:(const User &)initialUser {
  132. if (self = [super init]) {
  133. _localStore = localStore;
  134. _remoteStore = remoteStore;
  135. _queryViewsByQuery = [NSMutableDictionary dictionary];
  136. _queryViewsByTarget = [NSMutableDictionary dictionary];
  137. _limboCollector = [[FSTEagerGarbageCollector alloc] init];
  138. _limboDocumentRefs = [[FSTReferenceSet alloc] init];
  139. [_limboCollector addGarbageSource:_limboDocumentRefs];
  140. _targetIdGenerator = TargetIdGenerator::SyncEngineTargetIdGenerator(0);
  141. _currentUser = initialUser;
  142. }
  143. return self;
  144. }
  145. - (FSTTargetID)listenToQuery:(FSTQuery *)query {
  146. [self assertDelegateExistsForSelector:_cmd];
  147. HARD_ASSERT(self.queryViewsByQuery[query] == nil, "We already listen to query: %s", query);
  148. FSTQueryData *queryData = [self.localStore allocateQuery:query];
  149. FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
  150. DocumentKeySet remoteKeys = [self.localStore remoteDocumentKeysForTarget:queryData.targetID];
  151. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:std::move(remoteKeys)];
  152. FSTViewDocumentChanges *viewDocChanges = [view computeChangesWithDocuments:docs];
  153. FSTViewChange *viewChange = [view applyChangesToDocuments:viewDocChanges];
  154. HARD_ASSERT(viewChange.limboChanges.count == 0,
  155. "View returned limbo docs before target ack from the server.");
  156. FSTQueryView *queryView = [[FSTQueryView alloc] initWithQuery:query
  157. targetID:queryData.targetID
  158. resumeToken:queryData.resumeToken
  159. view:view];
  160. self.queryViewsByQuery[query] = queryView;
  161. self.queryViewsByTarget[@(queryData.targetID)] = queryView;
  162. [self.delegate handleViewSnapshots:@[ viewChange.snapshot ]];
  163. [self.remoteStore listenToTargetWithQueryData:queryData];
  164. return queryData.targetID;
  165. }
  166. - (void)stopListeningToQuery:(FSTQuery *)query {
  167. [self assertDelegateExistsForSelector:_cmd];
  168. FSTQueryView *queryView = self.queryViewsByQuery[query];
  169. HARD_ASSERT(queryView, "Trying to stop listening to a query not found");
  170. [self.localStore releaseQuery:query];
  171. [self.remoteStore stopListeningToTargetID:queryView.targetID];
  172. [self removeAndCleanupQuery:queryView];
  173. [self.localStore collectGarbage];
  174. }
  175. - (void)writeMutations:(NSArray<FSTMutation *> *)mutations
  176. completion:(FSTVoidErrorBlock)completion {
  177. [self assertDelegateExistsForSelector:_cmd];
  178. FSTLocalWriteResult *result = [self.localStore locallyWriteMutations:mutations];
  179. [self addMutationCompletionBlock:completion batchID:result.batchID];
  180. [self emitNewSnapshotsWithChanges:result.changes remoteEvent:nil];
  181. [self.remoteStore fillWritePipeline];
  182. }
  183. - (void)addMutationCompletionBlock:(FSTVoidErrorBlock)completion batchID:(FSTBatchID)batchID {
  184. NSMutableDictionary<NSNumber *, FSTVoidErrorBlock> *completionBlocks =
  185. _mutationCompletionBlocks[_currentUser];
  186. if (!completionBlocks) {
  187. completionBlocks = [NSMutableDictionary dictionary];
  188. _mutationCompletionBlocks[_currentUser] = completionBlocks;
  189. }
  190. [completionBlocks setObject:completion forKey:@(batchID)];
  191. }
  192. /**
  193. * Takes an updateBlock in which a set of reads and writes can be performed atomically. In the
  194. * updateBlock, user code can read and write values using a transaction object. After the
  195. * updateBlock, all changes will be committed. If someone else has changed any of the data
  196. * referenced, then the updateBlock will be called again. If the updateBlock still fails after the
  197. * given number of retries, then the transaction will be rejected.
  198. *
  199. * The transaction object passed to the updateBlock contains methods for accessing documents
  200. * and collections. Unlike other firestore access, data accessed with the transaction will not
  201. * reflect local changes that have not been committed. For this reason, it is required that all
  202. * reads are performed before any writes. Transactions must be performed while online.
  203. */
  204. - (void)transactionWithRetries:(int)retries
  205. workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
  206. updateBlock:(FSTTransactionBlock)updateBlock
  207. completion:(FSTVoidIDErrorBlock)completion {
  208. [workerDispatchQueue verifyIsCurrentQueue];
  209. HARD_ASSERT(retries >= 0, "Got negative number of retries for transaction");
  210. FSTTransaction *transaction = [self.remoteStore transaction];
  211. updateBlock(transaction, ^(id _Nullable result, NSError *_Nullable error) {
  212. [workerDispatchQueue dispatchAsync:^{
  213. if (error) {
  214. completion(nil, error);
  215. return;
  216. }
  217. [transaction commitWithCompletion:^(NSError *_Nullable transactionError) {
  218. if (!transactionError) {
  219. completion(result, nil);
  220. return;
  221. }
  222. // TODO(b/35201829): Only retry on real transaction failures.
  223. if (retries == 0) {
  224. NSError *wrappedError =
  225. [NSError errorWithDomain:FIRFirestoreErrorDomain
  226. code:FIRFirestoreErrorCodeFailedPrecondition
  227. userInfo:@{
  228. NSLocalizedDescriptionKey : @"Transaction failed all retries.",
  229. NSUnderlyingErrorKey : transactionError
  230. }];
  231. completion(nil, wrappedError);
  232. return;
  233. }
  234. [workerDispatchQueue verifyIsCurrentQueue];
  235. return [self transactionWithRetries:(retries - 1)
  236. workerDispatchQueue:workerDispatchQueue
  237. updateBlock:updateBlock
  238. completion:completion];
  239. }];
  240. }];
  241. });
  242. }
  243. - (void)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent {
  244. [self assertDelegateExistsForSelector:_cmd];
  245. // Make sure limbo documents are deleted if there were no results.
  246. // Filter out document additions to targets that they already belong to.
  247. [remoteEvent.targetChanges enumerateKeysAndObjectsUsingBlock:^(
  248. FSTBoxedTargetID *_Nonnull targetID,
  249. FSTTargetChange *_Nonnull targetChange, BOOL *_Nonnull stop) {
  250. const auto iter = self->_limboKeysByTarget.find([targetID intValue]);
  251. if (iter == self->_limboKeysByTarget.end()) {
  252. FSTQueryView *qv = self.queryViewsByTarget[targetID];
  253. HARD_ASSERT(qv, "Missing queryview for non-limbo query: %s", [targetID intValue]);
  254. [targetChange.mapping filterUpdatesUsingExistingKeys:qv.view.syncedDocuments];
  255. } else {
  256. [remoteEvent synthesizeDeleteForLimboTargetChange:targetChange key:iter->second];
  257. }
  258. }];
  259. FSTMaybeDocumentDictionary *changes = [self.localStore applyRemoteEvent:remoteEvent];
  260. [self emitNewSnapshotsWithChanges:changes remoteEvent:remoteEvent];
  261. }
  262. - (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
  263. NSMutableArray<FSTViewSnapshot *> *newViewSnapshots = [NSMutableArray array];
  264. [self.queryViewsByQuery
  265. enumerateKeysAndObjectsUsingBlock:^(FSTQuery *query, FSTQueryView *queryView, BOOL *stop) {
  266. FSTViewChange *viewChange = [queryView.view applyChangedOnlineState:onlineState];
  267. HARD_ASSERT(viewChange.limboChanges.count == 0,
  268. "OnlineState should not affect limbo documents.");
  269. if (viewChange.snapshot) {
  270. [newViewSnapshots addObject:viewChange.snapshot];
  271. }
  272. }];
  273. [self.delegate handleViewSnapshots:newViewSnapshots];
  274. }
  275. - (void)rejectListenWithTargetID:(const TargetId)targetID error:(NSError *)error {
  276. [self assertDelegateExistsForSelector:_cmd];
  277. const auto iter = _limboKeysByTarget.find(targetID);
  278. if (iter != _limboKeysByTarget.end()) {
  279. const DocumentKey limboKey = iter->second;
  280. // Since this query failed, we won't want to manually unlisten to it.
  281. // So go ahead and remove it from bookkeeping.
  282. _limboTargetsByKey.erase(limboKey);
  283. _limboKeysByTarget.erase(targetID);
  284. // TODO(dimond): Retry on transient errors?
  285. // It's a limbo doc. Create a synthetic event saying it was deleted. This is kind of a hack.
  286. // Ideally, we would have a method in the local store to purge a document. However, it would
  287. // be tricky to keep all of the local store's invariants with another method.
  288. NSMutableDictionary<NSNumber *, FSTTargetChange *> *targetChanges =
  289. [NSMutableDictionary dictionary];
  290. FSTDeletedDocument *doc =
  291. [FSTDeletedDocument documentWithKey:limboKey version:SnapshotVersion::None()];
  292. DocumentKeySet limboDocuments = DocumentKeySet{doc.key};
  293. FSTRemoteEvent *event =
  294. [[FSTRemoteEvent alloc] initWithSnapshotVersion:SnapshotVersion::None()
  295. targetChanges:targetChanges
  296. documentUpdates:{
  297. { limboKey, doc }
  298. }
  299. limboDocuments:std::move(limboDocuments)];
  300. [self applyRemoteEvent:event];
  301. } else {
  302. FSTQueryView *queryView = self.queryViewsByTarget[@(targetID)];
  303. HARD_ASSERT(queryView, "Unknown targetId: %s", targetID);
  304. [self.localStore releaseQuery:queryView.query];
  305. [self removeAndCleanupQuery:queryView];
  306. [self.delegate handleError:error forQuery:queryView.query];
  307. }
  308. }
  309. - (void)applySuccessfulWriteWithResult:(FSTMutationBatchResult *)batchResult {
  310. [self assertDelegateExistsForSelector:_cmd];
  311. // The local store may or may not be able to apply the write result and raise events immediately
  312. // (depending on whether the watcher is caught up), so we raise user callbacks first so that they
  313. // consistently happen before listen events.
  314. [self processUserCallbacksForBatchID:batchResult.batch.batchID error:nil];
  315. FSTMaybeDocumentDictionary *changes = [self.localStore acknowledgeBatchWithResult:batchResult];
  316. [self emitNewSnapshotsWithChanges:changes remoteEvent:nil];
  317. }
  318. - (void)rejectFailedWriteWithBatchID:(FSTBatchID)batchID error:(NSError *)error {
  319. [self assertDelegateExistsForSelector:_cmd];
  320. // The local store may or may not be able to apply the write result and raise events immediately
  321. // (depending on whether the watcher is caught up), so we raise user callbacks first so that they
  322. // consistently happen before listen events.
  323. [self processUserCallbacksForBatchID:batchID error:error];
  324. FSTMaybeDocumentDictionary *changes = [self.localStore rejectBatchID:batchID];
  325. [self emitNewSnapshotsWithChanges:changes remoteEvent:nil];
  326. }
  327. - (void)processUserCallbacksForBatchID:(FSTBatchID)batchID error:(NSError *_Nullable)error {
  328. NSMutableDictionary<NSNumber *, FSTVoidErrorBlock> *completionBlocks =
  329. _mutationCompletionBlocks[_currentUser];
  330. // NOTE: Mutations restored from persistence won't have completion blocks, so it's okay for
  331. // this (or the completion below) to be nil.
  332. if (completionBlocks) {
  333. NSNumber *boxedBatchID = @(batchID);
  334. FSTVoidErrorBlock completion = completionBlocks[boxedBatchID];
  335. if (completion) {
  336. completion(error);
  337. [completionBlocks removeObjectForKey:boxedBatchID];
  338. }
  339. }
  340. }
  341. - (void)assertDelegateExistsForSelector:(SEL)methodSelector {
  342. HARD_ASSERT(self.delegate, "Tried to call '%s' before delegate was registered.",
  343. NSStringFromSelector(methodSelector));
  344. }
  345. - (void)removeAndCleanupQuery:(FSTQueryView *)queryView {
  346. [self.queryViewsByQuery removeObjectForKey:queryView.query];
  347. [self.queryViewsByTarget removeObjectForKey:@(queryView.targetID)];
  348. [self.limboDocumentRefs removeReferencesForID:queryView.targetID];
  349. [self garbageCollectLimboDocuments];
  350. }
  351. /**
  352. * Computes a new snapshot from the changes and calls the registered callback with the new snapshot.
  353. */
  354. - (void)emitNewSnapshotsWithChanges:(FSTMaybeDocumentDictionary *)changes
  355. remoteEvent:(FSTRemoteEvent *_Nullable)remoteEvent {
  356. NSMutableArray<FSTViewSnapshot *> *newSnapshots = [NSMutableArray array];
  357. NSMutableArray<FSTLocalViewChanges *> *documentChangesInAllViews = [NSMutableArray array];
  358. [self.queryViewsByQuery
  359. enumerateKeysAndObjectsUsingBlock:^(FSTQuery *query, FSTQueryView *queryView, BOOL *stop) {
  360. FSTView *view = queryView.view;
  361. FSTViewDocumentChanges *viewDocChanges = [view computeChangesWithDocuments:changes];
  362. if (viewDocChanges.needsRefill) {
  363. // The query has a limit and some docs were removed/updated, so we need to re-run the
  364. // query against the local store to make sure we didn't lose any good docs that had been
  365. // past the limit.
  366. FSTDocumentDictionary *docs = [self.localStore executeQuery:queryView.query];
  367. viewDocChanges = [view computeChangesWithDocuments:docs previousChanges:viewDocChanges];
  368. }
  369. FSTTargetChange *_Nullable targetChange = remoteEvent.targetChanges[@(queryView.targetID)];
  370. FSTViewChange *viewChange =
  371. [queryView.view applyChangesToDocuments:viewDocChanges targetChange:targetChange];
  372. [self updateTrackedLimboDocumentsWithChanges:viewChange.limboChanges
  373. targetID:queryView.targetID];
  374. if (viewChange.snapshot) {
  375. [newSnapshots addObject:viewChange.snapshot];
  376. FSTLocalViewChanges *docChanges =
  377. [FSTLocalViewChanges changesForViewSnapshot:viewChange.snapshot];
  378. [documentChangesInAllViews addObject:docChanges];
  379. }
  380. }];
  381. [self.delegate handleViewSnapshots:newSnapshots];
  382. [self.localStore notifyLocalViewChanges:documentChangesInAllViews];
  383. [self.localStore collectGarbage];
  384. }
  385. /** Updates the limbo document state for the given targetID. */
  386. - (void)updateTrackedLimboDocumentsWithChanges:(NSArray<FSTLimboDocumentChange *> *)limboChanges
  387. targetID:(FSTTargetID)targetID {
  388. for (FSTLimboDocumentChange *limboChange in limboChanges) {
  389. switch (limboChange.type) {
  390. case FSTLimboDocumentChangeTypeAdded:
  391. [self.limboDocumentRefs addReferenceToKey:limboChange.key forID:targetID];
  392. [self trackLimboChange:limboChange];
  393. break;
  394. case FSTLimboDocumentChangeTypeRemoved:
  395. LOG_DEBUG("Document no longer in limbo: %s", limboChange.key.ToString());
  396. [self.limboDocumentRefs removeReferenceToKey:limboChange.key forID:targetID];
  397. break;
  398. default:
  399. HARD_FAIL("Unknown limbo change type: %s", limboChange.type);
  400. }
  401. }
  402. [self garbageCollectLimboDocuments];
  403. }
  404. - (void)trackLimboChange:(FSTLimboDocumentChange *)limboChange {
  405. DocumentKey key{limboChange.key};
  406. if (_limboTargetsByKey.find(key) == _limboTargetsByKey.end()) {
  407. LOG_DEBUG("New document in limbo: %s", key.ToString());
  408. TargetId limboTargetID = _targetIdGenerator.NextId();
  409. FSTQuery *query = [FSTQuery queryWithPath:key.path()];
  410. FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
  411. targetID:limboTargetID
  412. listenSequenceNumber:kIrrelevantSequenceNumber
  413. purpose:FSTQueryPurposeLimboResolution];
  414. _limboKeysByTarget[limboTargetID] = key;
  415. [self.remoteStore listenToTargetWithQueryData:queryData];
  416. _limboTargetsByKey[key] = limboTargetID;
  417. }
  418. }
  419. /** Garbage collect the limbo documents that we no longer need to track. */
  420. - (void)garbageCollectLimboDocuments {
  421. const std::set<DocumentKey> garbage = [self.limboCollector collectGarbage];
  422. for (const DocumentKey &key : garbage) {
  423. const auto iter = _limboTargetsByKey.find(key);
  424. if (iter == _limboTargetsByKey.end()) {
  425. // This target already got removed, because the query failed.
  426. return;
  427. }
  428. TargetId limboTargetID = iter->second;
  429. [self.remoteStore stopListeningToTargetID:limboTargetID];
  430. _limboTargetsByKey.erase(key);
  431. _limboKeysByTarget.erase(limboTargetID);
  432. }
  433. }
  434. // Used for testing
  435. - (std::map<DocumentKey, TargetId>)currentLimboDocuments {
  436. // Return defensive copy
  437. return _limboTargetsByKey;
  438. }
  439. - (void)userDidChange:(const User &)user {
  440. _currentUser = user;
  441. // Notify local store and emit any resulting events from swapping out the mutation queue.
  442. FSTMaybeDocumentDictionary *changes = [self.localStore userDidChange:user];
  443. [self emitNewSnapshotsWithChanges:changes remoteEvent:nil];
  444. // Notify remote store so it can restart its streams.
  445. [self.remoteStore userDidChange:user];
  446. }
  447. @end
  448. NS_ASSUME_NONNULL_END