Files
firebase-ios-sdk/Firebase/Database/Core/Utilities/FIRRetryHelper.m
T
2017-05-15 12:27:07 -07:00

140 lines
3.9 KiB
Objective-C

/*
* Copyright 2017 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 "FIRRetryHelper.h"
#import "FUtilities.h"
@interface FIRRetryHelperTask : NSObject
@property (nonatomic, strong) void (^block)();
@end
@implementation FIRRetryHelperTask
- (instancetype) initWithBlock:(void (^)())block {
self = [super init];
if (self != nil) {
self->_block = [block copy];
}
return self;
}
- (BOOL) isCanceled {
return self.block == nil;
}
- (void) cancel {
self.block = nil;
}
- (void) execute {
if (self.block) {
self.block();
}
}
@end
@interface FIRRetryHelper ()
@property (nonatomic, strong) dispatch_queue_t dispatchQueue;
@property (nonatomic) NSTimeInterval minRetryDelayAfterFailure;
@property (nonatomic) NSTimeInterval maxRetryDelay;
@property (nonatomic) double retryExponent;
@property (nonatomic) double jitterFactor;
@property (nonatomic) BOOL lastWasSuccess;
@property (nonatomic) NSTimeInterval currentRetryDelay;
@property (nonatomic, strong) FIRRetryHelperTask *scheduledRetry;
@end
@implementation FIRRetryHelper
- (instancetype) initWithDispatchQueue:(dispatch_queue_t)dispatchQueue
minRetryDelayAfterFailure:(NSTimeInterval)minRetryDelayAfterFailure
maxRetryDelay:(NSTimeInterval)maxRetryDelay
retryExponent:(double)retryExponent
jitterFactor:(double)jitterFactor {
self = [super init];
if (self != nil) {
self->_dispatchQueue = dispatchQueue;
self->_minRetryDelayAfterFailure = minRetryDelayAfterFailure;
self->_maxRetryDelay = maxRetryDelay;
self->_retryExponent = retryExponent;
self->_jitterFactor = jitterFactor;
self->_lastWasSuccess = YES;
}
return self;
}
- (void) retry:(void (^)())block {
if (self.scheduledRetry != nil) {
FFLog(@"I-RDB054001", @"Canceling existing retry attempt");
[self.scheduledRetry cancel];
self.scheduledRetry = nil;
}
NSTimeInterval delay;
if (self.lastWasSuccess) {
delay = 0;
} else {
if (self.currentRetryDelay == 0) {
self.currentRetryDelay = self.minRetryDelayAfterFailure;
} else {
NSTimeInterval newDelay = (self.currentRetryDelay * self.retryExponent);
self.currentRetryDelay = MIN(newDelay, self.maxRetryDelay);
}
delay = ((1 - self.jitterFactor) * self.currentRetryDelay) +
(self.jitterFactor * self.currentRetryDelay * [FUtilities randomDouble]);
FFLog(@"I-RDB054002", @"Scheduling retry in %fs", delay);
}
self.lastWasSuccess = NO;
FIRRetryHelperTask *task = [[FIRRetryHelperTask alloc] initWithBlock:block];
self.scheduledRetry = task;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (long long)(delay * NSEC_PER_SEC));
dispatch_after(popTime, self.dispatchQueue, ^{
if (![task isCanceled]) {
self.scheduledRetry = nil;
[task execute];
}
});
}
- (void) signalSuccess {
self.lastWasSuccess = YES;
self.currentRetryDelay = 0;
}
- (void) cancel {
if (self.scheduledRetry != nil) {
FFLog(@"I-RDB054003", @"Canceling existing retry attempt");
[self.scheduledRetry cancel];
self.scheduledRetry = nil;
} else {
FFLog(@"I-RDB054004", @"No existing retry attempt to cancel");
}
self.currentRetryDelay = 0;
}
@end