aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore
diff options
context:
space:
mode:
Diffstat (limited to 'Firestore')
-rw-r--r--Firestore/CHANGELOG.md4
-rw-r--r--Firestore/Example/Tests/Core/FSTEventManagerTests.mm8
-rw-r--r--Firestore/Example/Tests/Core/FSTQueryListenerTests.mm18
-rw-r--r--Firestore/Example/Tests/Integration/FSTDatastoreTests.mm4
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSpecTests.mm25
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h6
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm95
-rw-r--r--Firestore/Example/Tests/SpecTests/json/offline_spec_test.json152
-rw-r--r--Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm4
-rw-r--r--Firestore/Source/Core/FSTEventManager.mm8
-rw-r--r--Firestore/Source/Core/FSTFirestoreClient.mm4
-rw-r--r--Firestore/Source/Core/FSTTypes.h19
-rw-r--r--Firestore/Source/Core/FSTView.mm2
-rw-r--r--Firestore/Source/Remote/FSTOnlineStateTracker.h71
-rw-r--r--Firestore/Source/Remote/FSTOnlineStateTracker.mm149
-rw-r--r--Firestore/Source/Remote/FSTRemoteStore.h8
-rw-r--r--Firestore/Source/Remote/FSTRemoteStore.mm117
-rw-r--r--Firestore/Source/Remote/FSTStream.mm4
-rw-r--r--Firestore/Source/Util/FSTDispatchQueue.h26
-rw-r--r--Firestore/Source/Util/FSTDispatchQueue.mm4
20 files changed, 569 insertions, 159 deletions
diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md
index b472f0c..9b35ea3 100644
--- a/Firestore/CHANGELOG.md
+++ b/Firestore/CHANGELOG.md
@@ -1,4 +1,8 @@
# Unreleased
+- [changed] If the SDK's attempt to connect to the Cloud Firestore backend
+ neither succeeds nor fails within 10 seconds, the SDK will consider itself
+ "offline", causing getDocument() calls to resolve with cached results, rather
+ than continuing to wait.
# v0.10.2
- [changed] When you delete a FirebaseApp, the associated Firestore instances
diff --git a/Firestore/Example/Tests/Core/FSTEventManagerTests.mm b/Firestore/Example/Tests/Core/FSTEventManagerTests.mm
index fcde17d..f5f7b5b 100644
--- a/Firestore/Example/Tests/Core/FSTEventManagerTests.mm
+++ b/Firestore/Example/Tests/Core/FSTEventManagerTests.mm
@@ -143,9 +143,9 @@ NS_ASSUME_NONNULL_BEGIN
.andDo(^(NSInvocation *invocation) {
[events addObject:@(FSTOnlineStateUnknown)];
});
- OCMStub([fakeListener applyChangedOnlineState:FSTOnlineStateHealthy])
+ OCMStub([fakeListener applyChangedOnlineState:FSTOnlineStateOnline])
.andDo(^(NSInvocation *invocation) {
- [events addObject:@(FSTOnlineStateHealthy)];
+ [events addObject:@(FSTOnlineStateOnline)];
});
FSTSyncEngine *syncEngineMock = OCMClassMock([FSTSyncEngine class]);
@@ -154,8 +154,8 @@ NS_ASSUME_NONNULL_BEGIN
[eventManager addListener:fakeListener];
XCTAssertEqualObjects(events, @[ @(FSTOnlineStateUnknown) ]);
- [eventManager applyChangedOnlineState:FSTOnlineStateHealthy];
- XCTAssertEqualObjects(events, (@[ @(FSTOnlineStateUnknown), @(FSTOnlineStateHealthy) ]));
+ [eventManager applyChangedOnlineState:FSTOnlineStateOnline];
+ XCTAssertEqualObjects(events, (@[ @(FSTOnlineStateUnknown), @(FSTOnlineStateOnline) ]));
}
@end
diff --git a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
index 4856b5f..1b26360 100644
--- a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
+++ b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
@@ -340,10 +340,10 @@ NS_ASSUME_NONNULL_BEGIN
[FSTTargetChange changeWithDocuments:@[ doc1, doc2 ]
currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]);
- [listener applyChangedOnlineState:FSTOnlineStateHealthy]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateOnline]; // no event
[listener queryDidChangeViewSnapshot:snap1];
[listener applyChangedOnlineState:FSTOnlineStateUnknown];
- [listener applyChangedOnlineState:FSTOnlineStateHealthy];
+ [listener applyChangedOnlineState:FSTOnlineStateOnline];
[listener queryDidChangeViewSnapshot:snap2];
[listener queryDidChangeViewSnapshot:snap3];
@@ -379,11 +379,11 @@ NS_ASSUME_NONNULL_BEGIN
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1 ], nil);
FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[ doc2 ], nil);
- [listener applyChangedOnlineState:FSTOnlineStateHealthy]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateOnline]; // no event
[listener queryDidChangeViewSnapshot:snap1]; // no event
- [listener applyChangedOnlineState:FSTOnlineStateFailed]; // event
+ [listener applyChangedOnlineState:FSTOnlineStateOffline]; // event
[listener applyChangedOnlineState:FSTOnlineStateUnknown]; // no event
- [listener applyChangedOnlineState:FSTOnlineStateFailed]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateOffline]; // no event
[listener queryDidChangeViewSnapshot:snap2]; // another event
FSTDocumentViewChange *change1 =
@@ -419,9 +419,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[], nil);
- [listener applyChangedOnlineState:FSTOnlineStateHealthy]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateOnline]; // no event
[listener queryDidChangeViewSnapshot:snap1]; // no event
- [listener applyChangedOnlineState:FSTOnlineStateFailed]; // event
+ [listener applyChangedOnlineState:FSTOnlineStateOffline]; // event
FSTViewSnapshot *expectedSnap = [[FSTViewSnapshot alloc]
initWithQuery:query
@@ -445,8 +445,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[], nil);
- [listener applyChangedOnlineState:FSTOnlineStateFailed]; // no event
- [listener queryDidChangeViewSnapshot:snap1]; // event
+ [listener applyChangedOnlineState:FSTOnlineStateOffline]; // no event
+ [listener queryDidChangeViewSnapshot:snap1]; // event
FSTViewSnapshot *expectedSnap = [[FSTViewSnapshot alloc]
initWithQuery:query
diff --git a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
index 4323ccd..77581d4 100644
--- a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
+++ b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
@@ -175,7 +175,9 @@ NS_ASSUME_NONNULL_BEGIN
workerDispatchQueue:_testWorkerQueue
credentials:&_credentials];
- _remoteStore = [FSTRemoteStore remoteStoreWithLocalStore:_localStore datastore:_datastore];
+ _remoteStore = [[FSTRemoteStore alloc] initWithLocalStore:_localStore
+ datastore:_datastore
+ workerDispatchQueue:_testWorkerQueue];
[_testWorkerQueue dispatchAsync:^() {
[_remoteStore start];
diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
index 43b2a5f..b00ea07 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
+++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
@@ -22,7 +22,6 @@
#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Core/FSTViewSnapshot.h"
#import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
#import "Firestore/Source/Local/FSTNoOpGarbageCollector.h"
#import "Firestore/Source/Local/FSTPersistence.h"
@@ -36,6 +35,7 @@
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTClasses.h"
+#import "Firestore/Source/Util/FSTDispatchQueue.h"
#import "Firestore/Source/Util/FSTLogger.h"
#import "Firestore/Example/Tests/Remote/FSTWatchChange+Testing.h"
@@ -323,6 +323,27 @@ static NSString *const kNoIOSTag = @"no-ios";
}
}
+- (void)doRunTimer:(NSString *)timer {
+ FSTTimerID timerID;
+ if ([timer isEqualToString:@"all"]) {
+ timerID = FSTTimerIDAll;
+ } else if ([timer isEqualToString:@"listen_stream_idle"]) {
+ timerID = FSTTimerIDListenStreamIdle;
+ } else if ([timer isEqualToString:@"listen_stream_connection"]) {
+ timerID = FSTTimerIDListenStreamConnectionBackoff;
+ } else if ([timer isEqualToString:@"write_stream_idle"]) {
+ timerID = FSTTimerIDWriteStreamIdle;
+ } else if ([timer isEqualToString:@"write_stream_connection"]) {
+ timerID = FSTTimerIDWriteStreamConnectionBackoff;
+ } else if ([timer isEqualToString:@"online_state_timeout"]) {
+ timerID = FSTTimerIDOnlineStateTimeout;
+ } else {
+ FSTFail(@"runTimer spec step specified unknown timer: %@", timer);
+ }
+
+ [self.driver runTimer:timerID];
+}
+
- (void)doDisableNetwork {
[self.driver disableNetwork];
}
@@ -391,6 +412,8 @@ static NSString *const kNoIOSTag = @"no-ios";
[self doWriteAck:step[@"writeAck"]];
} else if (step[@"failWrite"]) {
[self doFailWrite:step[@"failWrite"]];
+ } else if (step[@"runTimer"]) {
+ [self doRunTimer:step[@"runTimer"]];
} else if (step[@"enableNetwork"]) {
if ([step[@"enableNetwork"] boolValue]) {
[self doEnableNetwork];
diff --git a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
index 466a347..f3d9a5d 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
+++ b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
@@ -20,6 +20,7 @@
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Remote/FSTRemoteStore.h"
+#import "Firestore/Source/Util/FSTDispatchQueue.h"
#include "Firestore/core/src/firebase/firestore/auth/user.h"
@@ -224,6 +225,11 @@ typedef std::unordered_map<firebase::firestore::auth::User,
- (void)enableNetwork;
/**
+ * Runs a pending timer callback on the FSTDispatchQueue.
+ */
+- (void)runTimer:(FSTTimerID)timerID;
+
+/**
* Switches the FSTSyncEngine to a new user. The test driver tracks the outstanding mutations for
* each user, so future receiveWriteAck/Error operations will validate the write sent to the mock
* datastore matches the next outstanding write for that user.
diff --git a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm
index 07dc84f..bfcd4dd 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm
+++ b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm
@@ -31,7 +31,6 @@
#import "Firestore/Source/Remote/FSTDatastore.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTAssert.h"
-#import "Firestore/Source/Util/FSTDispatchQueue.h"
#import "Firestore/Source/Util/FSTLogger.h"
#import "Firestore/Example/Tests/Core/FSTSyncEngine+Testing.h"
@@ -72,6 +71,7 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, strong, readonly) FSTRemoteStore *remoteStore;
@property(nonatomic, strong, readonly) FSTLocalStore *localStore;
@property(nonatomic, strong, readonly) FSTSyncEngine *syncEngine;
+@property(nonatomic, strong, readonly) FSTDispatchQueue *dispatchQueue;
#pragma mark - Data structures for holding events sent by the watch stream.
@@ -117,16 +117,19 @@ NS_ASSUME_NONNULL_BEGIN
_databaseInfo = {DatabaseId{"project", "database"}, "persistence", "host", false};
// Set up the sync engine and various stores.
- dispatch_queue_t mainQueue = dispatch_get_main_queue();
- FSTDispatchQueue *dispatchQueue = [FSTDispatchQueue queueWith:mainQueue];
+ dispatch_queue_t queue =
+ dispatch_queue_create("sync_engine_test_driver", DISPATCH_QUEUE_SERIAL);
+ _dispatchQueue = [FSTDispatchQueue queueWith:queue];
_localStore = [[FSTLocalStore alloc] initWithPersistence:persistence
garbageCollector:garbageCollector
initialUser:initialUser];
_datastore = [[FSTMockDatastore alloc] initWithDatabaseInfo:&_databaseInfo
- workerDispatchQueue:dispatchQueue
+ workerDispatchQueue:_dispatchQueue
credentials:&_credentialProvider];
- _remoteStore = [FSTRemoteStore remoteStoreWithLocalStore:_localStore datastore:_datastore];
+ _remoteStore = [[FSTRemoteStore alloc] initWithLocalStore:_localStore
+ datastore:_datastore
+ workerDispatchQueue:_dispatchQueue];
_syncEngine = [[FSTSyncEngine alloc] initWithLocalStore:_localStore
remoteStore:_remoteStore
@@ -168,8 +171,10 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)start {
- [self.localStore start];
- [self.remoteStore start];
+ [self.dispatchQueue dispatchSync:^{
+ [self.localStore start];
+ [self.remoteStore start];
+ }];
}
- (void)validateUsage {
@@ -180,8 +185,10 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)shutdown {
- [self.remoteStore shutdown];
- [self.localStore shutdown];
+ [self.dispatchQueue dispatchSync:^{
+ [self.remoteStore shutdown];
+ [self.localStore shutdown];
+ }];
}
- (void)validateNextWriteSent:(FSTMutation *)expectedWrite {
@@ -208,19 +215,29 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)disableNetwork {
- // Make sure to execute all writes that are currently queued. This allows us
- // to assert on the total number of requests sent before shutdown.
- [self.remoteStore fillWritePipeline];
- [self.remoteStore disableNetwork];
+ [self.dispatchQueue dispatchSync:^{
+ // Make sure to execute all writes that are currently queued. This allows us
+ // to assert on the total number of requests sent before shutdown.
+ [self.remoteStore fillWritePipeline];
+ [self.remoteStore disableNetwork];
+ }];
}
- (void)enableNetwork {
- [self.remoteStore enableNetwork];
+ [self.dispatchQueue dispatchSync:^{
+ [self.remoteStore enableNetwork];
+ }];
+}
+
+- (void)runTimer:(FSTTimerID)timerID {
+ [self.dispatchQueue runDelayedCallbacksUntil:timerID];
}
- (void)changeUser:(const User &)user {
_currentUser = user;
- [self.syncEngine userDidChange:user];
+ [self.dispatchQueue dispatchSync:^{
+ [self.syncEngine userDidChange:user];
+ }];
}
- (FSTOutstandingWrite *)receiveWriteAckWithVersion:(FSTSnapshotVersion *)commitVersion
@@ -230,7 +247,9 @@ NS_ASSUME_NONNULL_BEGIN
[[self currentOutstandingWrites] removeObjectAtIndex:0];
[self validateNextWriteSent:write.write];
- [self.datastore ackWriteWithVersion:commitVersion mutationResults:mutationResults];
+ [self.dispatchQueue dispatchSync:^{
+ [self.datastore ackWriteWithVersion:commitVersion mutationResults:mutationResults];
+ }];
return write;
}
@@ -250,7 +269,9 @@ NS_ASSUME_NONNULL_BEGIN
}
FSTLog(@"Failing a write.");
- [self.datastore failWriteWithError:error];
+ [self.dispatchQueue dispatchSync:^{
+ [self.datastore failWriteWithError:error];
+ }];
return write;
}
@@ -278,13 +299,19 @@ NS_ASSUME_NONNULL_BEGIN
[self.events addObject:event];
}];
self.queryListeners[query] = listener;
- return [self.eventManager addListener:listener];
+ __block FSTTargetID targetID;
+ [self.dispatchQueue dispatchSync:^{
+ targetID = [self.eventManager addListener:listener];
+ }];
+ return targetID;
}
- (void)removeUserListenerWithQuery:(FSTQuery *)query {
FSTQueryListener *listener = self.queryListeners[query];
[self.queryListeners removeObjectForKey:query];
- [self.eventManager removeListener:listener];
+ [self.dispatchQueue dispatchSync:^{
+ [self.eventManager removeListener:listener];
+ }];
}
- (void)writeUserMutation:(FSTMutation *)mutation {
@@ -292,28 +319,34 @@ NS_ASSUME_NONNULL_BEGIN
write.write = mutation;
[[self currentOutstandingWrites] addObject:write];
FSTLog(@"sending a user write.");
- [self.syncEngine writeMutations:@[ mutation ]
- completion:^(NSError *_Nullable error) {
- FSTLog(@"A callback was called with error: %@", error);
- write.done = YES;
- write.error = error;
- }];
+ [self.dispatchQueue dispatchSync:^{
+ [self.syncEngine writeMutations:@[ mutation ]
+ completion:^(NSError *_Nullable error) {
+ FSTLog(@"A callback was called with error: %@", error);
+ write.done = YES;
+ write.error = error;
+ }];
+ }];
}
- (void)receiveWatchChange:(FSTWatchChange *)change
snapshotVersion:(FSTSnapshotVersion *_Nullable)snapshot {
- [self.datastore writeWatchChange:change snapshotVersion:snapshot];
+ [self.dispatchQueue dispatchSync:^{
+ [self.datastore writeWatchChange:change snapshotVersion:snapshot];
+ }];
}
- (void)receiveWatchStreamError:(int)errorCode userInfo:(NSDictionary<NSString *, id> *)userInfo {
NSError *error =
[NSError errorWithDomain:FIRFirestoreErrorDomain code:errorCode userInfo:userInfo];
- [self.datastore failWatchStreamWithError:error];
- // Unlike web, stream should re-open synchronously (if we have any listeners)
- if (self.queryListeners.count > 0) {
- FSTAssert(self.datastore.isWatchStreamOpen, @"Watch stream is open");
- }
+ [self.dispatchQueue dispatchSync:^{
+ [self.datastore failWatchStreamWithError:error];
+ // Unlike web, stream should re-open synchronously (if we have any listeners)
+ if (self.queryListeners.count > 0) {
+ FSTAssert(self.datastore.isWatchStreamOpen, @"Watch stream is open");
+ }
+ }];
}
- (NSDictionary<FSTDocumentKey *, FSTBoxedTargetID *> *)currentLimboDocuments {
diff --git a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
index 6cbbc80..fc9f295 100644
--- a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
@@ -713,5 +713,157 @@
}
}
]
+ },
+ "OnlineState timeout triggers offline behavior": {
+ "describeName": "Offline:",
+ "itName": "OnlineState timeout triggers offline behavior",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "runTimer": "online_state_timeout",
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ }
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ }
+ }
+ },
+ {
+ "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
+ }
+ ]
+ },
+ {
+ "runTimer": "all"
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ }
+ },
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1000"
+ }
+ }
+ }
+ },
+ {
+ "runTimer": "online_state_timeout",
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
}
}
diff --git a/Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm b/Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm
index 9f5b52d..5ef860c 100644
--- a/Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm
+++ b/Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm
@@ -21,9 +21,9 @@
#import "Firestore/Example/Tests/Util/XCTestCase+Await.h"
// In these generic tests the specific TimerIDs don't matter.
-static const FSTTimerID timerID1 = FSTTimerIDListenStreamConnection;
+static const FSTTimerID timerID1 = FSTTimerIDListenStreamConnectionBackoff;
static const FSTTimerID timerID2 = FSTTimerIDListenStreamIdle;
-static const FSTTimerID timerID3 = FSTTimerIDWriteStreamConnection;
+static const FSTTimerID timerID3 = FSTTimerIDWriteStreamConnectionBackoff;
@interface FSTDispatchQueueTests : XCTestCase
@end
diff --git a/Firestore/Source/Core/FSTEventManager.mm b/Firestore/Source/Core/FSTEventManager.mm
index bc204a0..b02fc5f 100644
--- a/Firestore/Source/Core/FSTEventManager.mm
+++ b/Firestore/Source/Core/FSTEventManager.mm
@@ -169,9 +169,9 @@ NS_ASSUME_NONNULL_BEGIN
return YES;
}
- // NOTE: We consider OnlineState.Unknown as online (it should become Failed
- // or Online if we wait long enough).
- BOOL maybeOnline = onlineState != FSTOnlineStateFailed;
+ // NOTE: We consider OnlineState.Unknown as online (it should become Offline or Online if we
+ // wait long enough).
+ BOOL maybeOnline = onlineState != FSTOnlineStateOffline;
// Don't raise the event if we're online, aren't synced yet (checked
// above) and are waiting for a sync.
if (self.options.waitForSyncWhenOnline && maybeOnline) {
@@ -180,7 +180,7 @@ NS_ASSUME_NONNULL_BEGIN
}
// Raise data from cache if we have any documents or we are offline
- return !snapshot.documents.isEmpty || onlineState == FSTOnlineStateFailed;
+ return !snapshot.documents.isEmpty || onlineState == FSTOnlineStateOffline;
}
- (BOOL)shouldRaiseEventForSnapshot:(FSTViewSnapshot *)snapshot {
diff --git a/Firestore/Source/Core/FSTFirestoreClient.mm b/Firestore/Source/Core/FSTFirestoreClient.mm
index fb86e0b..288cbe2 100644
--- a/Firestore/Source/Core/FSTFirestoreClient.mm
+++ b/Firestore/Source/Core/FSTFirestoreClient.mm
@@ -181,7 +181,9 @@ NS_ASSUME_NONNULL_BEGIN
workerDispatchQueue:self.workerDispatchQueue
credentials:_credentialsProvider];
- _remoteStore = [FSTRemoteStore remoteStoreWithLocalStore:_localStore datastore:datastore];
+ _remoteStore = [[FSTRemoteStore alloc] initWithLocalStore:_localStore
+ datastore:datastore
+ workerDispatchQueue:self.workerDispatchQueue];
_syncEngine = [[FSTSyncEngine alloc] initWithLocalStore:_localStore
remoteStore:_remoteStore
diff --git a/Firestore/Source/Core/FSTTypes.h b/Firestore/Source/Core/FSTTypes.h
index 877ec94..688b2bf 100644
--- a/Firestore/Source/Core/FSTTypes.h
+++ b/Firestore/Source/Core/FSTTypes.h
@@ -65,12 +65,18 @@ typedef void (^FSTVoidMaybeDocumentArrayErrorBlock)(
typedef void (^FSTTransactionBlock)(FSTTransaction *transaction,
void (^completion)(id _Nullable, NSError *_Nullable));
-/** Describes the online state of the Firestore client */
+/**
+ * Describes the online state of the Firestore client. Note that this does not indicate whether
+ * or not the remote store is trying to connect or not. This is primarily used by the View /
+ * EventManager code to change their behavior while offline (e.g. get() calls shouldn't wait for
+ * data from the server and snapshot events should set metadata.isFromCache=true).
+ */
typedef NS_ENUM(NSUInteger, FSTOnlineState) {
/**
* The Firestore client is in an unknown online state. This means the client is either not
* actively trying to establish a connection or it is currently trying to establish a connection,
- * but it has not succeeded or failed yet.
+ * but it has not succeeded or failed yet. Higher-level components should not operate in
+ * offline mode.
*/
FSTOnlineStateUnknown,
@@ -79,13 +85,14 @@ typedef NS_ENUM(NSUInteger, FSTOnlineState) {
* successful connection and there has been at least one successful message received from the
* backends.
*/
- FSTOnlineStateHealthy,
+ FSTOnlineStateOnline,
/**
- * The client considers itself offline. It is either trying to establish a connection but
- * failing, or it has been explicitly marked offline via a call to `disableNetwork`.
+ * The client is either trying to establish a connection but failing, or it has been explicitly
+ * marked offline via a call to `disableNetwork`. Higher-level components should operate in
+ * offline mode.
*/
- FSTOnlineStateFailed
+ FSTOnlineStateOffline
};
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTView.mm b/Firestore/Source/Core/FSTView.mm
index d6b4558..b2a39cb 100644
--- a/Firestore/Source/Core/FSTView.mm
+++ b/Firestore/Source/Core/FSTView.mm
@@ -337,7 +337,7 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
}
- (FSTViewChange *)applyChangedOnlineState:(FSTOnlineState)onlineState {
- if (self.isCurrent && onlineState == FSTOnlineStateFailed) {
+ if (self.isCurrent && onlineState == FSTOnlineStateOffline) {
// If we're offline, set `current` to NO and then call applyChanges to refresh our syncState
// and generate an FSTViewChange as appropriate. We are guaranteed to get a new FSTTargetChange
// that sets `current` back to YES once the client is back online.
diff --git a/Firestore/Source/Remote/FSTOnlineStateTracker.h b/Firestore/Source/Remote/FSTOnlineStateTracker.h
new file mode 100644
index 0000000..a414a18
--- /dev/null
+++ b/Firestore/Source/Remote/FSTOnlineStateTracker.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "Firestore/Source/Core/FSTTypes.h"
+
+@class FSTDispatchQueue;
+@protocol FSTOnlineStateDelegate;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * A component used by the FSTRemoteStore to track the FSTOnlineState (that is, whether or not the
+ * client as a whole should be considered to be online or offline), implementing the appropriate
+ * heuristics.
+ *
+ * In particular, when the client is trying to connect to the backend, we allow up to
+ * kMaxWatchStreamFailures within kOnlineStateTimeout for a connection to succeed. If we have too
+ * many failures or the timeout elapses, then we set the FSTOnlineState to Offline, and
+ * the client will behave as if it is offline (getDocument() calls will return cached data, etc.).
+ */
+@interface FSTOnlineStateTracker : NSObject
+
+- (instancetype)initWithWorkerDispatchQueue:(FSTDispatchQueue *)queue;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+/** A delegate to be notified on FSTOnlineState changes. */
+@property(nonatomic, weak) id<FSTOnlineStateDelegate> onlineStateDelegate;
+
+/**
+ * Called by FSTRemoteStore when a watch stream is started.
+ *
+ * It sets the FSTOnlineState to Unknown and starts the onlineStateTimer if necessary.
+ */
+- (void)handleWatchStreamStart;
+
+/**
+ * Called by FSTRemoteStore when a watch stream fails.
+ *
+ * Updates our FSTOnlineState as appropriate. The first failure moves us to FSTOnlineStateUnknown.
+ * We then may allow multiple failures (based on kMaxWatchStreamFailures) before we actually
+ * transition to FSTOnlineStateOffline.
+ */
+- (void)handleWatchStreamFailure;
+
+/**
+ * Explicitly sets the FSTOnlineState to the specified state.
+ *
+ * Note that this resets the timers / failure counters, etc. used by our Offline heuristics, so
+ * it must not be used in place of handleWatchStreamStart and handleWatchStreamFailure.
+ */
+- (void)updateState:(FSTOnlineState)newState;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Remote/FSTOnlineStateTracker.mm b/Firestore/Source/Remote/FSTOnlineStateTracker.mm
new file mode 100644
index 0000000..41650cd
--- /dev/null
+++ b/Firestore/Source/Remote/FSTOnlineStateTracker.mm
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "Firestore/Source/Remote/FSTOnlineStateTracker.h"
+#import "Firestore/Source/Remote/FSTRemoteStore.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+#import "Firestore/Source/Util/FSTDispatchQueue.h"
+#import "Firestore/Source/Util/FSTLogger.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+// To deal with transient failures, we allow multiple stream attempts before giving up and
+// transitioning from FSTOnlineState Unknown to Offline.
+static const int kMaxWatchStreamFailures = 2;
+
+// To deal with stream attempts that don't succeed or fail in a timely manner, we have a
+// timeout for FSTOnlineState to reach Online or Offline. If the timeout is reached, we transition
+// to Offline rather than waiting indefinitely.
+static const NSTimeInterval kOnlineStateTimeout = 10;
+
+@interface FSTOnlineStateTracker ()
+
+/** The current FSTOnlineState. */
+@property(nonatomic, assign) FSTOnlineState state;
+
+/**
+ * A count of consecutive failures to open the stream. If it reaches the maximum defined by
+ * kMaxWatchStreamFailures, we'll revert to FSTOnlineStateOffline.
+ */
+@property(nonatomic, assign) int watchStreamFailures;
+
+/**
+ * A timer that elapses after kOnlineStateTimeout, at which point we transition from FSTOnlineState
+ * Unknown to Offline without waiting for the stream to actually fail (kMaxWatchStreamFailures
+ * times).
+ */
+@property(nonatomic, strong, nullable) FSTDelayedCallback *watchStreamTimer;
+
+/**
+ * Whether the client should log a warning message if it fails to connect to the backend
+ * (initially YES, cleared after a successful stream, or if we've logged the message already).
+ */
+@property(nonatomic, assign) BOOL shouldWarnClientIsOffline;
+
+/** The FSTDispatchQueue to use for running timers (and to call onlineStateDelegate). */
+@property(nonatomic, strong, readonly) FSTDispatchQueue *queue;
+
+@end
+
+@implementation FSTOnlineStateTracker
+- (instancetype)initWithWorkerDispatchQueue:(FSTDispatchQueue *)queue {
+ if (self = [super init]) {
+ _queue = queue;
+ _state = FSTOnlineStateUnknown;
+ _shouldWarnClientIsOffline = YES;
+ }
+ return self;
+}
+
+- (void)handleWatchStreamStart {
+ [self setAndBroadcastState:FSTOnlineStateUnknown];
+
+ if (self.watchStreamTimer == nil) {
+ self.watchStreamTimer = [self.queue
+ dispatchAfterDelay:kOnlineStateTimeout
+ timerID:FSTTimerIDOnlineStateTimeout
+ block:^{
+ self.watchStreamTimer = nil;
+ FSTAssert(
+ self.state == FSTOnlineStateUnknown,
+ @"Timer should be canceled if we transitioned to a different state.");
+ FSTLog(
+ @"Watch stream didn't reach Online or Offline within %f seconds. "
+ @"Considering "
+ "client offline.",
+ kOnlineStateTimeout);
+ [self logClientOfflineWarningIfNecessary];
+ [self setAndBroadcastState:FSTOnlineStateOffline];
+
+ // NOTE: handleWatchStreamFailure will continue to increment
+ // watchStreamFailures even though we are already marked Offline but this is
+ // non-harmful.
+ }];
+ }
+}
+
+- (void)handleWatchStreamFailure {
+ if (self.state == FSTOnlineStateOnline) {
+ [self setAndBroadcastState:FSTOnlineStateUnknown];
+ } else {
+ self.watchStreamFailures++;
+ if (self.watchStreamFailures >= kMaxWatchStreamFailures) {
+ [self clearOnlineStateTimer];
+ [self logClientOfflineWarningIfNecessary];
+ [self setAndBroadcastState:FSTOnlineStateOffline];
+ }
+ }
+}
+
+- (void)updateState:(FSTOnlineState)newState {
+ [self clearOnlineStateTimer];
+ self.watchStreamFailures = 0;
+
+ if (newState == FSTOnlineStateOnline) {
+ // We've connected to watch at least once. Don't warn the developer about being offline going
+ // forward.
+ self.shouldWarnClientIsOffline = NO;
+ }
+
+ [self setAndBroadcastState:newState];
+}
+
+- (void)setAndBroadcastState:(FSTOnlineState)newState {
+ if (newState != self.state) {
+ self.state = newState;
+ [self.onlineStateDelegate applyChangedOnlineState:newState];
+ }
+}
+
+- (void)logClientOfflineWarningIfNecessary {
+ if (self.shouldWarnClientIsOffline) {
+ FSTWarn(@"Could not reach Firestore backend.");
+ self.shouldWarnClientIsOffline = NO;
+ }
+}
+
+- (void)clearOnlineStateTimer {
+ if (self.watchStreamTimer) {
+ [self.watchStreamTimer cancel];
+ self.watchStreamTimer = nil;
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Remote/FSTRemoteStore.h b/Firestore/Source/Remote/FSTRemoteStore.h
index 4ea9379..6f4d565 100644
--- a/Firestore/Source/Remote/FSTRemoteStore.h
+++ b/Firestore/Source/Remote/FSTRemoteStore.h
@@ -30,6 +30,7 @@
@class FSTQueryData;
@class FSTRemoteEvent;
@class FSTTransaction;
+@class FSTDispatchQueue;
NS_ASSUME_NONNULL_BEGIN
@@ -95,10 +96,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface FSTRemoteStore : NSObject
-+ (instancetype)remoteStoreWithLocalStore:(FSTLocalStore *)localStore
- datastore:(FSTDatastore *)datastore;
+- (instancetype)initWithLocalStore:(FSTLocalStore *)localStore
+ datastore:(FSTDatastore *)datastore
+ workerDispatchQueue:(FSTDispatchQueue *)queue;
-- (instancetype)init __attribute__((unavailable("Use static constructor method.")));
+- (instancetype)init NS_UNAVAILABLE;
@property(nonatomic, weak) id<FSTRemoteSyncer> syncEngine;
diff --git a/Firestore/Source/Remote/FSTRemoteStore.mm b/Firestore/Source/Remote/FSTRemoteStore.mm
index b762722..081b90e 100644
--- a/Firestore/Source/Remote/FSTRemoteStore.mm
+++ b/Firestore/Source/Remote/FSTRemoteStore.mm
@@ -30,6 +30,7 @@
#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Remote/FSTDatastore.h"
#import "Firestore/Source/Remote/FSTExistenceFilter.h"
+#import "Firestore/Source/Remote/FSTOnlineStateTracker.h"
#import "Firestore/Source/Remote/FSTRemoteEvent.h"
#import "Firestore/Source/Remote/FSTStream.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
@@ -50,21 +51,10 @@ NS_ASSUME_NONNULL_BEGIN
*/
static const int kMaxPendingWrites = 10;
-/**
- * The FSTRemoteStore notifies an onlineStateDelegate with FSTOnlineStateFailed if we fail to
- * connect to the backend. This subsequently triggers get() requests to fail or use cached data,
- * etc. Unfortunately, our connections have historically been subject to various transient failures.
- * So we wait for multiple failures before notifying the onlineStateDelegate.
- */
-static const int kOnlineAttemptsBeforeFailure = 2;
-
#pragma mark - FSTRemoteStore
@interface FSTRemoteStore () <FSTWatchStreamDelegate, FSTWriteStreamDelegate>
-- (instancetype)initWithLocalStore:(FSTLocalStore *)localStore
- datastore:(FSTDatastore *)datastore NS_DESIGNATED_INITIALIZER;
-
/**
* The local store, used to fill the write pipeline with outbound mutations and resolve existence
* filter mismatches. Immutable after initialization.
@@ -110,17 +100,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
@property(nonatomic, strong) NSMutableArray<FSTWatchChange *> *accumulatedChanges;
@property(nonatomic, assign) FSTBatchID lastBatchSeen;
-/**
- * The online state of the watch stream. The state is set to healthy if and only if there are
- * messages received by the backend.
- */
-@property(nonatomic, assign) FSTOnlineState watchStreamOnlineState;
-
-/** A count of consecutive failures to open the stream. */
-@property(nonatomic, assign) int watchStreamFailures;
-
-/** Whether the client should fire offline warning. */
-@property(nonatomic, assign) BOOL shouldWarnOffline;
+@property(nonatomic, strong, readonly) FSTOnlineStateTracker *onlineStateTracker;
#pragma mark Write Stream
// The writeStream is null when the network is disabled. The non-null check is performed by
@@ -128,12 +108,6 @@ static const int kOnlineAttemptsBeforeFailure = 2;
@property(nonatomic, strong, nullable) FSTWriteStream *writeStream;
/**
- * The approximate time the StreamingWrite stream was opened. Used to estimate if stream was
- * closed due to an auth expiration (a recoverable error) or some other more permanent error.
- */
-@property(nonatomic, strong, nullable) NSDate *writeStreamOpenTime;
-
-/**
* A FIFO queue of in-flight writes. This is in-flight from the point of view of the caller of
* writeMutations, not from the point of view from the Datastore itself. In particular, these
* requests may not have been sent to the Datastore server if the write stream is not yet running.
@@ -143,12 +117,9 @@ static const int kOnlineAttemptsBeforeFailure = 2;
@implementation FSTRemoteStore
-+ (instancetype)remoteStoreWithLocalStore:(FSTLocalStore *)localStore
- datastore:(FSTDatastore *)datastore {
- return [[FSTRemoteStore alloc] initWithLocalStore:localStore datastore:datastore];
-}
-
-- (instancetype)initWithLocalStore:(FSTLocalStore *)localStore datastore:(FSTDatastore *)datastore {
+- (instancetype)initWithLocalStore:(FSTLocalStore *)localStore
+ datastore:(FSTDatastore *)datastore
+ workerDispatchQueue:(FSTDispatchQueue *)queue {
if (self = [super init]) {
_localStore = localStore;
_datastore = datastore;
@@ -157,9 +128,8 @@ static const int kOnlineAttemptsBeforeFailure = 2;
_accumulatedChanges = [NSMutableArray array];
_lastBatchSeen = kFSTBatchIDUnknown;
- _watchStreamOnlineState = FSTOnlineStateUnknown;
- _shouldWarnOffline = YES;
_pendingWrites = [NSMutableArray array];
+ _onlineStateTracker = [[FSTOnlineStateTracker alloc] initWithWorkerDispatchQueue:queue];
}
return self;
}
@@ -169,48 +139,14 @@ static const int kOnlineAttemptsBeforeFailure = 2;
[self enableNetwork];
}
-/**
- * Updates our OnlineState to the new state, updating local state and notifying the
- * onlineStateHandler as appropriate.
- */
-- (void)updateOnlineState:(FSTOnlineState)newState {
- // Update and broadcast the new state.
- if (newState != self.watchStreamOnlineState) {
- if (newState == FSTOnlineStateHealthy) {
- // We've connected to watch at least once. Don't warn the developer about being offline going
- // forward.
- self.shouldWarnOffline = NO;
- } else if (newState == FSTOnlineStateUnknown) {
- // The state is set to unknown when a healthy stream is closed (e.g. due to a token timeout)
- // or when we have no active listens and therefore there's no need to start the stream.
- // Assuming there is (possibly in the future) an active listen, then we will eventually move
- // to state Online or Failed, but we always want to make at least kOnlineAttemptsBeforeFailure
- // attempts before failing, so we reset the count here.
- self.watchStreamFailures = 0;
- }
- self.watchStreamOnlineState = newState;
- [self.onlineStateDelegate applyChangedOnlineState:newState];
- }
+@dynamic onlineStateDelegate;
+
+- (nullable id<FSTOnlineStateDelegate>)onlineStateDelegate {
+ return self.onlineStateTracker.onlineStateDelegate;
}
-/**
- * Updates our FSTOnlineState as appropriate after the watch stream reports a failure. The first
- * failure moves us to the 'Unknown' state. We then may allow multiple failures (based on
- * kOnlineAttemptsBeforeFailure) before we actually transition to FSTOnlineStateFailed.
- */
-- (void)updateOnlineStateAfterFailure {
- if (self.watchStreamOnlineState == FSTOnlineStateHealthy) {
- [self updateOnlineState:FSTOnlineStateUnknown];
- } else {
- self.watchStreamFailures++;
- if (self.watchStreamFailures >= kOnlineAttemptsBeforeFailure) {
- if (self.shouldWarnOffline) {
- FSTWarn(@"Could not reach Firestore backend.");
- self.shouldWarnOffline = NO;
- }
- [self updateOnlineState:FSTOnlineStateFailed];
- }
- }
+- (void)setOnlineStateDelegate:(nullable id<FSTOnlineStateDelegate>)delegate {
+ self.onlineStateTracker.onlineStateDelegate = delegate;
}
#pragma mark Online/Offline state
@@ -235,18 +171,17 @@ static const int kOnlineAttemptsBeforeFailure = 2;
if ([self shouldStartWatchStream]) {
[self startWatchStream];
+ } else {
+ [self.onlineStateTracker updateState:FSTOnlineStateUnknown];
}
[self fillWritePipeline]; // This may start the writeStream.
-
- // We move back to the unknown state because we might not want to re-open the stream
- [self updateOnlineState:FSTOnlineStateUnknown];
}
- (void)disableNetwork {
[self disableNetworkInternal];
- // Set the FSTOnlineState to failed so get()'s return from cache, etc.
- [self updateOnlineState:FSTOnlineStateFailed];
+ // Set the FSTOnlineState to Offline so get()s return from cache, etc.
+ [self.onlineStateTracker updateState:FSTOnlineStateOffline];
}
/** Disables the network, setting the FSTOnlineState to the specified targetOnlineState. */
@@ -270,9 +205,9 @@ static const int kOnlineAttemptsBeforeFailure = 2;
- (void)shutdown {
FSTLog(@"FSTRemoteStore %p shutting down", (__bridge void *)self);
[self disableNetworkInternal];
- // Set the FSTOnlineState to Unknown (rather than Failed) to avoid potentially triggering
+ // Set the FSTOnlineState to Unknown (rather than Offline) to avoid potentially triggering
// spurious listener events with cached data, etc.
- [self updateOnlineState:FSTOnlineStateUnknown];
+ [self.onlineStateTracker updateState:FSTOnlineStateUnknown];
}
- (void)userDidChange:(const User &)user {
@@ -283,7 +218,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
// for the new user and re-fill the write pipeline with new mutations from the LocalStore
// (since mutations are per-user).
[self disableNetworkInternal];
- [self updateOnlineState:FSTOnlineStateUnknown];
+ [self.onlineStateTracker updateState:FSTOnlineStateUnknown];
[self enableNetwork];
}
}
@@ -294,6 +229,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
FSTAssert([self shouldStartWatchStream],
@"startWatchStream: called when shouldStartWatchStream: is false.");
[self.watchStream startWithDelegate:self];
+ [self.onlineStateTracker handleWatchStreamStart];
}
- (void)listenToTargetWithQueryData:(FSTQueryData *)queryData {
@@ -365,8 +301,8 @@ static const int kOnlineAttemptsBeforeFailure = 2;
- (void)watchStreamDidChange:(FSTWatchChange *)change
snapshotVersion:(FSTSnapshotVersion *)snapshotVersion {
- // Mark the connection as healthy because we got a message from the server.
- [self updateOnlineState:FSTOnlineStateHealthy];
+ // Mark the connection as Online because we got a message from the server.
+ [self.onlineStateTracker updateState:FSTOnlineStateOnline];
FSTWatchTargetChange *watchTargetChange =
[change isKindOfClass:[FSTWatchTargetChange class]] ? (FSTWatchTargetChange *)change : nil;
@@ -397,19 +333,20 @@ static const int kOnlineAttemptsBeforeFailure = 2;
- (void)watchStreamWasInterruptedWithError:(nullable NSError *)error {
FSTAssert([self isNetworkEnabled],
- @"watchStreamDidClose should only be called when the network is enabled");
+ @"watchStreamWasInterruptedWithError: should only be called when the network is "
+ "enabled");
[self cleanUpWatchStreamState];
+ [self.onlineStateTracker handleWatchStreamFailure];
// If the watch stream closed due to an error, retry the connection if there are any active
// watch targets.
if ([self shouldStartWatchStream]) {
- [self updateOnlineStateAfterFailure];
[self startWatchStream];
} else {
// We don't need to restart the watch stream because there are no active targets. The online
// state is set to unknown because there is no active attempt at establishing a connection.
- [self updateOnlineState:FSTOnlineStateUnknown];
+ [self.onlineStateTracker updateState:FSTOnlineStateUnknown];
}
}
@@ -604,8 +541,6 @@ static const int kOnlineAttemptsBeforeFailure = 2;
}
- (void)writeStreamDidOpen {
- self.writeStreamOpenTime = [NSDate date];
-
[self.writeStream writeHandshake];
}
diff --git a/Firestore/Source/Remote/FSTStream.mm b/Firestore/Source/Remote/FSTStream.mm
index 6bec3ad..6735df1 100644
--- a/Firestore/Source/Remote/FSTStream.mm
+++ b/Firestore/Source/Remote/FSTStream.mm
@@ -620,7 +620,7 @@ static const NSTimeInterval kIdleTimeout = 60.0;
serializer:(FSTSerializerBeta *)serializer {
self = [super initWithDatabase:database
workerDispatchQueue:workerDispatchQueue
- connectionTimerID:FSTTimerIDListenStreamConnection
+ connectionTimerID:FSTTimerIDListenStreamConnectionBackoff
idleTimerID:FSTTimerIDListenStreamIdle
credentials:credentials
responseMessageClass:[GCFSListenResponse class]];
@@ -705,7 +705,7 @@ static const NSTimeInterval kIdleTimeout = 60.0;
serializer:(FSTSerializerBeta *)serializer {
self = [super initWithDatabase:database
workerDispatchQueue:workerDispatchQueue
- connectionTimerID:FSTTimerIDWriteStreamConnection
+ connectionTimerID:FSTTimerIDWriteStreamConnectionBackoff
idleTimerID:FSTTimerIDWriteStreamIdle
credentials:credentials
responseMessageClass:[GCFSWriteResponse class]];
diff --git a/Firestore/Source/Util/FSTDispatchQueue.h b/Firestore/Source/Util/FSTDispatchQueue.h
index 9b28c9c..7922600 100644
--- a/Firestore/Source/Util/FSTDispatchQueue.h
+++ b/Firestore/Source/Util/FSTDispatchQueue.h
@@ -23,11 +23,24 @@ NS_ASSUME_NONNULL_BEGIN
* can then be used from tests to check for the presence of callbacks or to run them early.
*/
typedef NS_ENUM(NSInteger, FSTTimerID) {
- FSTTimerIDAll, // Sentinel value to be used with runDelayedCallbacksUntil: to run all blocks.
+ /** All can be used with runDelayedCallbacksUntil: to run all timers. */
+ FSTTimerIDAll,
+
+ /**
+ * The following 4 timers are used in FSTStream for the listen and write streams. The "Idle" timer
+ * is used to close the stream due to inactivity. The "ConnectionBackoff" timer is used to
+ * restart a stream once the appropriate backoff delay has elapsed.
+ */
FSTTimerIDListenStreamIdle,
- FSTTimerIDListenStreamConnection,
+ FSTTimerIDListenStreamConnectionBackoff,
FSTTimerIDWriteStreamIdle,
- FSTTimerIDWriteStreamConnection
+ FSTTimerIDWriteStreamConnectionBackoff,
+
+ /**
+ * A timer used in FSTOnlineStateTracker to transition from FSTOnlineState Unknown to Offline
+ * after a set timeout, rather than waiting indefinitely for success or failure.
+ */
+ FSTTimerIDOnlineStateTimeout
};
/**
@@ -81,6 +94,13 @@ typedef NS_ENUM(NSInteger, FSTTimerID) {
- (void)dispatchAsyncAllowingSameQueue:(void (^)(void))block;
/**
+ * Wrapper for dispatch_sync(). Mostly meant for use in tests.
+ *
+ * @param block The block to run.
+ */
+- (void)dispatchSync:(void (^)(void))block;
+
+/**
* Schedules a callback after the specified delay.
*
* Unlike dispatchAsync: this method does not require you to dispatch to a different queue than
diff --git a/Firestore/Source/Util/FSTDispatchQueue.mm b/Firestore/Source/Util/FSTDispatchQueue.mm
index 5bd7f27..3184d29 100644
--- a/Firestore/Source/Util/FSTDispatchQueue.mm
+++ b/Firestore/Source/Util/FSTDispatchQueue.mm
@@ -187,6 +187,10 @@ NS_ASSUME_NONNULL_BEGIN
dispatch_async(self.queue, block);
}
+- (void)dispatchSync:(void (^)(void))block {
+ dispatch_sync(self.queue, block);
+}
+
- (FSTDelayedCallback *)dispatchAfterDelay:(NSTimeInterval)delay
timerID:(FSTTimerID)timerID
block:(void (^)(void))block {