2017-05-15 12:27:07 -07:00
|
|
|
/*
|
|
|
|
|
* 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"
|
2019-08-05 13:27:47 -07:00
|
|
|
#import <FirebaseCore/FIRLogger.h>
|
2017-05-15 12:27:07 -07:00
|
|
|
|
|
|
|
|
@interface FIRRetryHelperTask : NSObject
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
@property(nonatomic, strong) void (^block)(void);
|
2017-05-15 12:27:07 -07:00
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation FIRRetryHelperTask
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
- (instancetype)initWithBlock:(void (^)(void))block {
|
2017-05-15 12:27:07 -07:00
|
|
|
self = [super init];
|
|
|
|
|
if (self != nil) {
|
|
|
|
|
self->_block = [block copy];
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
- (BOOL)isCanceled {
|
2017-05-15 12:27:07 -07:00
|
|
|
return self.block == nil;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
- (void)cancel {
|
2017-05-15 12:27:07 -07:00
|
|
|
self.block = nil;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
- (void)execute {
|
2017-05-15 12:27:07 -07:00
|
|
|
if (self.block) {
|
|
|
|
|
self.block();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@interface FIRRetryHelper ()
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
@property(nonatomic, strong) dispatch_queue_t dispatchQueue;
|
|
|
|
|
@property(nonatomic) NSTimeInterval minRetryDelayAfterFailure;
|
|
|
|
|
@property(nonatomic) NSTimeInterval maxRetryDelay;
|
|
|
|
|
@property(nonatomic) double retryExponent;
|
|
|
|
|
@property(nonatomic) double jitterFactor;
|
2017-05-15 12:27:07 -07:00
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
@property(nonatomic) BOOL lastWasSuccess;
|
|
|
|
|
@property(nonatomic) NSTimeInterval currentRetryDelay;
|
2017-05-15 12:27:07 -07:00
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
@property(nonatomic, strong) FIRRetryHelperTask *scheduledRetry;
|
2017-05-15 12:27:07 -07:00
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation FIRRetryHelper
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
- (instancetype)initWithDispatchQueue:(dispatch_queue_t)dispatchQueue
|
|
|
|
|
minRetryDelayAfterFailure:(NSTimeInterval)minRetryDelayAfterFailure
|
|
|
|
|
maxRetryDelay:(NSTimeInterval)maxRetryDelay
|
|
|
|
|
retryExponent:(double)retryExponent
|
|
|
|
|
jitterFactor:(double)jitterFactor {
|
2017-05-15 12:27:07 -07:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
- (void)retry:(void (^)(void))block {
|
2017-05-15 12:27:07 -07:00
|
|
|
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 {
|
2019-08-05 13:27:47 -07:00
|
|
|
NSTimeInterval newDelay =
|
|
|
|
|
(self.currentRetryDelay * self.retryExponent);
|
2017-05-15 12:27:07 -07:00
|
|
|
self.currentRetryDelay = MIN(newDelay, self.maxRetryDelay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delay = ((1 - self.jitterFactor) * self.currentRetryDelay) +
|
2019-08-05 13:27:47 -07:00
|
|
|
(self.jitterFactor * self.currentRetryDelay *
|
|
|
|
|
[FUtilities randomDouble]);
|
2017-05-15 12:27:07 -07:00
|
|
|
FFLog(@"I-RDB054002", @"Scheduling retry in %fs", delay);
|
|
|
|
|
}
|
|
|
|
|
self.lastWasSuccess = NO;
|
|
|
|
|
FIRRetryHelperTask *task = [[FIRRetryHelperTask alloc] initWithBlock:block];
|
|
|
|
|
self.scheduledRetry = task;
|
2019-08-05 13:27:47 -07:00
|
|
|
dispatch_time_t popTime =
|
|
|
|
|
dispatch_time(DISPATCH_TIME_NOW, (long long)(delay * NSEC_PER_SEC));
|
2017-05-15 12:27:07 -07:00
|
|
|
dispatch_after(popTime, self.dispatchQueue, ^{
|
2019-08-05 13:27:47 -07:00
|
|
|
if (![task isCanceled]) {
|
|
|
|
|
self.scheduledRetry = nil;
|
|
|
|
|
[task execute];
|
|
|
|
|
}
|
2017-05-15 12:27:07 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
- (void)signalSuccess {
|
2017-05-15 12:27:07 -07:00
|
|
|
self.lastWasSuccess = YES;
|
|
|
|
|
self.currentRetryDelay = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
- (void)cancel {
|
2017-05-15 12:27:07 -07:00
|
|
|
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
|