aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Michael Lehenbauer <mikelehen@gmail.com>2018-01-27 15:03:05 -0800
committerGravatar Gil <mcg@google.com>2018-01-27 15:03:05 -0800
commit4d7c9733691396503d9abc413cb6c8547ab938c5 (patch)
treeacba2369e37719e06c2483e5c7819e1b32956317
parentaf6976a907b0d2a9fadbb14d7258bab44f075364 (diff)
Fix b/72502745: OnlineState changes cause limbo document crash. (#470) (#714)
[PORT OF https://github.com/firebase/firebase-js-sdk/pull/470] Context: I made a previous change to raise isFromCache=true events when the client goes offline. As part of this change I added an assert for "OnlineState should not affect limbo documents", which it turns out was not valid because: * When we go offline, we set the view to non-current and call View.applyChanges(). * View.applyChanges() calls applyTargetChange() even though there's no target change and it recalculates limbo documents. * When the view is not current, we consider no documents to be in limbo. * Therefore all limbo documents are removed and so applyChanges() ends up returning unexpected LimboDocumentChanges. Fix: I've modified the View logic so that we don't recalculate limbo documents (and generate LimboDocumentChanges) when the View is not current, so now my assert holds and there should be less spurious removal / re-adding of limbo documents.
-rw-r--r--Firestore/Example/Tests/SpecTests/json/offline_spec_test.json254
-rw-r--r--Firestore/Source/Core/FSTView.m26
2 files changed, 269 insertions, 11 deletions
diff --git a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
index 3981cec..6cbbc80 100644
--- a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
@@ -459,5 +459,259 @@
]
}
]
+ },
+ "Queries with limbo documents handle going offline.": {
+ "describeName": "Offline:",
+ "itName": "Queries with limbo documents handle going offline.",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchReset": [
+ 2
+ ]
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1001"
+ ],
+ "watchSnapshot": 1001,
+ "stateExpect": {
+ "limboDocs": [
+ "collection/a"
+ ],
+ "activeTargets": {
+ "1": {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ },
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ }
+ },
+ "stateExpect": {
+ "activeTargets": {
+ "1": {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ },
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1001"
+ }
+ }
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ }
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1001"
+ ],
+ "watchSnapshot": 1001
+ },
+ {
+ "watchAck": [
+ 1
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [],
+ "targets": [
+ 1
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 1
+ ],
+ "resume-token-1001"
+ ],
+ "watchSnapshot": 1001,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "removed": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ],
+ "stateExpect": {
+ "limboDocs": [],
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1001"
+ }
+ }
+ }
+ }
+ ]
}
}
diff --git a/Firestore/Source/Core/FSTView.m b/Firestore/Source/Core/FSTView.m
index 78019c6..d6b4558 100644
--- a/Firestore/Source/Core/FSTView.m
+++ b/Firestore/Source/Core/FSTView.m
@@ -312,8 +312,8 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
}
return self.query.comparator(c1.document, c2.document);
}];
-
- NSArray<FSTLimboDocumentChange *> *limboChanges = [self applyTargetChange:targetChange];
+ [self applyTargetChange:targetChange];
+ NSArray<FSTLimboDocumentChange *> *limboChanges = [self updateLimboDocuments];
BOOL synced = self.limboDocuments.count == 0 && self.isCurrent;
FSTSyncState newSyncState = synced ? FSTSyncStateSynced : FSTSyncStateLocal;
BOOL syncStateChanged = newSyncState != self.syncState;
@@ -378,10 +378,9 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
}
/**
- * Updates syncedDocuments, isAcked, and limbo docs based on the given change.
- * @return the list of changes to which docs are in limbo.
+ * Updates syncedDocuments and current based on the given change.
*/
-- (NSArray<FSTLimboDocumentChange *> *)applyTargetChange:(nullable FSTTargetChange *)targetChange {
+- (void)applyTargetChange:(nullable FSTTargetChange *)targetChange {
if (targetChange) {
FSTTargetMapping *targetMapping = targetChange.mapping;
if ([targetMapping isKindOfClass:[FSTResetMapping class]]) {
@@ -408,16 +407,21 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
break;
}
}
+}
+
+/** Updates limboDocuments and returns any changes as FSTLimboDocumentChanges. */
+- (NSArray<FSTLimboDocumentChange *> *)updateLimboDocuments {
+ // We can only determine limbo documents when we're in-sync with the server.
+ if (!self.isCurrent) {
+ return @[];
+ }
- // Recompute the set of limbo docs.
// TODO(klimt): Do this incrementally so that it's not quadratic when updating many documents.
FSTDocumentKeySet *oldLimboDocuments = self.limboDocuments;
self.limboDocuments = [FSTDocumentKeySet keySet];
- if (self.isCurrent) {
- for (FSTDocument *doc in self.documentSet.documentEnumerator) {
- if ([self shouldBeLimboDocumentKey:doc.key]) {
- self.limboDocuments = [self.limboDocuments setByAddingObject:doc.key];
- }
+ for (FSTDocument *doc in self.documentSet.documentEnumerator) {
+ if ([self shouldBeLimboDocumentKey:doc.key]) {
+ self.limboDocuments = [self.limboDocuments setByAddingObject:doc.key];
}
}