Skip to content

Add 10 second timeout waiting for connection before client behaves as-if offline. #872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 4 additions & 4 deletions Firestore/Example/Tests/Core/FSTEventManagerTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ - (void)testWillForwardOnlineStateChanges {
.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]);
Expand All @@ -154,8 +154,8 @@ - (void)testWillForwardOnlineStateChanges {

[eventManager addListener:fakeListener];
XCTAssertEqualObjects(events, @[ @(FSTOnlineStateUnknown) ]);
[eventManager applyChangedOnlineState:FSTOnlineStateHealthy];
XCTAssertEqualObjects(events, (@[ @(FSTOnlineStateUnknown), @(FSTOnlineStateHealthy) ]));
[eventManager applyChangedOnlineState:FSTOnlineStateOnline];
XCTAssertEqualObjects(events, (@[ @(FSTOnlineStateUnknown), @(FSTOnlineStateOnline) ]));
}

@end
Expand Down
18 changes: 9 additions & 9 deletions Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -340,10 +340,10 @@ - (void)testWillWaitForSyncIfOnline {
[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];

Expand Down Expand Up @@ -379,11 +379,11 @@ - (void)testWillRaiseInitialEventWhenGoingOffline {
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 =
Expand Down Expand Up @@ -419,9 +419,9 @@ - (void)testWillRaiseInitialEventWhenGoingOfflineAndThereAreNoDocs {
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
Expand All @@ -445,8 +445,8 @@ - (void)testWillRaiseInitialEventWhenStartingOfflineAndThereAreNoDocs {
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
Expand Down
4 changes: 3 additions & 1 deletion Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,9 @@ - (void)setUp {
workerDispatchQueue:_testWorkerQueue
credentials:&_credentials];

_remoteStore = [FSTRemoteStore remoteStoreWithLocalStore:_localStore datastore:_datastore];
_remoteStore = [[FSTRemoteStore alloc] initWithLocalStore:_localStore
datastore:_datastore
workerDispatchQueue:_testWorkerQueue];

[_testWorkerQueue dispatchAsync:^() {
[_remoteStore start];
Expand Down
25 changes: 24 additions & 1 deletion Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -323,6 +323,27 @@ - (void)doFailWrite:(NSDictionary *)spec {
}
}

- (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];
}
Expand Down Expand Up @@ -391,6 +412,8 @@ - (void)doStep:(NSDictionary *)step {
[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];
Expand Down
6 changes: 6 additions & 0 deletions Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -223,6 +224,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
Expand Down
95 changes: 64 additions & 31 deletions Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -72,6 +71,7 @@ @interface FSTSyncEngineTestDriver ()
@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.

Expand Down Expand Up @@ -117,16 +117,19 @@ - (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
_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
Expand Down Expand Up @@ -168,8 +171,10 @@ - (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
}

- (void)start {
[self.localStore start];
[self.remoteStore start];
[self.dispatchQueue dispatchSync:^{
[self.localStore start];
[self.remoteStore start];
}];
}

- (void)validateUsage {
Expand All @@ -180,8 +185,10 @@ - (void)validateUsage {
}

- (void)shutdown {
[self.remoteStore shutdown];
[self.localStore shutdown];
[self.dispatchQueue dispatchSync:^{
[self.remoteStore shutdown];
[self.localStore shutdown];
}];
}

- (void)validateNextWriteSent:(FSTMutation *)expectedWrite {
Expand All @@ -208,19 +215,29 @@ - (int)watchStreamRequestCount {
}

- (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
Expand All @@ -230,7 +247,9 @@ - (FSTOutstandingWrite *)receiveWriteAckWithVersion:(FSTSnapshotVersion *)commit
[[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;
}
Expand All @@ -250,7 +269,9 @@ - (FSTOutstandingWrite *)receiveWriteError:(int)errorCode
}

FSTLog(@"Failing a write.");
[self.datastore failWriteWithError:error];
[self.dispatchQueue dispatchSync:^{
[self.datastore failWriteWithError:error];
}];

return write;
}
Expand Down Expand Up @@ -278,42 +299,54 @@ - (FSTTargetID)addUserListenerWithQuery:(FSTQuery *)query {
[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 {
FSTOutstandingWrite *write = [[FSTOutstandingWrite alloc] init];
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 {
Expand Down
Loading