diff options
Diffstat (limited to 'Firestore')
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 { |