mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-11 22:55:59 +05:45
[Port of https://github.com/firebase/firebase-js-sdk/commit/0fa319e5e019dd0d40ab441d2ff9f8f6d4724e43] * Refactored FSTOnlineState tracking out of FSTRemoteStore and into new FSTOnlineStateTracker component. * Added a 10 second timeout to transition from OnlineState.Unknown to OnlineState.Offline rather than waiting indefinitely for the stream to succeed or fail. * Removed hack to run SpecTests using an FSTDispatchQueue that wrapped dispatch_get_main_queue(). This was incompatible with [FSTDispatchQueue runDelayedCallbacksUntil:] since it queues work and blocks waiting for it to complete. Now spec tests create / use a proper FSTDispatchQueue. * Added a SpecTest to verify OnlineState timeout behavior. * Misc cleanup: * Renamed FSTOnlineState states: Failed => Offline, Healthy => Online * Renamed FSTTimerIds (ListenStreamConnection => ListenStreamConnectionBackoff) * Added ability to run timers from spec tests.
150 lines
5.0 KiB
Plaintext
150 lines
5.0 KiB
Plaintext
/*
|
|
* 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
|