FSTRemoteEvent.mm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  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/Remote/FSTRemoteEvent.h"
  17. #include <map>
  18. #include <set>
  19. #include <unordered_map>
  20. #include <utility>
  21. #import "Firestore/Source/Core/FSTQuery.h"
  22. #import "Firestore/Source/Core/FSTViewSnapshot.h"
  23. #import "Firestore/Source/Local/FSTQueryData.h"
  24. #import "Firestore/Source/Model/FSTDocument.h"
  25. #import "Firestore/Source/Remote/FSTExistenceFilter.h"
  26. #import "Firestore/Source/Remote/FSTRemoteStore.h"
  27. #import "Firestore/Source/Remote/FSTWatchChange.h"
  28. #import "Firestore/Source/Util/FSTClasses.h"
  29. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  30. #include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
  31. #include "Firestore/core/src/firebase/firestore/util/hashing.h"
  32. #include "Firestore/core/src/firebase/firestore/util/log.h"
  33. using firebase::firestore::model::DocumentKey;
  34. using firebase::firestore::model::DocumentKeyHash;
  35. using firebase::firestore::model::DocumentKeySet;
  36. using firebase::firestore::model::SnapshotVersion;
  37. using firebase::firestore::util::Hash;
  38. NS_ASSUME_NONNULL_BEGIN
  39. #pragma mark - FSTTargetChange
  40. @implementation FSTTargetChange {
  41. DocumentKeySet _addedDocuments;
  42. DocumentKeySet _modifiedDocuments;
  43. DocumentKeySet _removedDocuments;
  44. }
  45. - (instancetype)initWithResumeToken:(NSData *)resumeToken
  46. current:(BOOL)current
  47. addedDocuments:(DocumentKeySet)addedDocuments
  48. modifiedDocuments:(DocumentKeySet)modifiedDocuments
  49. removedDocuments:(DocumentKeySet)removedDocuments {
  50. if (self = [super init]) {
  51. _resumeToken = [resumeToken copy];
  52. _current = current;
  53. _addedDocuments = std::move(addedDocuments);
  54. _modifiedDocuments = std::move(modifiedDocuments);
  55. _removedDocuments = std::move(removedDocuments);
  56. }
  57. return self;
  58. }
  59. - (const DocumentKeySet &)addedDocuments {
  60. return _addedDocuments;
  61. }
  62. - (const DocumentKeySet &)modifiedDocuments {
  63. return _modifiedDocuments;
  64. }
  65. - (const DocumentKeySet &)removedDocuments {
  66. return _removedDocuments;
  67. }
  68. - (BOOL)isEqual:(id)other {
  69. if (other == self) {
  70. return YES;
  71. }
  72. if (![other isMemberOfClass:[FSTTargetChange class]]) {
  73. return NO;
  74. }
  75. return [self current] == [other current] &&
  76. [[self resumeToken] isEqualToData:[other resumeToken]] &&
  77. [self addedDocuments] == [other addedDocuments] &&
  78. [self modifiedDocuments] == [other modifiedDocuments] &&
  79. [self removedDocuments] == [other removedDocuments];
  80. }
  81. @end
  82. #pragma mark - FSTTargetState
  83. /** Tracks the internal state of a Watch target. */
  84. @interface FSTTargetState : NSObject
  85. /**
  86. * Whether this target has been marked 'current'.
  87. *
  88. * 'Current' has special meaning in the RPC protocol: It implies that the Watch backend has sent us
  89. * all changes up to the point at which the target was added and that the target is consistent with
  90. * the rest of the watch stream.
  91. */
  92. @property(nonatomic) BOOL current;
  93. /** The last resume token sent to us for this target. */
  94. @property(nonatomic, readonly, strong) NSData *resumeToken;
  95. /** Whether we have modified any state that should trigger a snapshot. */
  96. @property(nonatomic, readonly) BOOL hasPendingChanges;
  97. /** Whether this target has pending target adds or target removes. */
  98. - (BOOL)isPending;
  99. /**
  100. * Applies the resume token to the TargetChange, but only when it has a new value. Empty
  101. * resumeTokens are discarded.
  102. */
  103. - (void)updateResumeToken:(NSData *)resumeToken;
  104. /** Resets the document changes and sets `hasPendingChanges` to false. */
  105. - (void)clearPendingChanges;
  106. /**
  107. * Creates a target change from the current set of changes.
  108. *
  109. * To reset the document changes after raising this snapshot, call `clearPendingChanges()`.
  110. */
  111. - (FSTTargetChange *)toTargetChange;
  112. - (void)recordTargetRequest;
  113. - (void)recordTargetResponse;
  114. - (void)markCurrent;
  115. - (void)addDocumentChangeWithType:(FSTDocumentViewChangeType)type
  116. forKey:(const DocumentKey &)documentKey;
  117. - (void)removeDocumentChangeForKey:(const DocumentKey &)documentKey;
  118. @end
  119. @implementation FSTTargetState {
  120. /**
  121. * The number of outstanding responses (adds or removes) that we are waiting on. We only consider
  122. * targets active that have no outstanding responses.
  123. */
  124. int _outstandingResponses;
  125. /**
  126. * Keeps track of the document changes since the last raised snapshot.
  127. *
  128. * These changes are continuously updated as we receive document updates and always reflect the
  129. * current set of changes against the last issued snapshot.
  130. */
  131. std::unordered_map<DocumentKey, FSTDocumentViewChangeType, DocumentKeyHash> _documentChanges;
  132. }
  133. - (instancetype)init {
  134. if (self = [super init]) {
  135. _resumeToken = [NSData data];
  136. _outstandingResponses = 0;
  137. // We initialize to 'true' so that newly-added targets are included in the next RemoteEvent.
  138. _hasPendingChanges = YES;
  139. }
  140. return self;
  141. }
  142. - (BOOL)isPending {
  143. return _outstandingResponses != 0;
  144. }
  145. - (void)updateResumeToken:(NSData *)resumeToken {
  146. if (resumeToken.length > 0) {
  147. _hasPendingChanges = YES;
  148. _resumeToken = [resumeToken copy];
  149. }
  150. }
  151. - (void)clearPendingChanges {
  152. _hasPendingChanges = NO;
  153. _documentChanges.clear();
  154. }
  155. - (void)recordTargetRequest {
  156. _outstandingResponses += 1;
  157. }
  158. - (void)recordTargetResponse {
  159. _outstandingResponses -= 1;
  160. }
  161. - (void)markCurrent {
  162. _hasPendingChanges = YES;
  163. _current = true;
  164. }
  165. - (void)addDocumentChangeWithType:(FSTDocumentViewChangeType)type
  166. forKey:(const DocumentKey &)documentKey {
  167. _hasPendingChanges = YES;
  168. _documentChanges[documentKey] = type;
  169. }
  170. - (void)removeDocumentChangeForKey:(const DocumentKey &)documentKey {
  171. _hasPendingChanges = YES;
  172. _documentChanges.erase(documentKey);
  173. }
  174. - (FSTTargetChange *)toTargetChange {
  175. DocumentKeySet addedDocuments;
  176. DocumentKeySet modifiedDocuments;
  177. DocumentKeySet removedDocuments;
  178. for (const auto &entry : _documentChanges) {
  179. switch (entry.second) {
  180. case FSTDocumentViewChangeTypeAdded:
  181. addedDocuments = addedDocuments.insert(entry.first);
  182. break;
  183. case FSTDocumentViewChangeTypeModified:
  184. modifiedDocuments = modifiedDocuments.insert(entry.first);
  185. break;
  186. case FSTDocumentViewChangeTypeRemoved:
  187. removedDocuments = removedDocuments.insert(entry.first);
  188. break;
  189. default:
  190. HARD_FAIL("Encountered invalid change type: %s", entry.second);
  191. }
  192. }
  193. return [[FSTTargetChange alloc] initWithResumeToken:_resumeToken
  194. current:_current
  195. addedDocuments:std::move(addedDocuments)
  196. modifiedDocuments:std::move(modifiedDocuments)
  197. removedDocuments:std::move(removedDocuments)];
  198. }
  199. @end
  200. #pragma mark - FSTRemoteEvent
  201. @implementation FSTRemoteEvent {
  202. SnapshotVersion _snapshotVersion;
  203. std::unordered_map<FSTTargetID, FSTTargetChange *> _targetChanges;
  204. std::unordered_set<FSTTargetID> _targetMismatches;
  205. std::unordered_map<DocumentKey, FSTMaybeDocument *, DocumentKeyHash> _documentUpdates;
  206. DocumentKeySet _limboDocumentChanges;
  207. }
  208. - (instancetype)
  209. initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
  210. targetChanges:(std::unordered_map<FSTTargetID, FSTTargetChange *>)targetChanges
  211. targetMismatches:(std::unordered_set<FSTTargetID>)targetMismatches
  212. documentUpdates:
  213. (std::unordered_map<DocumentKey, FSTMaybeDocument *, DocumentKeyHash>)documentUpdates
  214. limboDocuments:(DocumentKeySet)limboDocuments {
  215. self = [super init];
  216. if (self) {
  217. _snapshotVersion = std::move(snapshotVersion);
  218. _targetChanges = std::move(targetChanges);
  219. _targetMismatches = std::move(targetMismatches);
  220. _documentUpdates = std::move(documentUpdates);
  221. _limboDocumentChanges = std::move(limboDocuments);
  222. }
  223. return self;
  224. }
  225. - (const SnapshotVersion &)snapshotVersion {
  226. return _snapshotVersion;
  227. }
  228. - (const DocumentKeySet &)limboDocumentChanges {
  229. return _limboDocumentChanges;
  230. }
  231. - (const std::unordered_map<FSTTargetID, FSTTargetChange *> &)targetChanges {
  232. return _targetChanges;
  233. }
  234. - (const std::unordered_map<DocumentKey, FSTMaybeDocument *, DocumentKeyHash> &)documentUpdates {
  235. return _documentUpdates;
  236. }
  237. - (const std::unordered_set<FSTTargetID> &)targetMismatches {
  238. return _targetMismatches;
  239. }
  240. @end
  241. #pragma mark - FSTWatchChangeAggregator
  242. @implementation FSTWatchChangeAggregator {
  243. /** The internal state of all tracked targets. */
  244. std::unordered_map<FSTTargetID, FSTTargetState *> _targetStates;
  245. /** Keeps track of document to update */
  246. std::unordered_map<DocumentKey, FSTMaybeDocument *, DocumentKeyHash> _pendingDocumentUpdates;
  247. /** A mapping of document keys to their set of target IDs. */
  248. std::unordered_map<DocumentKey, std::set<FSTTargetID>, DocumentKeyHash>
  249. _pendingDocumentTargetMappings;
  250. /**
  251. * A list of targets with existence filter mismatches. These targets are known to be inconsistent
  252. * and their listens needs to be re-established by RemoteStore.
  253. */
  254. std::unordered_set<FSTTargetID> _pendingTargetResets;
  255. id<FSTTargetMetadataProvider> _targetMetadataProvider;
  256. }
  257. - (instancetype)initWithTargetMetadataProvider:
  258. (id<FSTTargetMetadataProvider>)targetMetadataProvider {
  259. self = [super init];
  260. if (self) {
  261. _targetMetadataProvider = targetMetadataProvider;
  262. }
  263. return self;
  264. }
  265. - (void)handleDocumentChange:(FSTDocumentWatchChange *)documentChange {
  266. for (FSTBoxedTargetID *targetID in documentChange.updatedTargetIDs) {
  267. if ([documentChange.document isKindOfClass:[FSTDocument class]]) {
  268. [self addDocument:documentChange.document toTarget:targetID.intValue];
  269. } else if ([documentChange.document isKindOfClass:[FSTDeletedDocument class]]) {
  270. [self removeDocument:documentChange.document
  271. withKey:documentChange.documentKey
  272. fromTarget:targetID.intValue];
  273. }
  274. }
  275. for (FSTBoxedTargetID *targetID in documentChange.removedTargetIDs) {
  276. [self removeDocument:documentChange.document
  277. withKey:documentChange.documentKey
  278. fromTarget:targetID.intValue];
  279. }
  280. }
  281. - (void)handleTargetChange:(FSTWatchTargetChange *)targetChange {
  282. for (FSTBoxedTargetID *boxedTargetID in targetChange.targetIDs) {
  283. int targetID = boxedTargetID.intValue;
  284. FSTTargetState *targetState = [self ensureTargetStateForTarget:targetID];
  285. switch (targetChange.state) {
  286. case FSTWatchTargetChangeStateNoChange:
  287. if ([self isActiveTarget:targetID]) {
  288. [targetState updateResumeToken:targetChange.resumeToken];
  289. }
  290. break;
  291. case FSTWatchTargetChangeStateAdded:
  292. // We need to decrement the number of pending acks needed from watch for this targetId.
  293. [targetState recordTargetResponse];
  294. if (!targetState.isPending) {
  295. // We have a freshly added target, so we need to reset any state that we had previously.
  296. // This can happen e.g. when remove and add back a target for existence filter mismatches.
  297. [targetState clearPendingChanges];
  298. }
  299. [targetState updateResumeToken:targetChange.resumeToken];
  300. break;
  301. case FSTWatchTargetChangeStateRemoved:
  302. // We need to keep track of removed targets to we can post-filter and remove any target
  303. // changes.
  304. [targetState recordTargetResponse];
  305. if (!targetState.isPending) {
  306. [self removeTarget:targetID];
  307. }
  308. HARD_ASSERT(!targetChange.cause, "WatchChangeAggregator does not handle errored targets");
  309. break;
  310. case FSTWatchTargetChangeStateCurrent:
  311. if ([self isActiveTarget:targetID]) {
  312. [targetState markCurrent];
  313. [targetState updateResumeToken:targetChange.resumeToken];
  314. }
  315. break;
  316. case FSTWatchTargetChangeStateReset:
  317. if ([self isActiveTarget:targetID]) {
  318. // Reset the target and synthesizes removes for all existing documents. The backend will
  319. // re-add any documents that still match the target before it sends the next global
  320. // snapshot.
  321. [self resetTarget:targetID];
  322. [targetState updateResumeToken:targetChange.resumeToken];
  323. }
  324. break;
  325. default:
  326. HARD_FAIL("Unknown target watch change state: %s", targetChange.state);
  327. }
  328. }
  329. }
  330. - (void)removeTarget:(FSTTargetID)targetID {
  331. _targetStates.erase(targetID);
  332. }
  333. - (void)handleExistenceFilter:(FSTExistenceFilterWatchChange *)existenceFilter {
  334. FSTTargetID targetID = existenceFilter.targetID;
  335. int expectedCount = existenceFilter.filter.count;
  336. FSTQueryData *queryData = [self queryDataForActiveTarget:targetID];
  337. if (queryData) {
  338. FSTQuery *query = queryData.query;
  339. if ([query isDocumentQuery]) {
  340. if (expectedCount == 0) {
  341. // The existence filter told us the document does not exist. We deduce that this document
  342. // does not exist and apply a deleted document to our updates. Without applying this deleted
  343. // document there might be another query that will raise this document as part of a snapshot
  344. // until it is resolved, essentially exposing inconsistency between queries.
  345. FSTDocumentKey *key = [FSTDocumentKey keyWithPath:query.path];
  346. [self
  347. removeDocument:[FSTDeletedDocument documentWithKey:key version:SnapshotVersion::None()]
  348. withKey:key
  349. fromTarget:targetID];
  350. } else {
  351. HARD_ASSERT(expectedCount == 1, "Single document existence filter with count: %s",
  352. expectedCount);
  353. }
  354. } else {
  355. int currentSize = [self currentDocumentCountForTarget:targetID];
  356. if (currentSize != expectedCount) {
  357. // Existence filter mismatch: We reset the mapping and raise a new snapshot with
  358. // `isFromCache:true`.
  359. [self resetTarget:targetID];
  360. _pendingTargetResets.insert(targetID);
  361. }
  362. }
  363. }
  364. }
  365. - (int)currentDocumentCountForTarget:(FSTTargetID)targetID {
  366. FSTTargetState *targetState = [self ensureTargetStateForTarget:targetID];
  367. FSTTargetChange *targetChange = [targetState toTargetChange];
  368. return ([_targetMetadataProvider remoteKeysForTarget:@(targetID)].size() +
  369. targetChange.addedDocuments.size() - targetChange.removedDocuments.size());
  370. }
  371. /**
  372. * Resets the state of a Watch target to its initial state (e.g. sets 'current' to false, clears the
  373. * resume token and removes its target mapping from all documents).
  374. */
  375. - (void)resetTarget:(FSTTargetID)targetID {
  376. auto currentTargetState = _targetStates.find(targetID);
  377. HARD_ASSERT(currentTargetState != _targetStates.end() && !(currentTargetState->second.isPending),
  378. "Should only reset active targets");
  379. _targetStates[targetID] = [FSTTargetState new];
  380. // Trigger removal for any documents currently mapped to this target. These removals will be part
  381. // of the initial snapshot if Watch does not resend these documents.
  382. DocumentKeySet existingKeys = [_targetMetadataProvider remoteKeysForTarget:@(targetID)];
  383. for (FSTDocumentKey *key : existingKeys) {
  384. [self removeDocument:nil withKey:key fromTarget:targetID];
  385. }
  386. }
  387. /**
  388. * Adds the provided document to the internal list of document updates and its document key to the
  389. * given target's mapping.
  390. */
  391. - (void)addDocument:(FSTMaybeDocument *)document toTarget:(FSTTargetID)targetID {
  392. if (![self isActiveTarget:targetID]) {
  393. return;
  394. }
  395. FSTDocumentViewChangeType changeType = [self containsDocument:document.key inTarget:targetID]
  396. ? FSTDocumentViewChangeTypeModified
  397. : FSTDocumentViewChangeTypeAdded;
  398. FSTTargetState *targetState = [self ensureTargetStateForTarget:targetID];
  399. [targetState addDocumentChangeWithType:changeType forKey:document.key];
  400. _pendingDocumentUpdates[document.key] = document;
  401. _pendingDocumentTargetMappings[document.key].insert(targetID);
  402. }
  403. /**
  404. * Removes the provided document from the target mapping. If the document no longer matches the
  405. * target, but the document's state is still known (e.g. we know that the document was deleted or we
  406. * received the change that caused the filter mismatch), the new document can be provided to update
  407. * the remote document cache.
  408. */
  409. - (void)removeDocument:(FSTMaybeDocument *_Nullable)document
  410. withKey:(const DocumentKey &)key
  411. fromTarget:(FSTTargetID)targetID {
  412. if (![self isActiveTarget:targetID]) {
  413. return;
  414. }
  415. FSTTargetState *targetState = [self ensureTargetStateForTarget:targetID];
  416. if ([self containsDocument:key inTarget:targetID]) {
  417. [targetState addDocumentChangeWithType:FSTDocumentViewChangeTypeRemoved forKey:key];
  418. } else {
  419. // The document may have entered and left the target before we raised a snapshot, so we can just
  420. // ignore the change.
  421. [targetState removeDocumentChangeForKey:key];
  422. }
  423. _pendingDocumentTargetMappings[key].erase(targetID);
  424. if (document) {
  425. _pendingDocumentUpdates[key] = document;
  426. }
  427. }
  428. /**
  429. * Returns whether the LocalStore considers the document to be part of the specified target.
  430. */
  431. - (BOOL)containsDocument:(FSTDocumentKey *)key inTarget:(FSTTargetID)targetID {
  432. const DocumentKeySet &existingKeys = [_targetMetadataProvider remoteKeysForTarget:@(targetID)];
  433. return existingKeys.contains(key);
  434. }
  435. - (FSTTargetState *)ensureTargetStateForTarget:(FSTTargetID)targetID {
  436. if (!_targetStates[targetID]) {
  437. _targetStates[targetID] = [FSTTargetState new];
  438. }
  439. return _targetStates[targetID];
  440. }
  441. /**
  442. * Returns YES if the given targetId is active. Active targets are those for which there are no
  443. * pending requests to add a listen and are in the current list of targets the client cares about.
  444. *
  445. * Clients can repeatedly listen and stop listening to targets, so this check is useful in
  446. * preventing in preventing race conditions for a target where events arrive but the server hasn't
  447. * yet acknowledged the intended change in state.
  448. */
  449. - (BOOL)isActiveTarget:(FSTTargetID)targetID {
  450. return [self queryDataForActiveTarget:targetID] != nil;
  451. }
  452. - (nullable FSTQueryData *)queryDataForActiveTarget:(FSTTargetID)targetID {
  453. auto targetState = _targetStates.find(targetID);
  454. return targetState != _targetStates.end() && targetState->second.isPending
  455. ? nil
  456. : [_targetMetadataProvider queryDataForTarget:@(targetID)];
  457. }
  458. - (FSTRemoteEvent *)remoteEventAtSnapshotVersion:(const SnapshotVersion &)snapshotVersion {
  459. std::unordered_map<FSTTargetID, FSTTargetChange *> targetChanges;
  460. for (const auto &entry : _targetStates) {
  461. FSTTargetID targetID = entry.first;
  462. FSTTargetState *targetState = entry.second;
  463. FSTQueryData *queryData = [self queryDataForActiveTarget:targetID];
  464. if (queryData) {
  465. if (targetState.current && [queryData.query isDocumentQuery]) {
  466. // Document queries for document that don't exist can produce an empty result set. To update
  467. // our local cache, we synthesize a document delete if we have not previously received the
  468. // document. This resolves the limbo state of the document, removing it from
  469. // limboDocumentRefs.
  470. FSTDocumentKey *key = [FSTDocumentKey keyWithPath:queryData.query.path];
  471. if (_pendingDocumentUpdates.find(key) == _pendingDocumentUpdates.end() &&
  472. ![self containsDocument:key inTarget:targetID]) {
  473. [self removeDocument:[FSTDeletedDocument documentWithKey:key version:snapshotVersion]
  474. withKey:key
  475. fromTarget:targetID];
  476. }
  477. }
  478. if (targetState.hasPendingChanges) {
  479. targetChanges[targetID] = [targetState toTargetChange];
  480. [targetState clearPendingChanges];
  481. }
  482. }
  483. }
  484. DocumentKeySet resolvedLimboDocuments;
  485. // We extract the set of limbo-only document updates as the GC logic special-cases documents that
  486. // do not appear in the query cache.
  487. //
  488. // TODO(gsoltis): Expand on this comment.
  489. for (const auto &entry : _pendingDocumentTargetMappings) {
  490. BOOL isOnlyLimboTarget = YES;
  491. for (FSTTargetID targetID : entry.second) {
  492. FSTQueryData *queryData = [self queryDataForActiveTarget:targetID];
  493. if (queryData && queryData.purpose != FSTQueryPurposeLimboResolution) {
  494. isOnlyLimboTarget = NO;
  495. break;
  496. }
  497. }
  498. if (isOnlyLimboTarget) {
  499. resolvedLimboDocuments = resolvedLimboDocuments.insert(entry.first);
  500. }
  501. }
  502. FSTRemoteEvent *remoteEvent =
  503. [[FSTRemoteEvent alloc] initWithSnapshotVersion:snapshotVersion
  504. targetChanges:targetChanges
  505. targetMismatches:_pendingTargetResets
  506. documentUpdates:_pendingDocumentUpdates
  507. limboDocuments:resolvedLimboDocuments];
  508. _pendingDocumentUpdates.clear();
  509. _pendingDocumentTargetMappings.clear();
  510. _pendingTargetResets.clear();
  511. return remoteEvent;
  512. }
  513. - (void)recordTargetRequest:(FSTBoxedTargetID *)targetID {
  514. // For each request we get we need to record we need a response for it.
  515. FSTTargetState *targetState = [self ensureTargetStateForTarget:targetID.intValue];
  516. [targetState recordTargetRequest];
  517. }
  518. @end
  519. NS_ASSUME_NONNULL_END